dompurify 3.4.9 → 3.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- /*! @license DOMPurify 3.4.9 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.9/LICENSE */
1
+ /*! @license DOMPurify 3.4.10 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.10/LICENSE */
2
2
 
3
3
  'use strict';
4
4
 
@@ -329,6 +329,13 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
329
329
  );
330
330
  const DOCTYPE_NAME = seal(/^html$/i);
331
331
  const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
332
+ // Markup-significant character probes used by _sanitizeElements.
333
+ // Shared module-level instances are safe despite the sticky /g flags:
334
+ // unapply() resets lastIndex for RegExp receivers before every call.
335
+ const ELEMENT_MARKUP_PROBE = seal(/<[/\w!]/g);
336
+ const COMMENT_MARKUP_PROBE = seal(/<[/\w]/g);
337
+ const FALLBACK_TAG_CLOSE = seal(/<\/no(script|embed|frames)/i);
338
+ const SELF_CLOSING_TAG = seal(/\/>/i);
332
339
 
333
340
  /* eslint-disable @typescript-eslint/indent */
334
341
  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
@@ -341,7 +348,7 @@ const NODE_TYPE = {
341
348
  // Deprecated
342
349
  entityNode: 6,
343
350
  // Deprecated
344
- progressingInstruction: 7,
351
+ processingInstruction: 7,
345
352
  comment: 8,
346
353
  document: 9,
347
354
  documentType: 10,
@@ -402,10 +409,25 @@ const _createHooksMap = function _createHooksMap() {
402
409
  uponSanitizeShadowNode: []
403
410
  };
404
411
  };
412
+ /**
413
+ * Resolve a set-valued configuration option: a fresh set built from
414
+ * cfg[key] when it is an own array property (seeded with a clone of
415
+ * options.base when given, case-normalized via options.transform),
416
+ * the fallback set otherwise.
417
+ *
418
+ * @param cfg the cloned, prototype-free configuration object
419
+ * @param key the configuration property to read
420
+ * @param fallback the set to use when the option is absent or not an array
421
+ * @param options transform and optional base set to merge into
422
+ * @returns the resolved set
423
+ */
424
+ const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
425
+ return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
426
+ };
405
427
  function createDOMPurify() {
406
428
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
407
429
  const DOMPurify = root => createDOMPurify(root);
408
- DOMPurify.version = '3.4.9';
430
+ DOMPurify.version = '3.4.10';
409
431
  DOMPurify.removed = [];
410
432
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
411
433
  // Not running in a browser, provide a factory function
@@ -662,8 +684,10 @@ function createDOMPurify() {
662
684
  /* Allowed XHTML+XML namespaces */
663
685
  let ALLOWED_NAMESPACES = null;
664
686
  const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
665
- let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
666
- let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
687
+ const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze(['mi', 'mo', 'mn', 'ms', 'mtext']);
688
+ let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
689
+ const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);
690
+ let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
667
691
  // Certain elements are allowed in both SVG and HTML
668
692
  // namespace. We need to specify them explicitly
669
693
  // so that they don't get erroneously deleted from
@@ -705,14 +729,32 @@ function createDOMPurify() {
705
729
  // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
706
730
  transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
707
731
  /* Set configuration parameters */
708
- ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') && arrayIsArray(cfg.ALLOWED_TAGS) ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
709
- ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') && arrayIsArray(cfg.ALLOWED_ATTR) ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
710
- ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') && arrayIsArray(cfg.ALLOWED_NAMESPACES) ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
711
- URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR) ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
712
- DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') && arrayIsArray(cfg.ADD_DATA_URI_TAGS) ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
713
- FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS) ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
714
- FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') && arrayIsArray(cfg.FORBID_TAGS) ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
715
- FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') && arrayIsArray(cfg.FORBID_ATTR) ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
732
+ ALLOWED_TAGS = _resolveSetOption(cfg, 'ALLOWED_TAGS', DEFAULT_ALLOWED_TAGS, {
733
+ transform: transformCaseFunc
734
+ });
735
+ ALLOWED_ATTR = _resolveSetOption(cfg, 'ALLOWED_ATTR', DEFAULT_ALLOWED_ATTR, {
736
+ transform: transformCaseFunc
737
+ });
738
+ ALLOWED_NAMESPACES = _resolveSetOption(cfg, 'ALLOWED_NAMESPACES', DEFAULT_ALLOWED_NAMESPACES, {
739
+ transform: stringToString
740
+ });
741
+ URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, 'ADD_URI_SAFE_ATTR', DEFAULT_URI_SAFE_ATTRIBUTES, {
742
+ transform: transformCaseFunc,
743
+ base: DEFAULT_URI_SAFE_ATTRIBUTES
744
+ });
745
+ DATA_URI_TAGS = _resolveSetOption(cfg, 'ADD_DATA_URI_TAGS', DEFAULT_DATA_URI_TAGS, {
746
+ transform: transformCaseFunc,
747
+ base: DEFAULT_DATA_URI_TAGS
748
+ });
749
+ FORBID_CONTENTS = _resolveSetOption(cfg, 'FORBID_CONTENTS', DEFAULT_FORBID_CONTENTS, {
750
+ transform: transformCaseFunc
751
+ });
752
+ FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {
753
+ transform: transformCaseFunc
754
+ });
755
+ FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {
756
+ transform: transformCaseFunc
757
+ });
716
758
  USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
