sanitize-html 2.17.0 → 2.17.1

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.
Files changed (2) hide show
  1. package/index.js +83 -39
  2. package/package.json +14 -13
package/index.js CHANGED
@@ -88,7 +88,8 @@ function sanitizeHtml(html, options, _recursing) {
88
88
  }
89
89
 
90
90
  let result = '';
91
- // Used for hot swapping the result variable with an empty string in order to "capture" the text written to it.
91
+ // Used for hot swapping the result variable with an empty string
92
+ // in order to "capture" the text written to it.
92
93
  let tempResult = '';
93
94
 
94
95
  function Frame(tag, attribs) {
@@ -119,7 +120,8 @@ function sanitizeHtml(html, options, _recursing) {
119
120
  options.parser = Object.assign({}, htmlParserDefaults, options.parser);
120
121
 
121
122
  const tagAllowed = function (name) {
122
- return options.allowedTags === false || (options.allowedTags || []).indexOf(name) > -1;
123
+ return options.allowedTags === false ||
124
+ (options.allowedTags || []).indexOf(name) > -1;
123
125
  };
124
126
 
125
127
  // vulnerableTags
@@ -301,7 +303,8 @@ function sanitizeHtml(html, options, _recursing) {
301
303
  }
302
304
 
303
305
  const isBeingEscaped = skip && (options.disallowedTagsMode === 'escape' || options.disallowedTagsMode === 'recursiveEscape');
304
- const shouldPreserveEscapedAttributes = isBeingEscaped && options.preserveEscapedAttributes;
306
+ const shouldPreserveEscapedAttributes = isBeingEscaped &&
307
+ options.preserveEscapedAttributes;
305
308
 
306
309
  if (shouldPreserveEscapedAttributes) {
307
310
  each(attribs, function(value, a) {
@@ -315,8 +318,10 @@ function sanitizeHtml(html, options, _recursing) {
315
318
  delete frame.attribs[a];
316
319
  return;
317
320
  }
318
- // If the value is empty, check if the attribute is in the allowedEmptyAttributes array.
319
- // If it is not in the allowedEmptyAttributes array, and it is a known non-boolean attribute, delete it
321
+ // If the value is empty, check if the attribute is
322
+ // in the allowedEmptyAttributes array.
323
+ // If it is not in the allowedEmptyAttributes array,
324
+ // and it is a known non-boolean attribute, delete it
320
325
  // List taken from https://html.spec.whatwg.org/multipage/indices.html#attributes-3
321
326
  if (value === '' && (!options.allowedEmptyAttributes.includes(a)) &&
322
327
  (options.nonBooleanAttributes.includes(a) || options.nonBooleanAttributes.includes('*'))) {
@@ -327,9 +332,11 @@ function sanitizeHtml(html, options, _recursing) {
327
332
  // as necessary if there are specific values defined.
328
333
  let passedAllowedAttributesMapCheck = false;
329
334
  if (!allowedAttributesMap ||
330
- (has(allowedAttributesMap, name) && allowedAttributesMap[name].indexOf(a) !== -1) ||
335
+ (has(allowedAttributesMap, name) &&
336
+ allowedAttributesMap[name].indexOf(a) !== -1) ||
331
337
  (allowedAttributesMap['*'] && allowedAttributesMap['*'].indexOf(a) !== -1) ||
332
- (has(allowedAttributesGlobMap, name) && allowedAttributesGlobMap[name].test(a)) ||
338
+ (has(allowedAttributesGlobMap, name) &&
339
+ allowedAttributesGlobMap[name].test(a)) ||
333
340
  (allowedAttributesGlobMap['*'] && allowedAttributesGlobMap['*'].test(a))) {
334
341
  passedAllowedAttributesMapCheck = true;
335
342
  } else if (allowedAttributesMap && allowedAttributesMap[name]) {
@@ -373,12 +380,14 @@ function sanitizeHtml(html, options, _recursing) {
373
380
  const parsed = parseUrl(value);
374
381
 
375
382
  if (options.allowedScriptHostnames || options.allowedScriptDomains) {
376
- const allowedHostname = (options.allowedScriptHostnames || []).find(function (hostname) {
377
- return hostname === parsed.url.hostname;
378
- });
379
- const allowedDomain = (options.allowedScriptDomains || []).find(function(domain) {
380
- return parsed.url.hostname === domain || parsed.url.hostname.endsWith(`.${domain}`);
381
- });
383
+ const allowedHostname = (options.allowedScriptHostnames || [])
384
+ .find(function (hostname) {
385
+ return hostname === parsed.url.hostname;
386
+ });
387
+ const allowedDomain = (options.allowedScriptDomains || [])
388
+ .find(function(domain) {
389
+ return parsed.url.hostname === domain || parsed.url.hostname.endsWith(`.${domain}`);
390
+ });
382
391
  allowed = allowedHostname || allowedDomain;
383
392
  }
384
393
  } catch (e) {
@@ -402,13 +411,18 @@ function sanitizeHtml(html, options, _recursing) {
402
411
  allowed = has(options, 'allowIframeRelativeUrls')
403
412
  ? options.allowIframeRelativeUrls
404
413
  : (!options.allowedIframeHostnames && !options.allowedIframeDomains);
405
- } else if (options.allowedIframeHostnames || options.allowedIframeDomains) {
406
- const allowedHostname = (options.allowedIframeHostnames || []).find(function (hostname) {
407
- return hostname === parsed.url.hostname;
408
- });
409
- const allowedDomain = (options.allowedIframeDomains || []).find(function(domain) {
410
- return parsed.url.hostname === domain || parsed.url.hostname.endsWith(`.${domain}`);
411
- });
414
+ } else if (
415
+ options.allowedIframeHostnames ||
416
+ options.allowedIframeDomains
417
+ ) {
418
+ const allowedHostname = (options.allowedIframeHostnames || [])
419
+ .find(function (hostname) {
420
+ return hostname === parsed.url.hostname;
421
+ });
422
+ const allowedDomain = (options.allowedIframeDomains || [])
423
+ .find(function(domain) {
424
+ return parsed.url.hostname === domain || parsed.url.hostname.endsWith(`.${domain}`);
425
+ });
412
426
  allowed = allowedHostname || allowedDomain;
413
427
  }
414
428
  } catch (e) {
@@ -462,9 +476,17 @@ function sanitizeHtml(html, options, _recursing) {
462
476
  return t;
463
477
  });
464
478
  if (allowedSpecificClasses && allowedWildcardClasses) {
465
- value = filterClasses(value, deepmerge(allowedSpecificClasses, allowedWildcardClasses), allowedClassesGlobs);
479
+ value = filterClasses(
480
+ value,
481
+ deepmerge(allowedSpecificClasses, allowedWildcardClasses),
482
+ allowedClassesGlobs
483
+ );
466
484
  } else {
467
- value = filterClasses(value, allowedSpecificClasses || allowedWildcardClasses, allowedClassesGlobs);
485
+ value = filterClasses(
486
+ value,
487
+ allowedSpecificClasses || allowedWildcardClasses,
488
+ allowedClassesGlobs
489
+ );
468
490
  }
469
491
  if (!value.length) {
470
492
  delete frame.attribs[a];
@@ -475,7 +497,10 @@ function sanitizeHtml(html, options, _recursing) {
475
497
  if (options.parseStyleAttributes) {
476
498
  try {
477
499
  const abstractSyntaxTree = postcssParse(name + ' {' + value + '}', { map: false });
478
- const filteredAST = filterCss(abstractSyntaxTree, options.allowedStyles);
500
+ const filteredAST = filterCss(
501
+ abstractSyntaxTree,
502
+ options.allowedStyles
503
+ );
479
504
 
480
505
  value = stringifyStyleAttributes(filteredAST);
481
506
 
@@ -608,7 +633,8 @@ function sanitizeHtml(html, options, _recursing) {
608
633
  tempResult = '';
609
634
  }
610
635
  // remove the opening tag from the result
611
- result = result.substring(0, frame.tagPosition) + result.substring(frame.tagPosition + frame.openingTagLength);
636
+ result = result.substring(0, frame.tagPosition) +
637
+ result.substring(frame.tagPosition + frame.openingTagLength);
612
638
  return;
613
639
  } else if (filterResult) {
614
640
  result = result.substring(0, frame.tagPosition);
@@ -643,6 +669,16 @@ function sanitizeHtml(html, options, _recursing) {
643
669
  parser.write(html);
644
670
  parser.end();
645
671
 
672
+ if (options.disallowedTagsMode === 'escape' || options.disallowedTagsMode === 'recursiveEscape') {
673
+ const lastParsedIndex = parser.endIndex;
674
+ if (lastParsedIndex != null && lastParsedIndex >= 0 && lastParsedIndex < html.length) {
675
+ const unparsed = html.substring(lastParsedIndex);
676
+ result += escapeHtml(unparsed);
677
+ } else if ((lastParsedIndex == null || lastParsedIndex < 0) && html.length > 0 && result === '') {
678
+ result = escapeHtml(html);
679
+ }
680
+ }
681
+
646
682
  return result;
647
683
 
648
684
  function initializeState() {
@@ -753,8 +789,10 @@ function sanitizeHtml(html, options, _recursing) {
753
789
  * Modifies the abstractSyntaxTree object.
754
790
  *
755
791
  * @param {object} abstractSyntaxTree - Object representation of CSS attributes.
756
- * @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }.
757
- * @param {object} allowedStyles - Keys are properties (i.e color), value is list of permitted regex rules (i.e /green/i).
792
+ * @property {array[Declaration]} abstractSyntaxTree.nodes[0] -
793
+ * Each object contains prop and value key, i.e { prop: 'color', value: 'red' }.
794
+ * @param {object} allowedStyles - Keys are properties (i.e color),
795
+ * value is list of permitted regex rules (i.e /green/i).
758
796
  * @return {object} - The modified tree.
759
797
  */
760
798
  function filterCss(abstractSyntaxTree, allowedStyles) {
@@ -776,7 +814,8 @@ function sanitizeHtml(html, options, _recursing) {
776
814
  }
777
815
 
778
816
  if (selectedRule) {
779
- abstractSyntaxTree.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []);
817
+ abstractSyntaxTree.nodes[0].nodes = astRules.nodes
818
+ .reduce(filterDeclarations(selectedRule), []);
780
819
  }
781
820
 
782
821
  return abstractSyntaxTree;
@@ -787,7 +826,8 @@ function sanitizeHtml(html, options, _recursing) {
787
826
  * values in the inline style attribute format.
788
827
  *
789
828
  * @param {AbstractSyntaxTree} filteredAST
790
- * @return {string} - Example: "color:yellow;text-align:center !important;font-family:helvetica;"
829
+ * @return {string} - Example:
830
+ * "color:yellow;text-align:center !important;font-family:helvetica;"
791
831
  */
792
832
  function stringifyStyleAttributes(filteredAST) {
793
833
  return filteredAST.nodes[0].nodes
@@ -804,21 +844,25 @@ function sanitizeHtml(html, options, _recursing) {
804
844
  * Filters the existing attributes for the given property. Discards any attributes
805
845
  * which don't match the allowlist.
806
846
  *
807
- * @param {object} selectedRule - Example: { color: red, font-family: helvetica }
808
- * @param {array} allowedDeclarationsList - List of declarations which pass the allowlist.
809
- * @param {object} attributeObject - Object representing the current css property.
810
- * @property {string} attributeObject.type - Typically 'declaration'.
811
- * @property {string} attributeObject.prop - The CSS property, i.e 'color'.
812
- * @property {string} attributeObject.value - The corresponding value to the css property, i.e 'red'.
813
- * @return {function} - When used in Array.reduce, will return an array of Declaration objects
847
+ * @param {object} selectedRule - Example: { color: red, font-family: helvetica }
848
+ * @param {array} allowedDeclarationsList - List of declarations
849
+ * which pass the allowlist.
850
+ * @param {object} attributeObject - Object representing the current css property.
851
+ * @property {string} attributeObject.type - Typically 'declaration'.
852
+ * @property {string} attributeObject.prop - The CSS property, i.e 'color'.
853
+ * @property {string} attributeObject.value - The corresponding value to
854
+ * the css property, i.e 'red'.
855
+ * @return {function} - When used in Array.reduce,
856
+ * will return an array of Declaration objects
814
857
  */
815
858
  function filterDeclarations(selectedRule) {
816
859
  return function (allowedDeclarationsList, attributeObject) {
817
860
  // If this property is allowlisted...
818
861
  if (has(selectedRule, attributeObject.prop)) {
819
- const matchesRegex = selectedRule[attributeObject.prop].some(function(regularExpression) {
820
- return regularExpression.test(attributeObject.value);
821
- });
862
+ const matchesRegex = selectedRule[attributeObject.prop]
863
+ .some(function(regularExpression) {
864
+ return regularExpression.test(attributeObject.value);
865
+ });
822
866
 
823
867
  if (matchesRegex) {
824
868
  allowedDeclarationsList.push(attributeObject);
@@ -950,7 +994,7 @@ sanitizeHtml.simpleTransform = function(newTagName, newAttribs, merge) {
950
994
 
951
995
  return {
952
996
  tagName: newTagName,
953
- attribs: attribs
997
+ attribs
954
998
  };
955
999
  };
956
1000
  };
package/package.json CHANGED
@@ -1,19 +1,18 @@
1
1
  {
2
2
  "name": "sanitize-html",
3
- "version": "2.17.0",
3
+ "version": "2.17.1",
4
4
  "description": "Clean up user-submitted HTML, preserving allowlisted elements and allowlisted attributes on a per-element basis",
5
5
  "sideEffects": false,
6
6
  "main": "index.js",
7
7
  "files": [
8
8
  "index.js"
9
9
  ],
10
- "scripts": {
11
- "test": "npx eslint . && mocha test/test.js"
12
- },
13
10
  "repository": {
14
11
  "type": "git",
15
- "url": "https://github.com/apostrophecms/sanitize-html.git"
12
+ "url": "https://github.com/apostrophecms/apostrophe.git",
13
+ "directory": "packages/sanitize-html"
16
14
  },
15
+ "homepage": "https://github.com/apostrophecms/apostrophe/tree/main/packages/sanitize-html#readme",
17
16
  "keywords": [
18
17
  "html",
19
18
  "parser",
@@ -31,14 +30,16 @@
31
30
  "postcss": "^8.3.11"
32
31
  },
33
32
  "devDependencies": {
34
- "eslint": "^7.3.1",
35
- "eslint-config-apostrophe": "^3.4.0",
36
- "eslint-config-standard": "^14.1.1",
37
- "eslint-plugin-import": "^2.25.2",
38
- "eslint-plugin-node": "^11.1.0",
39
- "eslint-plugin-promise": "^4.2.1",
40
- "eslint-plugin-standard": "^4.0.1",
33
+ "eslint": "^9.39.1",
41
34
  "mocha": "^10.2.0",
42
- "sinon": "^9.0.2"
35
+ "sinon": "^9.0.2",
36
+ "eslint-config-apostrophe": "^6.0.2"
37
+ },
38
+ "apostropheTestConfig": {
39
+ "requiresMongo": false
40
+ },
41
+ "scripts": {
42
+ "test": "npm run lint && mocha",
43
+ "lint": "eslint ."
43
44
  }
44
45
  }