amuchina
Version:
A work-in-progress HTML sanitizer that strives for: performance like window.Sanitizer, readiness like DOMPurify, and ability to run in a WebWorker like neither of those.
854 lines (819 loc) • 12.4 kB
text/typescript
/* IMPORT */
import {mergeMaps} from './utils';
/* ELEMENTS */
const HTML_ELEMENTS_ALLOW = [
'a',
'abbr',
'acronym',
'address',
'area',
'article',
'aside',
'audio',
'b',
'bdi',
'bdo',
'bgsound',
'big',
'blockquote',
'body',
'br',
'button',
'canvas',
'caption',
'center',
'cite',
'code',
'col',
'colgroup',
'datalist',
'dd',
'del',
'details',
'dfn',
'dialog',
'dir',
'div',
'dl',
'dt',
'em',
'fieldset',
'figcaption',
'figure',
'font',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'header',
'hgroup',
'hr',
'html',
'i',
'img',
'input',
'ins',
'kbd',
'keygen',
'label',
'layer',
'legend',
'li',
'link',
'listing',
'main',
'map',
'mark',
'marquee',
'menu',
'meta',
'meter',
'nav',
'nobr',
'ol',
'optgroup',
'option',
'output',
'p',
'picture',
'popup',
'pre',
'progress',
'q',
'rb',
'rp',
'rt',
'rtc',
'ruby',
's',
'samp',
'section',
'select',
'selectmenu',
'small',
'source',
'span',
'strike',
'strong',
'style',
'sub',
'summary',
'sup',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'time',
'tr',
'track',
'tt',
'u',
'ul',
'var',
'video',
'wbr'
];
const HTML_ELEMENTS_DISALLOW = [
'basefont',
'command',
'data',
'iframe',
'image',
'plaintext',
'portal',
'slot',
// 'template', //TODO: Not exactly correct to never allow this, too strict
'textarea',
'title',
'xmp'
];
const HTML_ELEMENTS = new Set ([
...HTML_ELEMENTS_ALLOW,
...HTML_ELEMENTS_DISALLOW
]);
const SVG_ELEMENTS_ALLOW = [
'svg',
'a',
'altglyph',
'altglyphdef',
'altglyphitem',
'animatecolor',
'animatemotion',
'animatetransform',
'circle',
'clippath',
'defs',
'desc',
'ellipse',
'filter',
'font',
'g',
'glyph',
'glyphref',
'hkern',
'image',
'line',
'lineargradient',
'marker',
'mask',
'metadata',
'mpath',
'path',
'pattern',
'polygon',
'polyline',
'radialgradient',
'rect',
'stop',
'style',
'switch',
'symbol',
'text',
'textpath',
'title',
'tref',
'tspan',
'view',
'vkern',
/* FILTERS */
'feBlend',
'feColorMatrix',
'feComponentTransfer',
'feComposite',
'feConvolveMatrix',
'feDiffuseLighting',
'feDisplacementMap',
'feDistantLight',
'feFlood',
'feFuncA',
'feFuncB',
'feFuncG',
'feFuncR',
'feGaussianBlur',
'feImage',
'feMerge',
'feMergeNode',
'feMorphology',
'feOffset',
'fePointLight',
'feSpecularLighting',
'feSpotLight',
'feTile',
'feTurbulence'
];
const SVG_ELEMENTS_DISALLOW = [
'animate',
'color-profile',
'cursor',
'discard',
'fedropshadow',
'font-face',
'font-face-format',
'font-face-name',
'font-face-src',
'font-face-uri',
'foreignobject',
'hatch',
'hatchpath',
'mesh',
'meshgradient',
'meshpatch',
'meshrow',
'missing-glyph',
'script',
'set',
'solidcolor',
'unknown',
'use'
];
const SVG_ELEMENTS = new Set ([
...SVG_ELEMENTS_ALLOW,
...SVG_ELEMENTS_DISALLOW
]);
const MATH_ELEMENTS_ALLOW = [
'math',
'menclose',
'merror',
'mfenced',
'mfrac',
'mglyph',
'mi',
'mlabeledtr',
'mmultiscripts',
'mn',
'mo',
'mover',
'mpadded',
'mphantom',
'mroot',
'mrow',
'ms',
'mspace',
'msqrt',
'mstyle',
'msub',
'msup',
'msubsup',
'mtable',
'mtd',
'mtext',
'mtr',
'munder',
'munderover'
];
const MATH_ELEMENTS_DISALLOW = [
'maction',
'maligngroup',
'malignmark',
'mlongdiv',
'mscarries',
'mscarry',
'msgroup',
'mstack',
'msline',
'msrow',
'semantics',
'annotation',
'annotation-xml',
'mprescripts',
'none'
];
const MATH_ELEMENTS = new Set ([
...MATH_ELEMENTS_ALLOW,
...MATH_ELEMENTS_DISALLOW
]);
/* ATTRIBUTES */
const HTML_ATTRIBUTES_ALLOW = [
'abbr',
'accept',
'accept-charset',
'accesskey',
'action',
'align',
'alink',
'allow',
'allowfullscreen',
'alt',
'anchor',
'archive',
'as',
'async',
'autocapitalize',
'autocomplete',
'autocorrect',
'autofocus',
'autopictureinpicture',
'autoplay',
'axis',
'background',
'behavior',
'bgcolor',
'border',
'bordercolor',
'capture',
'cellpadding',
'cellspacing',
'challenge',
'char',
'charoff',
'charset',
'checked',
'cite',
'class',
'classid',
'clear',
'code',
'codebase',
'codetype',
'color',
'cols',
'colspan',
'compact',
'content',
'contenteditable',
'controls',
'controlslist',
'conversiondestination',
'coords',
'crossorigin',
'csp',
'data',
'datetime',
'declare',
'decoding',
'default',
'defer',
'dir',
'direction',
'dirname',
'disabled',
'disablepictureinpicture',
'disableremoteplayback',
'disallowdocumentaccess',
'download',
'draggable',
'elementtiming',
'enctype',
'end',
'enterkeyhint',
'event',
'exportparts',
'face',
'for',
'form',
'formaction',
'formenctype',
'formmethod',
'formnovalidate',
'formtarget',
'frame',
'frameborder',
'headers',
'height',
'hidden',
'high',
'href',
'hreflang',
'hreftranslate',
'hspace',
'http-equiv',
'id',
'imagesizes',
'imagesrcset',
'importance',
'impressiondata',
'impressionexpiry',
'incremental',
'inert',
'inputmode',
'integrity',
'invisible',
'ismap',
'keytype',
'kind',
'label',
'lang',
'language',
'latencyhint',
'leftmargin',
'link',
'list',
'loading',
'longdesc',
'loop',
'low',
'lowsrc',
'manifest',
'marginheight',
'marginwidth',
'max',
'maxlength',
'mayscript',
'media',
'method',
'min',
'minlength',
'multiple',
'muted',
'name',
'nohref',
'nomodule',
'nonce',
'noresize',
'noshade',
'novalidate',
'nowrap',
'object',
'open',
'optimum',
'part',
'pattern',
'ping',
'placeholder',
'playsinline',
'policy',
'poster',
'preload',
'pseudo',
'readonly',
'referrerpolicy',
'rel',
'reportingorigin',
'required',
'resources',
'rev',
'reversed',
'role',
'rows',
'rowspan',
'rules',
'sandbox',
'scheme',
'scope',
'scopes',
'scrollamount',
'scrolldelay',
'scrolling',
'select',
'selected',
'shadowroot',
'shadowrootdelegatesfocus',
'shape',
'size',
'sizes',
'slot',
'span',
'spellcheck',
'src',
'srclang',
'srcset',
'standby',
'start',
'step',
'style',
'summary',
'tabindex',
'target',
'text',
'title',
'topmargin',
'translate',
'truespeed',
'trusttoken',
'type',
'usemap',
'valign',
'value',
'valuetype',
'version',
'virtualkeyboardpolicy',
'vlink',
'vspace',
'webkitdirectory',
'width',
'wrap'
];
const HTML_ATTRIBUTES_DISALLOW = [
'allowpaymentrequest',
'is'
];
const HTML_ATTRIBUTES = new Set ([
...HTML_ATTRIBUTES_ALLOW,
...HTML_ATTRIBUTES_DISALLOW
]);
const SVG_ATTRIBUTES_ALLOW = [
'accent-height',
'accumulate',
'additive',
'alignment-baseline',
'ascent',
'attributename',
'attributetype',
'azimuth',
'basefrequency',
'baseline-shift',
'begin',
'bias',
'by',
'class',
'clip',
'clippathunits',
'clip-path',
'clip-rule',
'color',
'color-interpolation',
'color-interpolation-filters',
'color-profile',
'color-rendering',
'cx',
'cy',
'd',
'dx',
'dy',
'diffuseconstant',
'direction',
'display',
'divisor',
'dominant-baseline',
'dur',
'edgemode',
'elevation',
'end',
'fill',
'fill-opacity',
'fill-rule',
'filter',
'filterunits',
'flood-color',
'flood-opacity',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'fx',
'fy',
'g1',
'g2',
'glyph-name',
'glyphref',
'gradientunits',
'gradienttransform',
'height',
'href',
'id',
'image-rendering',
'in',
'in2',
'k',
'k1',
'k2',
'k3',
'k4',
'kerning',
'keypoints',
'keysplines',
'keytimes',
'lang',
'lengthadjust',
'letter-spacing',
'kernelmatrix',
'kernelunitlength',
'lighting-color',
'local',
'marker-end',
'marker-mid',
'marker-start',
'markerheight',
'markerunits',
'markerwidth',
'maskcontentunits',
'maskunits',
'max',
'mask',
'media',
'method',
'mode',
'min',
'name',
'numoctaves',
'offset',
'operator',
'opacity',
'order',
'orient',
'orientation',
'origin',
'overflow',
'paint-order',
'path',
'pathlength',
'patterncontentunits',
'patterntransform',
'patternunits',
'points',
'preservealpha',
'preserveaspectratio',
'primitiveunits',
'r',
'rx',
'ry',
'radius',
'refx',
'refy',
'repeatcount',
'repeatdur',
'restart',
'result',
'rotate',
'scale',
'seed',
'shape-rendering',
'specularconstant',
'specularexponent',
'spreadmethod',
'startoffset',
'stddeviation',
'stitchtiles',
'stop-color',
'stop-opacity',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke',
'stroke-width',
'style',
'surfacescale',
'systemlanguage',
'tabindex',
'targetx',
'targety',
'transform',
'transform-origin',
'text-anchor',
'text-decoration',
'text-rendering',
'textlength',
'type',
'u1',
'u2',
'unicode',
'values',
'viewbox',
'visibility',
'version',
'vert-adv-y',
'vert-origin-x',
'vert-origin-y',
'width',
'word-spacing',
'wrap',
'writing-mode',
'xchannelselector',
'ychannelselector',
'x',
'x1',
'x2',
'xmlns',
'y',
'y1',
'y2',
'z',
'zoomandpan'
];
const SVG_ATTRIBUTES_DISALLOW: string[] = [
];
const SVG_ATTRIBUTES = new Set ([
...SVG_ATTRIBUTES_ALLOW,
...SVG_ATTRIBUTES_DISALLOW
]);
const MATH_ATTRIBUTES_ALLOW = [
'accent',
'accentunder',
'align',
'bevelled',
'close',
'columnsalign',
'columnlines',
'columnspan',
'denomalign',
'depth',
'dir',
'display',
'displaystyle',
'encoding',
'fence',
'frame',
'height',
'href',
'id',
'largeop',
'length',
'linethickness',
'lspace',
'lquote',
'mathbackground',
'mathcolor',
'mathsize',
'mathvariant',
'maxsize',
'minsize',
'movablelimits',
'notation',
'numalign',
'open',
'rowalign',
'rowlines',
'rowspacing',
'rowspan',
'rspace',
'rquote',
'scriptlevel',
'scriptminsize',
'scriptsizemultiplier',
'selection',
'separator',
'separators',
'stretchy',
'subscriptshift',
'supscriptshift',
'symmetric',
'voffset',
'width',
'xmlns'
];
const MATH_ATTRIBUTES_DISALLOW: string[] = [
];
const MATH_ATTRIBUTES = new Set ([
...MATH_ATTRIBUTES_ALLOW,
...MATH_ATTRIBUTES_DISALLOW
]);
/* NAMESPACES */
const NAMESPACES = {
HTML: 'http://www.w3.org/1999/xhtml',
SVG: 'http://www.w3.org/2000/svg',
MATH: 'http://www.w3.org/1998/Math/MathML'
};
const NAMESPACES_ELEMENTS = {
[NAMESPACES.HTML]: HTML_ELEMENTS,
[NAMESPACES.SVG]: SVG_ELEMENTS,
[NAMESPACES.MATH]: MATH_ELEMENTS
};
const NAMESPACES_ATTRIBUTES = {
[NAMESPACES.HTML]: HTML_ATTRIBUTES,
[NAMESPACES.SVG]: SVG_ATTRIBUTES,
[NAMESPACES.MATH]: MATH_ATTRIBUTES
};
const NAMESPACES_ROOTS = {
[NAMESPACES.HTML]: 'html',
[NAMESPACES.SVG]: 'svg',
[NAMESPACES.MATH]: 'math'
};
const NAMESPACES_PREFIXES = {
[NAMESPACES.HTML]: '',
[NAMESPACES.SVG]: 'svg:',
[NAMESPACES.MATH]: 'math:'
};
/* TAG NAMES */
const FUNKY_TAG_NAMES = new Set ([ // Funky elements need special handling
'A',
'AREA',
'BUTTON',
'FORM',
'IFRAME',
'INPUT'
]);
/* OTHERS */
const DEFAULTS = {
allowComments: true,
allowCustomElements: false,
allowUnknownMarkup: false,
allowElements: [
...HTML_ELEMENTS_ALLOW,
...SVG_ELEMENTS_ALLOW.map ( name => `svg:${name}` ),
...MATH_ELEMENTS_ALLOW.map ( name => `math:${name}` )
],
allowAttributes: mergeMaps ([
Object.fromEntries ( HTML_ATTRIBUTES_ALLOW.map ( name => [name, ['*']] ) ),
Object.fromEntries ( SVG_ATTRIBUTES_ALLOW.map ( name => [name, ['svg:*']] ) ),
Object.fromEntries ( MATH_ATTRIBUTES_ALLOW.map ( name => [name, ['math:*']] ) )
])
};
/* EXPORT */
export {HTML_ELEMENTS, SVG_ELEMENTS, MATH_ELEMENTS};
export {HTML_ATTRIBUTES, SVG_ATTRIBUTES, MATH_ATTRIBUTES};
export {NAMESPACES, NAMESPACES_ELEMENTS, NAMESPACES_ATTRIBUTES, NAMESPACES_ROOTS, NAMESPACES_PREFIXES};
export {FUNKY_TAG_NAMES};
export {DEFAULTS};