717
759
  ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
718
760
  ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
@@ -731,8 +773,8 @@ function createDOMPurify() {
731
773
  IN_PLACE = cfg.IN_PLACE || false; // Default false
732
774
  IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
733
775
  NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
734
- MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); // Default built-in map
735
- HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, ['annotation-xml']); // Default built-in map
776
+ MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS); // Default built-in map
777
+ HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS); // Default built-in map
736
778
  const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
737
779
  CUSTOM_ELEMENT_HANDLING = create(null);
738
780
  if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
@@ -744,6 +786,7 @@ function createDOMPurify() {
744
786
  if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
745
787
  CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
746
788
  }
789
+ seal(CUSTOM_ELEMENT_HANDLING);
747
790
  if (SAFE_FOR_TEMPLATES) {
748
791
  ALLOW_DATA_ATTR = false;
749
792
  }
@@ -909,6 +952,77 @@ function createDOMPurify() {
909
952
  * correctly. */
910
953
  const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
911
954
  const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
955
+ /**
956
+ * Namespace rules for an element in the SVG namespace.
957
+ *
958
+ * @param tagName the element's lowercase tag name
959
+ * @param parent the (possibly simulated) parent node
960
+ * @param parentTagName the parent's lowercase tag name
961
+ * @returns true if a spec-compliant parser could produce this element
962
+ */
963
+ const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) {
964
+ // The only way to switch from HTML namespace to SVG
965
+ // is via <svg>. If it happens via any other tag, then
966
+ // it should be killed.
967
+ if (parent.namespaceURI === HTML_NAMESPACE) {
968
+ return tagName === 'svg';
969
+ }
970
+ // The only way to switch from MathML to SVG is via <svg>
971
+ // if the parent is either <annotation-xml> or a MathML
972
+ // text integration point.
973
+ if (parent.namespaceURI === MATHML_NAMESPACE) {
974
+ return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
975
+ }
976
+ // We only allow elements that are defined in SVG
977
+ // spec. All others are disallowed in SVG namespace.
978
+ return Boolean(ALL_SVG_TAGS[tagName]);
979
+ };
980
+ /**
981
+ * Namespace rules for an element in the MathML namespace.
982
+ *
983
+ * @param tagName the element's lowercase tag name
984
+ * @param parent the (possibly simulated) parent node
985
+ * @param parentTagName the parent's lowercase tag name
986
+ * @returns true if a spec-compliant parser could produce this element
987
+ */
988
+ const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) {
989
+ // The only way to switch from HTML namespace to MathML
990
+ // is via <math>. If it happens via any other tag, then
991
+ // it should be killed.
992
+ if (parent.namespaceURI === HTML_NAMESPACE) {
993
+ return tagName === 'math';
994
+ }
995
+ // The only way to switch from SVG to MathML is via
996
+ // <math> and HTML integration points
997
+ if (parent.namespaceURI === SVG_NAMESPACE) {
998
+ return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
999
+ }
1000
+ // We only allow elements that are defined in MathML
1001
+ // spec. All others are disallowed in MathML namespace.
1002
+ return Boolean(ALL_MATHML_TAGS[tagName]);
1003
+ };
1004
+ /**
1005
+ * Namespace rules for an element in the HTML namespace.
1006
+ *
1007
+ * @param tagName the element's lowercase tag name
1008
+ * @param parent the (possibly simulated) parent node
1009
+ * @param parentTagName the parent's lowercase tag name
1010
+ * @returns true if a spec-compliant parser could produce this element
1011
+ */
1012
+ const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) {
1013
+ // The only way to switch from SVG to HTML is via
1014
+ // HTML integration points, and from MathML to HTML
1015
+ // is via MathML text integration points
1016
+ if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
1017
+ return false;
1018
+ }
1019
+ if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
1020
+ return false;
1021
+ }
1022
+ // We disallow tags that are specific for MathML
1023
+ // or SVG and should never appear in HTML namespace
1024
+ return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
1025
+ };
912
1026
  /**
913
1027
  * @param element a DOM element whose namespace is being checked
914
1028
  * @returns Return false if the element has a
@@ -931,51 +1045,13 @@ function createDOMPurify() {
931
1045
  return false;
932
1046
  }
933
1047
  if (element.namespaceURI === SVG_NAMESPACE) {
934
- // The only way to switch from HTML namespace to SVG
935
- // is via <svg>. If it happens via any other tag, then
936
- // it should be killed.
937
- if (parent.namespaceURI === HTML_NAMESPACE) {
938
- return tagName === 'svg';
939
- }
940
- // The only way to switch from MathML to SVG is via`
941
- // svg if parent is either <annotation-xml> or MathML
942
- // text integration points.
943
- if (parent.namespaceURI === MATHML_NAMESPACE) {
944
- return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
945
- }
946
- // We only allow elements that are defined in SVG
947
- // spec. All others are disallowed in SVG namespace.
948
- return Boolean(ALL_SVG_TAGS[tagName]);
1048
+ return _checkSvgNamespace(tagName, parent, parentTagName);
949
1049
  }
950
1050
  if (element.namespaceURI === MATHML_NAMESPACE) {
951
- // The only way to switch from HTML namespace to MathML
952
- // is via <math>. If it happens via any other tag, then
953
- // it should be killed.
954
- if (parent.namespaceURI === HTML_NAMESPACE) {
955
- return tagName === 'math';
956
- }
957
- // The only way to switch from SVG to MathML is via
958
- // <math> and HTML integration points
959
- if (parent.namespaceURI === SVG_NAMESPACE) {
960
- return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
961
- }
962
- // We only allow elements that are defined in MathML
963
- // spec. All others are disallowed in MathML namespace.
964
- return Boolean(ALL_MATHML_TAGS[tagName]);
1051
+ return _checkMathMlNamespace(tagName, parent, parentTagName);
965
1052
  }
966
1053
  if (element.namespaceURI === HTML_NAMESPACE) {
967
- // The only way to switch from SVG to HTML is via
968
- // HTML integration points, and from MathML to HTML
969
- // is via MathML text integration points
970
- if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
971
- return false;
972
- }
973
- if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
974
- return false;
975
- }
976
- // We disallow tags that are specific for MathML
977
- // or SVG and should never appear in HTML namespace
978
- return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
1054
+ return _checkHtmlNamespace(tagName, parent, parentTagName);
979
1055
  }
980
1056
  // For XHTML and XML documents that support custom namespaces
981
1057
  if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
@@ -1041,7 +1117,7 @@ function createDOMPurify() {
1041
1117
  * @param root the in-place root to empty
1042
1118
  */
