sanitize-html 2.5.0 → 2.6.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.6.0 (2021-11-23)
4
+
5
+ - Support for regular expressions in the `allowedClasses` option. Thanks to [Alex Rantos](https://github.com/alex-rantos).
6
+
7
+ ## 2.5.3 (2021-11-02):
8
+
9
+ - Fixed bug introduced by klona 2.0.5, by removing klona entirely.
10
+
11
+ ## 2.5.2 (2021-10-13):
12
+
13
+ - Nullish HTML input now returns an empty string. Nullish value may be explicit `null`, `undefined` or implicit `undefined` when value is not provided. Thanks to Artem Kostiuk for the contribution.
14
+ - Documented that all text content is escaped. Thanks to Siddharth Singh.
15
+
16
+ ## 2.5.1 (2021-09-14):
17
+ - The `allowedScriptHostnames` and `allowedScriptDomains` options now implicitly purge the inline content of all script tags, not just those with `src` attributes. This behavior was already strongly implied by the fact that they purged it in the case where a `src` attribute was actually present, and is necessary for the feature to provide any real security. Thanks to Grigorii Duca for pointing out the issue.
18
+
3
19
  ## 2.5.0 (2021-09-08):
4
20
 
5
21
  - New `allowedScriptHostnames` option, it enables you to specify which hostnames are allowed in a script tag.
package/README.md CHANGED
@@ -21,6 +21,7 @@ The syntax of poorly closed `p` and `img` elements is cleaned up.
21
21
  Allowing particular urls as a `src` to an iframe tag by filtering hostnames is also supported.
22
22
 
23
23
  HTML comments are not preserved.
24
+ Additionally, `sanitize-html` escapes _ALL_ text content - this means that ampersands, greater-than, and less-than signs are converted to their equivalent HTML character references (`&` --> `&amp;`, `<` --> `&lt;`, and so on). Additionally, in attribute values, quotation marks are escaped as well (`"` --> `&quot;`).
24
25
 
25
26
  ## Requirements
26
27
 
@@ -238,6 +239,7 @@ const clean = sanitizeHtml(dirty, {
238
239
  ```
239
240
 
240
241
  Similar to `allowedAttributes`, you can use `*` to allow classes with a certain prefix, or use `*` as a tag name to allow listed classes to be valid for any tag:
242
+
241
243
  ```js
242
244
  allowedClasses: {
243
245
  'code': [ 'language-*', 'lang-*' ],
@@ -245,6 +247,16 @@ allowedClasses: {
245
247
  }
246
248
  ```
247
249
 
250
+ Furthermore, regular expressions are supported too:
251
+
252
+ ```js
253
+ allowedClasses: {
254
+ p: [ /^regex\d{2}$/ ]
255
+ }
256
+ ```
257
+
258
+ > Note: It is advised that your regular expressions always begin with `^` so that you are requiring a known prefix. A regular expression with neither `^` nor `$` just requires that something appear in the middle.
259
+
248
260
  ### Allowed CSS Styles
249
261
 
250
262
  If you wish to allow specific CSS _styles_ on a particular element, you can do that with the `allowedStyles` option. Simply declare your desired attributes as regular expression options within an array for the given attribute. Specific elements will inherit allowlisted attributes from the global (`*`) attribute. Any other CSS classes are discarded.
package/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const htmlparser = require('htmlparser2');
2
2
  const escapeStringRegexp = require('escape-string-regexp');
3
- const { klona } = require('klona');
4
3
  const { isPlainObject } = require('is-plain-object');
5
4
  const deepmerge = require('deepmerge');
6
5
  const parseSrcset = require('parse-srcset');
@@ -81,6 +80,10 @@ const VALID_HTML_ATTRIBUTE_NAME = /^[^\0\t\n\f\r /<=>]+$/;
81
80
  // https://github.com/fb55/htmlparser2/issues/105
82
81
 
83
82
  function sanitizeHtml(html, options, _recursing) {
83
+ if (html == null) {
84
+ return '';
85
+ }
86
+
84
87
  let result = '';
85
88
  // Used for hot swapping the result variable with an empty string in order to "capture" the text written to it.
86
89
  let tempResult = '';
@@ -153,6 +156,7 @@ function sanitizeHtml(html, options, _recursing) {
153
156
  }
154
157
  const allowedClassesMap = {};
155
158
  const allowedClassesGlobMap = {};
159
+ const allowedClassesRegexMap = {};
156
160
  each(options.allowedClasses, function(classes, tag) {
157
161
  // Implicitly allows the class attribute
158
162
  if (allowedAttributesMap) {
@@ -163,10 +167,13 @@ function sanitizeHtml(html, options, _recursing) {
163
167
  }
164
168
 
165
169
  allowedClassesMap[tag] = [];
170
+ allowedClassesRegexMap[tag] = [];
166
171
  const globRegex = [];
167
172
  classes.forEach(function(obj) {
168
173
  if (typeof obj === 'string' && obj.indexOf('*') >= 0) {
169
174
  globRegex.push(escapeStringRegexp(obj).replace(/\\\*/g, '.*'));
175
+ } else if (obj instanceof RegExp) {
176
+ allowedClassesRegexMap[tag].push(obj);
170
177
  } else {
171
178
  allowedClassesMap[tag].push(obj);
172
179
  }
@@ -265,6 +272,13 @@ function sanitizeHtml(html, options, _recursing) {
265
272
  result = '';
266
273
  }
267
274
  result += '<' + name;
275
+
276
+ if (name === 'script') {
277
+ if (options.allowedScriptHostnames || options.allowedScriptDomains) {
278
+ frame.innerText = '';
279
+ }
280
+ }
281
+
268
282
  if (!allowedAttributesMap || has(allowedAttributesMap, name) || allowedAttributesMap['*']) {
269
283
  each(attribs, function(value, a) {
270
284
  if (!VALID_HTML_ATTRIBUTE_NAME.test(a)) {
@@ -315,10 +329,10 @@ function sanitizeHtml(html, options, _recursing) {
315
329
  return;
316
330
  }
317
331
  }
332
+
318
333
  if (name === 'script' && a === 'src') {
319
- let allowed = true;
320
334
 
321
- frame.innerText = '';
335
+ let allowed = true;
322
336
 
323
337
  try {
324
338
  const parsed = new URL(value);
@@ -421,12 +435,16 @@ function sanitizeHtml(html, options, _recursing) {
421
435
  const allowedSpecificClasses = allowedClassesMap[name];
422
436
  const allowedWildcardClasses = allowedClassesMap['*'];
423
437
  const allowedSpecificClassesGlob = allowedClassesGlobMap[name];
438
+ const allowedSpecificClassesRegex = allowedClassesRegexMap[name];
424
439
  const allowedWildcardClassesGlob = allowedClassesGlobMap['*'];
425
- const allowedClassesGlobs = [ allowedSpecificClassesGlob, allowedWildcardClassesGlob ].filter(
426
- function(t) {
440
+ const allowedClassesGlobs = [
441
+ allowedSpecificClassesGlob,
442
+ allowedWildcardClassesGlob
443
+ ]
444
+ .concat(allowedSpecificClassesRegex)
445
+ .filter(function (t) {
427
446
  return t;
428
- }
429
- );
447
+ });
430
448
  if (allowedSpecificClasses && allowedWildcardClasses) {
431
449
  value = filterClasses(value, deepmerge(allowedSpecificClasses, allowedWildcardClasses), allowedClassesGlobs);
432
450
  } else {
@@ -639,18 +657,18 @@ function sanitizeHtml(html, options, _recursing) {
639
657
 
640
658
  /**
641
659
  * Filters user input css properties by allowlisted regex attributes.
660
+ * Modifies the abstractSyntaxTree object.
642
661
  *
643
662
  * @param {object} abstractSyntaxTree - Object representation of CSS attributes.
644
663
  * @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }.
645
664
  * @param {object} allowedStyles - Keys are properties (i.e color), value is list of permitted regex rules (i.e /green/i).
646
- * @return {object} - Abstract Syntax Tree with filtered style attributes.
665
+ * @return {object} - The modified tree.
647
666
  */
648
667
  function filterCss(abstractSyntaxTree, allowedStyles) {
649
668
  if (!allowedStyles) {
650
669
  return abstractSyntaxTree;
651
670
  }
652
671
 
653
- const filteredAST = klona(abstractSyntaxTree);
654
672
  const astRules = abstractSyntaxTree.nodes[0];
655
673
  let selectedRule;
656
674
 
@@ -665,10 +683,10 @@ function sanitizeHtml(html, options, _recursing) {
665
683
  }
666
684
 
667
685
  if (selectedRule) {
668
- filteredAST.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []);
686
+ abstractSyntaxTree.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []);
669
687
  }
670
688
 
671
- return filteredAST;
689
+ return abstractSyntaxTree;
672
690
  }
673
691
 
674
692
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanitize-html",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
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",
@@ -27,15 +27,14 @@
27
27
  "escape-string-regexp": "^4.0.0",
28
28
  "htmlparser2": "^6.0.0",
29
29
  "is-plain-object": "^5.0.0",
30
- "klona": "^2.0.3",
31
30
  "parse-srcset": "^1.0.2",
32
- "postcss": "^8.0.2"
31
+ "postcss": "^8.3.11"
33
32
  },
34
33
  "devDependencies": {
35
34
  "eslint": "^7.3.1",
36
35
  "eslint-config-apostrophe": "^3.4.0",
37
36
  "eslint-config-standard": "^14.1.1",
38
- "eslint-plugin-import": "^2.21.2",
37
+ "eslint-plugin-import": "^2.25.2",
39
38
  "eslint-plugin-node": "^11.1.0",
40
39
  "eslint-plugin-promise": "^4.2.1",
41
40
  "eslint-plugin-standard": "^4.0.1",