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.
- package/index.js +83 -39
- 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
|
|
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 ||
|
|
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 &&
|
|
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
|
|
319
|
-
//
|
|
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) &&
|
|
335
|
+
(has(allowedAttributesMap, name) &&
|
|
336
|
+
allowedAttributesMap[name].indexOf(a) !== -1) ||
|
|
331
337
|
(allowedAttributesMap['*'] && allowedAttributesMap['*'].indexOf(a) !== -1) ||
|
|
332
|
-
(has(allowedAttributesGlobMap, name) &&
|
|
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 || [])
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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 (
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
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(
|
|
479
|
+
value = filterClasses(
|
|
480
|
+
value,
|
|
481
|
+
deepmerge(allowedSpecificClasses, allowedWildcardClasses),
|
|
482
|
+
allowedClassesGlobs
|
|
483
|
+
);
|
|
466
484
|
} else {
|
|
467
|
-
value = filterClasses(
|
|
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(
|
|
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) +
|
|
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] -
|
|
757
|
-
*
|
|
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
|
|
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:
|
|
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
|
|
808
|
-
* @param {array} allowedDeclarationsList
|
|
809
|
-
*
|
|
810
|
-
* @
|
|
811
|
-
* @property {string} attributeObject.
|
|
812
|
-
* @property {string} attributeObject.
|
|
813
|
-
* @
|
|
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]
|
|
820
|
-
|
|
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
|
|
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.
|
|
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/
|
|
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": "^
|
|
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
|
}
|