1043
1119
  const _neutralizeRoot = function _neutralizeRoot(root) {
1044
- const childNodes = getChildNodes ? getChildNodes(root) : root.childNodes;
1120
+ const childNodes = getChildNodes(root);
1045
1121
  if (childNodes) {
1046
1122
  const snapshot = [];
1047
1123
  arrayForEach(childNodes, child => {
@@ -1055,7 +1131,7 @@ function createDOMPurify() {
1055
1131
  }
1056
1132
  });
1057
1133
  }
1058
- const attributes = getAttributes ? getAttributes(root) : null;
1134
+ const attributes = getAttributes(root);
1059
1135
  if (attributes) {
1060
1136
  for (let i = attributes.length - 1; i >= 0; --i) {
1061
1137
  const attribute = attributes[i];
@@ -1113,7 +1189,7 @@ function createDOMPurify() {
1113
1189
  * @param element the element to strip
1114
1190
  */
1115
1191
  const _stripDisallowedAttributes = function _stripDisallowedAttributes(element) {
1116
- const attributes = getAttributes ? getAttributes(element) : element.attributes;
1192
+ const attributes = getAttributes(element);
1117
1193
  if (!attributes) {
1118
1194
  return;
1119
1195
  }
@@ -1160,7 +1236,7 @@ function createDOMPurify() {
1160
1236
  if (nodeType === NODE_TYPE.element) {
1161
1237
  _stripDisallowedAttributes(node);
1162
1238
  }
1163
- const childNodes = getChildNodes ? getChildNodes(node) : node.childNodes;
1239
+ const childNodes = getChildNodes(node);
1164
1240
  if (childNodes) {
1165
1241
  for (let i = childNodes.length - 1; i >= 0; --i) {
1166
1242
  stack.push(childNodes[i]);
@@ -1229,6 +1305,20 @@ function createDOMPurify() {
1229
1305
  // eslint-disable-next-line no-bitwise
1230
1306
  NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
1231
1307
  };
1308
+ /**
1309
+ * Replace template expression syntax (mustache, ERB, template
1310
+ * literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
1311
+ * sites. Order matters: mustache, then ERB, then template literal.
1312
+ *
1313
+ * @param value the string to scrub
1314
+ * @returns the scrubbed string
1315
+ */
1316
+ const _stripTemplateExpressions = function _stripTemplateExpressions(value) {
1317
+ value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
1318
+ value = stringReplace(value, ERB_EXPR$1, ' ');
1319
+ value = stringReplace(value, TMPLIT_EXPR$1, ' ');
1320
+ return value;
1321
+ };
1232
1322
  /**
1233
1323
  * Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
1234
1324
  * character data of an element subtree. Used as the final safety net for
@@ -1249,29 +1339,27 @@ function createDOMPurify() {
1249
1339
  * @param node The root element whose character data should be scrubbed.
1250
1340
  */
1251
1341
  const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
1252
- var _node$querySelectorAl, _node$querySelectorAl2;
1342
+ var _node$querySelectorAl;
1253
1343
  node.normalize();
1254
1344
  const walker = createNodeIterator.call(node.ownerDocument || node, node,
1255
1345
  // eslint-disable-next-line no-bitwise
1256
1346
  NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
1257
1347
  let currentNode = walker.nextNode();
1258
1348
  while (currentNode) {
1259
- let data = currentNode.data;
1260
- arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
1261
- data = stringReplace(data, expr, ' ');
1262
- });
1263
- currentNode.data = data;
1349
+ currentNode.data = _stripTemplateExpressions(currentNode.data);
1264
1350
  currentNode = walker.nextNode();
1265
1351
  }
1266
1352
  // NodeIterator does not descend into <template>.content per the DOM spec,
1267
1353
  // so we must explicitly recurse into each template's content fragment,
1268
1354
  // mirroring the approach used by _sanitizeShadowDOM.
1269
- const templates = (_node$querySelectorAl = (_node$querySelectorAl2 = node.querySelectorAll) === null || _node$querySelectorAl2 === void 0 ? void 0 : _node$querySelectorAl2.call(node, 'template')) !== null && _node$querySelectorAl !== void 0 ? _node$querySelectorAl : [];
1270
- arrayForEach(Array.from(templates), tmpl => {
1271
- if (_isDocumentFragment(tmpl.content)) {
1272
- _scrubTemplateExpressions2(tmpl.content);
1273
- }
1274
- });
1355
+ const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, 'template');
1356
+ if (templates) {
1357
+ arrayForEach(templates, tmpl => {
1358
+ if (_isDocumentFragment(tmpl.content)) {
1359
+ _scrubTemplateExpressions2(tmpl.content);
1360
+ }
1361
+ });
1362
+ }
1275
1363
  };
1276
1364
  /**
1277
1365
  * _isClobbered
@@ -1367,67 +1455,64 @@ function createDOMPurify() {
1367
1455
  }
1368
1456
  };
1369
1457
  function _executeHooks(hooks, currentNode, data) {
1458
+ if (hooks.length === 0) {
1459
+ return;
1460
+ }
1370
1461
  arrayForEach(hooks, hook => {
1371
1462
  hook.call(DOMPurify, currentNode, data, CONFIG);
1372
1463
  });
1373
1464
  }
1374
1465
  /**
1375
- * _sanitizeElements
1466
+ * Structural-threat checks that condemn a node regardless of the
1467
+ * allowlists: mXSS via namespace confusion, risky CSS construction,
1468
+ * processing instructions, markup-bearing comments. Pure predicate;
1469
+ * the caller removes. Check order is load-bearing.
1376
1470
  *
1377
- * @protect nodeName
1378
- * @protect textContent
1379
- * @protect removeChild
1380
- * @param currentNode to check for permission to exist
1381
- * @return true if node was killed, false if left alive
1471
+ * @param currentNode the node to inspect
1472
+ * @param tagName the node's transformCaseFunc'd tag name
1473
+ * @return true if the node must be removed
1382
1474
  */
1383
- const _sanitizeElements = function _sanitizeElements(currentNode) {
1384
- let content = null;
1385
- /* Execute a hook if present */
1386
- _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
1387
- /* Check if element is clobbered or can clobber */
1388
- if (_isClobbered(currentNode)) {
1389
- _forceRemove(currentNode);
1390
- return true;
1391
- }
1392
- /* Now let's check the element's type and name */
1393
- const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
1394
- /* Execute a hook if present */
1395
- _executeHooks(hooks.uponSanitizeElement, currentNode, {
1396
- tagName,
1397
- allowedTags: ALLOWED_TAGS
1398
- });
1475
+ const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
1399
1476
  /* Detect mXSS attempts abusing namespace confusion */
1400
- if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
1401
- _forceRemove(currentNode);
1477
+ if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) {
1402
1478
  return true;
1403
1479
  }
1404
1480
  /* Remove risky CSS construction leading to mXSS */
1405
1481
  if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
1406
- _forceRemove(currentNode);
1407
1482
  return true;
1408
1483
  }
1409
1484
  /* Remove any occurrence of processing instructions */
1410
- if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
1411
- _forceRemove(currentNode);
1485
+ if (currentNode.nodeType === NODE_TYPE.processingInstruction) {
1412
1486
  return true;
1413
1487
  }
1414
1488
  /* Remove any kind of possibly harmful comments */
1415
- if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
1416
- _forceRemove(currentNode);
1489
+ if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) {
1417
1490
  return true;
1418
1491
  }
1419
- /* Remove element if anything forbids its presence */
1420
- if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
1421
- /* Check if we have a custom element to handle */
1422
- if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
1423
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
1424
- return false;
1425
- }
1426
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
1427
- return false;
1428
- }
1492
+ return false;
1493
+ };
1494
+ /**
1495
+ * Handle a node whose tag is forbidden or not allowlisted: keep
1496
+ * allowed custom elements (false return exits _sanitizeElements
1497
+ * early - namespace/fallback checks and the afterSanitizeElements
1498
+ * hook are intentionally skipped for kept custom elements), else
1499
+ * hoist content per KEEP_CONTENT and remove.
1500
+ *
1501
+ * @param currentNode the disallowed node
1502
+ * @param tagName the node's transformCaseFunc'd tag name
1503
+ * @return true if the node was removed, false if kept
1504
+ */
1505
+ const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
1506
+ /* Check if we have a custom element to handle */
1507
+ if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
1508
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
1509
+ return false;
1510
+ }
1511
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
1512
+ return false;
1429
1513
  }
