Merge pull request #6638 from allenve/xss-fix

fix: html 组件支持filterHtml
This commit is contained in:
hsm-lv 2023-04-18 15:24:46 +08:00 committed by GitHub
commit dacca057e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 145 additions and 61 deletions

View File

@ -14,6 +14,7 @@ export interface HtmlProps {
inline: boolean; inline: boolean;
classPrefix: string; classPrefix: string;
classnames: ClassNamesFn; classnames: ClassNamesFn;
filterHtml?: (input: string) => string;
} }
export class Html extends React.Component<HtmlProps> { export class Html extends React.Component<HtmlProps> {
@ -45,10 +46,10 @@ export class Html extends React.Component<HtmlProps> {
} }
_render() { _render() {
const {html} = this.props; const {html, filterHtml} = this.props;
if (html) { if (html) {
this.dom.innerHTML = html; this.dom.innerHTML = filterHtml ? filterHtml(html) : html;
} }
} }

View File

@ -80,6 +80,10 @@ export interface TooltipObject {
* CSS类名 * CSS类名
*/ */
tooltipClassName?: string; tooltipClassName?: string;
/**
* html xss filter
*/
filterHtml?: (input: string) => string;
} }
export interface TooltipWrapperProps { export interface TooltipWrapperProps {
@ -300,7 +304,8 @@ export class TooltipWrapper extends React.Component<
offset, offset,
tooltipTheme = 'light', tooltipTheme = 'light',
showArrow = true, showArrow = true,
children children,
filterHtml
} = tooltipObj; } = tooltipObj;
const childProps: any = { const childProps: any = {
@ -352,7 +357,10 @@ export class TooltipWrapper extends React.Component<
{children ? ( {children ? (
<>{typeof children === 'function' ? children() : children}</> <>{typeof children === 'function' ? children() : children}</>
) : ( ) : (
<Html html={typeof content === 'string' ? content : ''} /> <Html
html={typeof content === 'string' ? content : ''}
filterHtml={filterHtml}
/>
)} )}
</Tooltip> </Tooltip>
</Overlay> </Overlay>

View File

@ -280,7 +280,15 @@ export default class App extends React.Component<AppProps, object> {
} }
renderHeader() { renderHeader() {
const {classnames: cx, brandName, header, render, store, logo} = this.props; const {
classnames: cx,
brandName,
header,
render,
store,
logo,
env
} = this.props;
return ( return (
<> <>
@ -294,7 +302,11 @@ export default class App extends React.Component<AppProps, object> {
<div className={cx('Layout-brand')}> <div className={cx('Layout-brand')}>
{logo && ~logo.indexOf('<svg') ? ( {logo && ~logo.indexOf('<svg') ? (
<Html className={cx('AppLogo-html')} html={logo} /> <Html
className={cx('AppLogo-html')}
html={logo}
filterHtml={env.filterHtml}
/>
) : logo ? ( ) : logo ? (
<img className={cx('AppLogo')} src={logo} /> <img className={cx('AppLogo')} src={logo} />
) : null} ) : null}

View File

@ -2126,7 +2126,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
labelField, labelField,
labelTpl, labelTpl,
primaryField, primaryField,
translate: __ translate: __,
env
} = this.props; } = this.props;
if (!store.selectedItems.length) { if (!store.selectedItems.length) {
@ -2150,7 +2151,10 @@ export default class CRUD extends React.Component<CRUDProps, any> {
</span> </span>
<span className={cx('Crud-valueLabel')}> <span className={cx('Crud-valueLabel')}>
{labelTpl ? ( {labelTpl ? (
<Html html={filter(labelTpl, item)} /> <Html
html={filter(labelTpl, item)}
filterHtml={env.filterHtml}
/>
) : ( ) : (
getVariable(item, labelField || 'label') || getVariable(item, labelField || 'label') ||
getVariable(item, primaryField || 'id') getVariable(item, primaryField || 'id')

View File

@ -1087,7 +1087,8 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
labelField, labelField,
labelTpl, labelTpl,
primaryField, primaryField,
translate: __ translate: __,
env
} = this.props; } = this.props;
if (!store.selectedItems.length) { if (!store.selectedItems.length) {
@ -1111,7 +1112,10 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
</span> </span>
<span className={cx('Crud-valueLabel')}> <span className={cx('Crud-valueLabel')}>
{labelTpl ? ( {labelTpl ? (
<Html html={filter(labelTpl, item)} /> <Html
html={filter(labelTpl, item)}
filterHtml={env.filterHtml}
/>
) : ( ) : (
getVariable(item, labelField || 'label') || getVariable(item, labelField || 'label') ||
getVariable(item, primaryField || 'id') getVariable(item, primaryField || 'id')

View File

@ -99,7 +99,7 @@ export interface CarouselSchema extends BaseSchema {
* *
*/ */
multiple?: { multiple?: {
count: number count: number;
}; };
/** /**
@ -152,7 +152,7 @@ const defaultSchema = {
className={cx('Carousel-image')} className={cx('Carousel-image')}
/> />
) : data.hasOwnProperty('html') ? ( ) : data.hasOwnProperty('html') ? (
<Html html={data.html} /> <Html html={data.html} filterHtml={props.env.filterHtml} />
) : data.hasOwnProperty('item') ? ( ) : data.hasOwnProperty('item') ? (
<span>{data.item}</span> <span>{data.item}</span>
) : ( ) : (
@ -431,7 +431,7 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
newOptions = new Array(options.length); newOptions = new Array(options.length);
for (let i = 0; i < options.length; i++) { for (let i = 0; i < options.length; i++) {
newOptions[i] = new Array(count); newOptions[i] = new Array(count);
for(let j = 0; j < count; j++) { for (let j = 0; j < count; j++) {
newOptions[i][j] = options[(i + j) % options.length]; newOptions[i][j] = options[(i + j) % options.length];
} }
} }
@ -475,13 +475,23 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
if (Array.isArray(options) && options.length) { if (Array.isArray(options) && options.length) {
let multipleCount = 1; let multipleCount = 1;
if (multiple && typeof multiple.count === 'number' && multiple.count >= 2) { if (
multipleCount = Math.floor(multiple.count) < options.length ? Math.floor(multiple.count) : options.length; multiple &&
typeof multiple.count === 'number' &&
multiple.count >= 2
) {
multipleCount =
Math.floor(multiple.count) < options.length
? Math.floor(multiple.count)
: options.length;
} }
const newOptions = this.getNewOptions(options, multipleCount); const newOptions = this.getNewOptions(options, multipleCount);
const transitionDuration = multipleCount > 1 && typeof duration === 'number' const transitionDuration =
? `${duration}ms`: (duration || '500ms'); multipleCount > 1 && typeof duration === 'number'
const timeout = multipleCount > 1 && typeof duration === 'number' ? duration : 500; ? `${duration}ms`
: duration || '500ms';
const timeout =
multipleCount > 1 && typeof duration === 'number' ? duration : 500;
body = ( body = (
<div <div
@ -506,9 +516,15 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
); );
} }
if (multipleCount > 1) { if (multipleCount > 1) {
if ((status === ENTERING || status === EXITING) && !this.loading) { if (
(status === ENTERING || status === EXITING) &&
!this.loading
) {
this.loading = true; this.loading = true;
} else if ((status === ENTERED || status === EXITED) && this.loading) { } else if (
(status === ENTERED || status === EXITED) &&
this.loading
) {
this.loading = false; this.loading = false;
} }
} }
@ -518,15 +534,29 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
} = { } = {
[ENTERING]: 0, [ENTERING]: 0,
[ENTERED]: 0, [ENTERED]: 0,
[EXITING]: animationName === 'slideRight' ? 100 / multipleCount : -100 / multipleCount, [EXITING]:
[EXITED]: animationName === 'slideRight' ? -100 / multipleCount : 100 / multipleCount animationName === 'slideRight'
? 100 / multipleCount
: -100 / multipleCount,
[EXITED]:
animationName === 'slideRight'
? -100 / multipleCount
: 100 / multipleCount
}; };
const itemStyle = multipleCount > 1 ? { const itemStyle =
multipleCount > 1
? {
transitionTimingFunction: 'linear', transitionTimingFunction: 'linear',
transitionDuration: transitionDuration, transitionDuration: transitionDuration,
...(animation === 'slide' ? {transform: `translateX(${transformStyles[status]}%)`} : {}) ...(animation === 'slide'
} : {}; ? {
const itemRender = (option: any) => render( transform: `translateX(${transformStyles[status]}%)`
}
: {})
}
: {};
const itemRender = (option: any) =>
render(
`${current}/body`, `${current}/body`,
itemSchema ? itemSchema : (defaultSchema as any), itemSchema ? itemSchema : (defaultSchema as any),
{ {
@ -550,16 +580,20 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
style={itemStyle} style={itemStyle}
> >
{multipleCount === 1 ? itemRender(option) : null} {multipleCount === 1 ? itemRender(option) : null}
{multipleCount > 1 ? {multipleCount > 1
newOptions[key].map((option: any, index: number) => ( ? newOptions[key].map((option: any, index: number) => (
<div key={index} style={{ <div
key={index}
style={{
width: 100 / multipleCount + '%', width: 100 / multipleCount + '%',
height: '100%', height: '100%',
float: 'left' float: 'left'
}}> }}
>
{itemRender(option)} {itemRender(option)}
</div> </div>
)) : null} ))
: null}
</div> </div>
); );
}} }}
@ -571,7 +605,11 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
return ( return (
<div <div
className={cx(`Carousel Carousel--${controlsTheme}`, {['Carousel-arrow--always']: !!alwaysShowArrow}, className)} className={cx(
`Carousel Carousel--${controlsTheme}`,
{['Carousel-arrow--always']: !!alwaysShowArrow},
className
)}
style={carouselStyles} style={carouselStyles}
> >
{body ? body : placeholder} {body ? body : placeholder}
@ -579,16 +617,28 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
{dots ? this.renderDots() : null} {dots ? this.renderDots() : null}
{arrows ? ( {arrows ? (
<div className={cx('Carousel-leftArrow')} onClick={this.prev}> <div className={cx('Carousel-leftArrow')} onClick={this.prev}>
{icons && icons.prev {icons && icons.prev ? (
? React.isValidElement(icons.prev) ? icons.prev : render('arrow-prev', icons.prev) React.isValidElement(icons.prev) ? (
: (<Icon icon="left-arrow" className="icon" />)} icons.prev
) : (
render('arrow-prev', icons.prev)
)
) : (
<Icon icon="left-arrow" className="icon" />
)}
</div> </div>
) : null} ) : null}
{arrows ? ( {arrows ? (
<div className={cx('Carousel-rightArrow')} onClick={this.next}> <div className={cx('Carousel-rightArrow')} onClick={this.next}>
{icons && icons.next {icons && icons.next ? (
? React.isValidElement(icons.next) ? icons.next : render('arrow-next', icons.next) React.isValidElement(icons.next) ? (
: (<Icon icon="right-arrow" className="icon" />)} icons.next
) : (
render('arrow-next', icons.next)
)
) : (
<Icon icon="right-arrow" className="icon" />
)}
</div> </div>
) : null} ) : null}
</div> </div>

View File

@ -387,7 +387,8 @@ export default class PickerControl extends React.PureComponent<
labelField, labelField,
labelTpl, labelTpl,
translate: __, translate: __,
disabled disabled,
env
} = this.props; } = this.props;
return ( return (
@ -418,7 +419,10 @@ export default class PickerControl extends React.PureComponent<
}} }}
> >
{labelTpl ? ( {labelTpl ? (
<Html html={filter(labelTpl, item)} /> <Html
html={filter(labelTpl, item)}
filterHtml={env.filterHtml}
/>
) : ( ) : (
`${ `${
getVariable(item, labelField || 'label') || getVariable(item, labelField || 'label') ||

View File

@ -253,7 +253,8 @@ export default class TooltipWrapper extends React.Component<
offset, offset,
showArrow, showArrow,
disabled, disabled,
enterable enterable,
filterHtml: env.filterHtml
}; };
return ( return (