UNPKG

@diplodoc/transform

Version:

A simple transformer of text in YFM (Yandex Flavored Markdown) to HTML

593 lines 12.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.sanitize = exports.sanitizeStyles = exports.defaultOptions = exports.defaultParseOptions = void 0; const sanitize_html_1 = __importDefault(require("sanitize-html")); // @ts-ignore const cssfilter_1 = __importDefault(require("cssfilter")); const cheerio = __importStar(require("cheerio")); const css_1 = __importDefault(require("css")); const log_1 = __importDefault(require("./log")); const htmlTags = [ 'a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr', 'iframe', 'style', ]; const svgTags = [ 'svg', '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', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern', 'animate', ]; const htmlAttrs = [ 'accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns', 'slot', 'frameborder', 'scrolling', 'allow', 'target', 'attributeName', 'aria-hidden', 'referrerpolicy', 'aria-describedby', 'data-*', 'wide-content', ]; const svgAttrs = [ 'viewBox', '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', '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', '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', 'from', 'to', 'xlink:href', 'use', ]; const defaultCssWhitelist = Object.assign(Object.assign({}, cssfilter_1.default.whiteList), { '--method': true }); const yfmHtmlAttrs = ['note-type', 'term-key']; const allowedTags = Array.from(new Set([...htmlTags, ...svgTags, ...sanitize_html_1.default.defaults.allowedTags])); const allowedAttributes = Array.from(new Set([...htmlAttrs, ...svgAttrs, ...yfmHtmlAttrs])); exports.defaultParseOptions = { lowerCaseAttributeNames: false, }; exports.defaultOptions = Object.assign(Object.assign({}, sanitize_html_1.default.defaults), { allowedTags, allowedAttributes: Object.assign(Object.assign({}, sanitize_html_1.default.defaults.allowedAttributes), { '*': allowedAttributes }), allowedSchemesAppliedToAttributes: [ ...sanitize_html_1.default.defaults.allowedSchemesAppliedToAttributes, 'xlink:href', 'from', 'to', ], allowVulnerableTags: true, parser: exports.defaultParseOptions, cssWhiteList: defaultCssWhitelist }); function sanitizeStyleTags(dom, cssWhiteList) { const styleTags = dom('style'); styleTags.each((_index, element) => { const styleText = dom(element).text(); try { const parsedCSS = css_1.default.parse(styleText); if (!parsedCSS.stylesheet) { return; } parsedCSS.stylesheet.rules = parsedCSS.stylesheet.rules.filter((rule) => rule.type === 'rule'); parsedCSS.stylesheet.rules.forEach((rule) => { if (!rule.declarations) { return; } rule.declarations = rule.declarations.filter((declaration) => { if (!declaration.property || !declaration.value) { return false; } const isWhiteListed = cssWhiteList[declaration.property]; if (isWhiteListed) { declaration.value = cssfilter_1.default.safeAttrValue(declaration.property, declaration.value); } if (!declaration.value) { return false; } return isWhiteListed; }); }); dom(element).text(css_1.default.stringify(parsedCSS)); } catch (error) { dom(element).remove(); const errorMessage = error instanceof Error ? error.message : `${error}`; log_1.default.info(errorMessage); } }); } function sanitizeStyleAttrs(dom, cssWhiteList) { const options = { whiteList: cssWhiteList, }; const cssSanitizer = new cssfilter_1.default.FilterCSS(options); dom('*').each((_index, element) => { const styleAttrValue = dom(element).attr('style'); if (!styleAttrValue) { return; } dom(element).attr('style', cssSanitizer.process(styleAttrValue)); }); } function sanitizeStyles(html, options) { const cssWhiteList = options.cssWhiteList || {}; const $ = cheerio.load(html); sanitizeStyleTags($, cssWhiteList); sanitizeStyleAttrs($, cssWhiteList); const styles = $('head').html() || ''; const content = $('body').html() || ''; return styles + content; } exports.sanitizeStyles = sanitizeStyles; function sanitize(html, options, additionalOptions) { var _a; const sanitizeOptions = options || exports.defaultOptions; if (additionalOptions === null || additionalOptions === void 0 ? void 0 : additionalOptions.cssWhiteList) { sanitizeOptions.cssWhiteList = Object.assign(Object.assign({}, sanitizeOptions.cssWhiteList), additionalOptions.cssWhiteList); } const needToSanitizeStyles = !((_a = sanitizeOptions.disableStyleSanitizer) !== null && _a !== void 0 ? _a : false); const modifiedHtml = needToSanitizeStyles ? sanitizeStyles(html, sanitizeOptions) : html; return (0, sanitize_html_1.default)(modifiedHtml, sanitizeOptions); } exports.sanitize = sanitize; exports.default = sanitize; //# sourceMappingURL=sanitize.js.map