1430
- /* Keep content except for bad-listed elements.
1514
+ }
1515
+ /* Keep content except for bad-listed elements.
1431
1516
  Use the cached prototype getters exclusively — the previous code
1432
1517
  had `|| currentNode.parentNode` / `|| currentNode.childNodes`
1433
1518
  fallbacks, but the cached getters always return the canonical
@@ -1435,12 +1520,12 @@ function createDOMPurify() {
1435
1520
  path was dead in safe cases and a clobbering surface in unsafe
1436
1521
  ones. Falsy cached results stay falsy; the `if (childNodes &&
1437
1522
  parentNode)` check already gates correctly. */
1438
- if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
1439
- const parentNode = getParentNode(currentNode);
1440
- const childNodes = getChildNodes(currentNode);
1441
- if (childNodes && parentNode) {
1442
- const childCount = childNodes.length;
1443
- /* In-place: hoist the *original* children so the iterator visits
1523
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
1524
+ const parentNode = getParentNode(currentNode);
1525
+ const childNodes = getChildNodes(currentNode);
1526
+ if (childNodes && parentNode) {
1527
+ const childCount = childNodes.length;
1528
+ /* In-place: hoist the *original* children so the iterator visits
1444
1529
  and sanitises them through the same allowlist pass as every other
1445
1530
  node. The caller built the tree in the live document, so the
1446
1531
  originals carry already-queued resource events (`<img onerror>`,
@@ -1450,24 +1535,57 @@ function createDOMPurify() {
1450
1535
  root is pre-validated as an allowed tag and so is never the node
1451
1536
  being removed, which keeps `parentNode` inside the iterator root
1452
1537
  and the relocated child inside the serialised tree.
1453
- Otherwise (string / DOM-copy paths): clone. The iterator is rooted
1538
+ Otherwise (string / DOM-copy paths): clone. The iterator is rooted
1454
1539
  at — and the result serialised from — `body`, so a restrictive
1455
1540
  ALLOWED_TAGS that removes `body` itself must leave its content in
1456
1541
  place, which only cloning does; and those paths parse into an
1457
1542
  inert document, so their discarded originals never had a queued
1458
1543
  event to neutralise.
1459
- `childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
1544
+ `childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
1460
1545
  valid whether we move (drops the trailing entry) or clone (leaves
1461
1546
  the list intact). */
1462
- for (let i = childCount - 1; i >= 0; --i) {
1463
- const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
1464
- parentNode.insertBefore(hoisted, getNextSibling(currentNode));
1465
- }
1547
+ for (let i = childCount - 1; i >= 0; --i) {
1548
+ const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
1549
+ parentNode.insertBefore(hoisted, getNextSibling(currentNode));
1466
1550
  }
1467
1551
  }
