import { ListType, AlignType, DirectionType, ScriptType } from './value-types'; import { MentionSanitizer } from './mentions/MentionSanitizer'; import * as url from './helpers/url'; import { encodeLink } from './funcs-html'; import { IMention } from './mentions/MentionSanitizer'; import { find } from './helpers/array'; interface TableCellLineAttributes { row?: string | undefined; cell?: string | undefined; rowspan?: string | undefined; colspan?: string | undefined; } interface TableColAttributes { width?: string | undefined; } interface ListAttributes { row?: string | undefined; cell?: string | undefined; rowspan?: string | undefined; colspan?: string | undefined; list: ListType; } interface BlockquoteAttributes { row?: string | undefined; cell?: string | undefined; rowspan?: string | undefined; colspan?: string | undefined; 'in-list'?: string | undefined; 'wrapper-indent'?: string | undefined; } interface CodeBlockAttributes { row?: string | undefined; cell?: string | undefined; rowspan?: string | undefined; colspan?: string | undefined; 'in-list'?: string | undefined; 'wrapper-indent'?: string | undefined; 'code-block'?: string | boolean | undefined; } interface IOpAttributes { background?: string | undefined; color?: string | undefined; font?: string | undefined; size?: string | undefined; width?: string | undefined; link?: string | undefined; bold?: boolean | undefined; italic?: boolean | undefined; underline?: boolean | undefined; strike?: boolean | undefined; script?: ScriptType; code?: boolean | undefined; list?: ListAttributes; blockquote?: BlockquoteAttributes; 'code-block'?: CodeBlockAttributes; header?: number | undefined; align?: AlignType; direction?: DirectionType; indent?: number | undefined; table?: string | undefined; 'table-cell-line'?: TableCellLineAttributes | undefined; row?: string | undefined; colspan?: string | undefined; rowspan?: string | undefined; 'table-col'?: TableColAttributes | undefined; mentions?: boolean | undefined; mention?: IMention | undefined; target?: string | undefined; rel?: string | undefined; // should this custom blot be rendered as block? renderAsBlock?: boolean | undefined; [key: string]: any; } interface IUrlSanitizerFn { (url: string): string | undefined; } interface IOpAttributeSanitizerOptions { urlSanitizer?: IUrlSanitizerFn; } class OpAttributeSanitizer { static sanitize( dirtyAttrs: IOpAttributes, sanitizeOptions: IOpAttributeSanitizerOptions ): IOpAttributes { var cleanAttrs: any = {}; if (!dirtyAttrs || typeof dirtyAttrs !== 'object') { return cleanAttrs; } let booleanAttrs = [ 'bold', 'italic', 'underline', 'strike', 'code', 'code-block', 'renderAsBlock', ]; let colorAttrs = ['background', 'color']; let { font, size, link, script, list, header, align, direction, indent, mentions, mention, width, target, rel, blockquote, } = dirtyAttrs; let codeBlock = dirtyAttrs['code-block']; let sanitizedAttrs = [ ...booleanAttrs, ...colorAttrs, 'font', 'size', 'link', 'script', 'list', 'blockquote', 'header', 'align', 'direction', 'indent', 'mentions', 'mention', 'width', 'target', 'rel', 'code-block', ]; booleanAttrs.forEach(function (prop: string) { var v = (dirtyAttrs)[prop]; if (v) { cleanAttrs[prop] = !!v; } }); colorAttrs.forEach(function (prop: string) { var val = (dirtyAttrs)[prop]; if ( val && (OpAttributeSanitizer.IsValidHexColor(val + '') || OpAttributeSanitizer.IsValidColorLiteral(val + '') || OpAttributeSanitizer.IsValidRGBColor(val + '')) ) { cleanAttrs[prop] = val; } }); if (font && OpAttributeSanitizer.IsValidFontName(font + '')) { cleanAttrs.font = font; } if (size && OpAttributeSanitizer.IsValidSize(size + '')) { cleanAttrs.size = size; } if (width && OpAttributeSanitizer.IsValidWidth(width + '')) { cleanAttrs.width = width; } if (link) { cleanAttrs.link = OpAttributeSanitizer.sanitizeLinkUsingOptions( link + '', sanitizeOptions ); } if (target && OpAttributeSanitizer.isValidTarget(target)) { cleanAttrs.target = target; } if (rel && OpAttributeSanitizer.IsValidRel(rel)) { cleanAttrs.rel = rel; } if (codeBlock) { let codeBlockLang: string | boolean = ''; if (typeof codeBlock === 'object') { codeBlockLang = codeBlock['code-block'] || true; } else { codeBlockLang = codeBlock; } if (OpAttributeSanitizer.IsValidLang(codeBlockLang)) { cleanAttrs['code-block'] = codeBlock; } else { cleanAttrs['code-block'] = !!codeBlock; } } if (script === ScriptType.Sub || ScriptType.Super === script) { cleanAttrs.script = script; } if ( list && (list!.list === ListType.Bullet || list!.list === ListType.Ordered || list!.list === ListType.Toggled || list!.list === ListType.NoneType || list!.list === ListType.Checked || list!.list === ListType.Unchecked) ) { cleanAttrs.list = list; } if (blockquote) { cleanAttrs.blockquote = blockquote; } if (Number(header)) { cleanAttrs.header = Math.min(Number(header), 6); } if ( find( [AlignType.Center, AlignType.Right, AlignType.Justify, AlignType.Left], (a) => a === align ) ) { cleanAttrs.align = align; } if (direction === DirectionType.Rtl) { cleanAttrs.direction = direction; } if (indent && Number(indent)) { cleanAttrs.indent = Math.min(Number(indent), 30); } if (mentions && mention) { let sanitizedMention = MentionSanitizer.sanitize( mention, sanitizeOptions ); if (Object.keys(sanitizedMention).length > 0) { cleanAttrs.mentions = !!mentions; cleanAttrs.mention = mention; } } return Object.keys(dirtyAttrs).reduce((cleaned, k) => { // this is a custom attr, put it back if (sanitizedAttrs.indexOf(k) === -1) { cleaned[k] = (dirtyAttrs)[k]; } return cleaned; }, cleanAttrs); } static sanitizeLinkUsingOptions( link: string, options: IOpAttributeSanitizerOptions ) { let sanitizerFn: IUrlSanitizerFn = () => { return undefined; }; if (options && typeof options.urlSanitizer === 'function') { sanitizerFn = options.urlSanitizer; } let result = sanitizerFn(link); return typeof result === 'string' ? result : encodeLink(url.sanitize(link)); } static IsValidHexColor(colorStr: string) { return !!colorStr.match(/^#([0-9A-F]{6}|[0-9A-F]{3})$/i); } static IsValidColorLiteral(colorStr: string) { return !!colorStr.match(/^[a-z]{1,50}$/i); } static IsValidRGBColor(colorStr: string) { const re = /^rgb\(((0|25[0-5]|2[0-4]\d|1\d\d|0?\d?\d),\s*){2}(0|25[0-5]|2[0-4]\d|1\d\d|0?\d?\d)\)$/i; return !!colorStr.match(re); } static IsValidFontName(fontName: string) { return !!fontName.match(/^[a-z\s0-9\- ]{1,30}$/i); } static IsValidSize(size: string) { return !!size.match(/^[a-z0-9\-]{1,20}$/i); } static IsValidWidth(width: string) { return !!width.match(/^[0-9]*(px|em|%)?$/); } static isValidTarget(target: string) { return !!target.match(/^[_a-zA-Z0-9\-]{1,50}$/); } static IsValidRel(relStr: string) { return !!relStr.match(/^[a-zA-Z\s\-]{1,250}$/i); } static IsValidLang(lang: string | boolean) { if (typeof lang === 'boolean') { return true; } return ( typeof lang === 'string' && !!lang.match(/^[a-zA-Z\s\-\\\/\+]{1,50}$/i) ); } } export { OpAttributeSanitizer, IOpAttributes, IOpAttributeSanitizerOptions };