1552
+ }
1553
+ _forceRemove(currentNode);
1554
+ return true;
1555
+ };
1556
+ /**
1557
+ * _sanitizeElements
1558
+ *
1559
+ * @protect nodeName
1560
+ * @protect textContent
1561
+ * @protect removeChild
1562
+ * @param currentNode to check for permission to exist
1563
+ * @return true if node was killed, false if left alive
1564
+ */
1565
+ const _sanitizeElements = function _sanitizeElements(currentNode) {
1566
+ /* Execute a hook if present */
1567
+ _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
1568
+ /* Check if element is clobbered or can clobber */
1569
+ if (_isClobbered(currentNode)) {
1468
1570
  _forceRemove(currentNode);
1469
1571
  return true;
1470
1572
  }
1573
+ /* Now let's check the element's type and name */
1574
+ const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
1575
+ /* Execute a hook if present */
1576
+ _executeHooks(hooks.uponSanitizeElement, currentNode, {
1577
+ tagName,
1578
+ allowedTags: ALLOWED_TAGS
1579
+ });
1580
+ /* Remove mXSS vectors, processing instructions and risky comments */
1581
+ if (_isUnsafeNode(currentNode, tagName)) {
1582
+ _forceRemove(currentNode);
1583
+ return true;
1584
+ }
1585
+ /* Remove element if anything forbids its presence */
1586
+ if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
1587
+ return _sanitizeDisallowedNode(currentNode, tagName);
1588
+ }
1471
1589
  /* Check whether element has a valid namespace.
1472
1590
  Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
1473
1591
  nodeType getter rather than `instanceof Element`, which is realm-
@@ -1480,17 +1598,14 @@ function createDOMPurify() {
1480
1598
  return true;
1481
1599
  }
1482
1600
  /* Make sure that older browsers don't get fallback-tag mXSS */
1483
- if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
1601
+ if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(FALLBACK_TAG_CLOSE, currentNode.innerHTML)) {
1484
1602
  _forceRemove(currentNode);
1485
1603
  return true;
1486
1604
  }
1487
1605
  /* Sanitize element content to be template-safe */
1488
1606
  if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
1489
1607
  /* Get the element's text content */
1490
- content = currentNode.textContent;
1491
- arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
1492
- content = stringReplace(content, expr, ' ');
1493
- });
1608
+ const content = _stripTemplateExpressions(currentNode.textContent);
1494
1609
  if (currentNode.textContent !== content) {
1495
1610
  arrayPush(DOMPurify.removed, {
1496
1611
  element: currentNode.cloneNode()
@@ -1525,7 +1640,7 @@ function createDOMPurify() {
1525
1640
  (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
1526
1641
  XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
1527
1642
  We don't need to check the value; it's always URI safe. */
1528
- if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted || FORBID_ATTR[lcName]) {
1643
+ if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted) {
1529
1644
  if (
1530
1645
  // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1531
1646
  // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
@@ -1557,6 +1672,63 @@ function createDOMPurify() {
1557
1672
  const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
1558
1673
  return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
1559
1674
  };
1675
+ /**
1676
+ * Wrap an attribute value in the matching Trusted Types object when
1677
+ * the active policy requires it. Namespaced attributes pass through
1678
+ * unchanged (no TT support yet, see
1679
+ * https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
1680
+ *
1681
+ * @param lcTag lowercase tag name of the containing element
1682
+ * @param lcName lowercase attribute name
1683
+ * @param namespaceURI the attribute's namespace, if any
1684
+ * @param value the attribute value to wrap
1685
+ * @return the value, wrapped when Trusted Types demand it
1686
+ */
1687
+ const _applyTrustedTypesToAttribute = function _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value) {
1688
+ if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function' && !namespaceURI) {
1689
+ switch (trustedTypes.getAttributeType(lcTag, lcName)) {
1690
+ case 'TrustedHTML':
1691
+ {
1692
+ return _createTrustedHTML(value);
1693
+ }
1694
+ case 'TrustedScriptURL':
1695
+ {
1696
+ return _createTrustedScriptURL(value);
1697
+ }
1698
+ }
1699
+ }
1700
+ return value;
1701
+ };
1702
+ /**
1703
+ * Write a modified attribute value back onto the element. On
1704
+ * success, re-probe for clobbering introduced by the new value and
1705
+ * remove the element when found; otherwise pop the removal entry
1706
+ * recorded by the earlier _removeAttribute (long-standing pairing
1707
+ * with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
1708
+ * failure, remove the attribute instead.
1709
+ *
1710
+ * @param currentNode the element carrying the attribute
1711
+ * @param name the attribute name as present on the element
1712
+ * @param namespaceURI the attribute's namespace, if any
1713
+ * @param value the new attribute value
1714
+ */
1715
+ const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
1716
+ try {
1717
+ if (namespaceURI) {
1718
+ currentNode.setAttributeNS(namespaceURI, name, value);
1719
+ } else {
1720
+ /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1721
+ currentNode.setAttribute(name, value);
1722
+ }
1723
+ if (_isClobbered(currentNode)) {
1724
+ _forceRemove(currentNode);
1725
+ } else {
1726
+ arrayPop(DOMPurify.removed);
1727
+ }
1728
+ } catch (_) {
1729
+ _removeAttribute(name, currentNode);
1730
+ }
1731
+ };
1560
1732
  /**
1561
1733
  * _sanitizeAttributes
1562
1734
  *
@@ -1583,6 +1755,7 @@ function createDOMPurify() {
1583
1755
  forceKeepAttr: undefined
1584
1756
  };
1585
1757
  let l = attributes.length;
1758
+ const lcTag = transformCaseFunc(currentNode.nodeName);
1586
1759
  /* Go backwards over all attributes; safely remove bad ones */
1587
1760
  while (l--) {
1588
1761
  const attr = attributes[l];
@@ -1620,7 +1793,7 @@ function createDOMPurify() {
1620
1793
  _removeAttribute(name, currentNode);
1621
1794
  continue;
1622
1795
  }
1623
- /* Did the hooks approve of the attribute? */
1796
+ /* Did the hooks force-keep the attribute? */
1624
1797
  if (hookEvent.forceKeepAttr) {
1625
1798
  continue;
1626
1799
  }
@@ -1630,56 +1803,24 @@ function createDOMPurify() {
1630
1803
  continue;
1631
1804
  }
1632
1805
  /* Work around a security issue in jQuery 3.0 */
1633
- if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
1806
+ if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
1634
1807
  _removeAttribute(name, currentNode);
1635
1808
  continue;
1636
1809
  }
1637
1810
  /* Sanitize attribute content to be template-safe */
1638
1811
  if (SAFE_FOR_TEMPLATES) {
1639
- arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
1640
- value = stringReplace(value, expr, ' ');
1641
- });
1812
+ value = _stripTemplateExpressions(value);
1642
1813
  }
1643
1814
  /* Is `value` valid for this attribute? */
1644
- const lcTag = transformCaseFunc(currentNode.nodeName);
1645
1815
  if (!_isValidAttribute(lcTag, lcName, value)) {
1646
1816
  _removeAttribute(name, currentNode);
1647
1817
  continue;
1648
1818
  }
1649
1819
  /* Handle attributes that require Trusted Types */
1650
- if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
1651
- if (namespaceURI) ; else {
1652
- switch (trustedTypes.getAttributeType(lcTag, lcName)) {
1653
- case 'TrustedHTML':
1654
- {
1655
- value = _createTrustedHTML(value);
1656
- break;
1657
- }
1658
- case 'TrustedScriptURL':
1659
- {
1660
- value = _createTrustedScriptURL(value);
1661
- break;
1662
- }
1663
- }
1664
- }
1665
- }
1820
+ value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
1666
1821
  /* Handle invalid data-* attribute set by try-catching it */
1667
1822
  if (value !== initValue) {
1668
- try {
1669
- if (namespaceURI) {
1670
- currentNode.setAttributeNS(namespaceURI, name, value);
1671
- } else {
1672
- /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1673
- currentNode.setAttribute(name, value);
1674
- }
1675
- if (_isClobbered(currentNode)) {
1676
- _forceRemove(currentNode);
1677
- } else {
1678
- arrayPop(DOMPurify.removed);
1679
- }
1680
- } catch (_) {
1681
- _removeAttribute(name, currentNode);
1682
- }
1823
+ _setAttributeValue(currentNode, name, namespaceURI, value);
1683
1824
  }
1684
1825
  }
1685
1826
  /* Execute a hook if present */
@@ -1721,7 +1862,7 @@ function createDOMPurify() {
1721
1862
  iterator also surfaces. */
1722
1863
  const shadowNodeType = getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType;
1723
1864
  if (shadowNodeType === NODE_TYPE.element) {
1724
- const innerSr = getShadowRoot ? getShadowRoot(shadowNode) : shadowNode.shadowRoot;
1865
+ const innerSr = getShadowRoot(shadowNode);
1725
1866
  if (_isDocumentFragment(innerSr)) {
1726
1867
  _sanitizeAttachedShadowRoots(innerSr);
1727
1868
  _sanitizeShadowDOM2(innerSr);
@@ -1783,7 +1924,7 @@ function createDOMPurify() {
1783
1924
  /* (pushed last → processed first) Children, snapshotted in reverse so
1784
1925
  the first child is processed first. Snapshotting matters because a
1785
1926
  hook may detach siblings mid-walk. */
1786
- const childNodes = getChildNodes ? getChildNodes(node) : node.childNodes;
1927
+ const childNodes = getChildNodes(node);
1787
1928
  if (childNodes) {
1788
1929
  for (let i = childNodes.length - 1; i >= 0; --i) {
1789
1930
  stack.push({
@@ -1813,7 +1954,7 @@ function createDOMPurify() {
1813
1954
  silently skipped foreign-realm shadow roots (e.g.
1814
1955
  iframe.contentDocument attachShadow). */
1815
1956
  if (isElement) {
1816
- const sr = getShadowRoot ? getShadowRoot(node) : node.shadowRoot;
1957
+ const sr = getShadowRoot(node);
1817
1958
  if (_isDocumentFragment(sr)) {
1818
1959
  /* Push the deferred sanitise first so it pops after the shadow
1819
1960
  walk we push next, i.e. nested shadow roots are discovered
@@ -2025,9 +2166,7 @@ function createDOMPurify() {
2025
2166
  }
2026
2167
  /* Sanitize final string template-safe */
2027
2168
  if (SAFE_FOR_TEMPLATES) {
2028
- arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
2029
- serializedHTML = stringReplace(serializedHTML, expr, ' ');
2030
- });
2169
+ serializedHTML = _stripTemplateExpressions(serializedHTML);
2031
2170
  }
2032
2171
  return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
2033
2172
  };