sanitize-html
Advanced tools
Comparing version 1.19.1 to 1.19.2
## Changelog | ||
1.19.2: | ||
* Switched out the heavy `postcss` dependency for the lightweight `css-tree` module. No API changes. Thanks to Justin Braithwaite. | ||
* Various doc updates. Thanks to Pulkit Aggarwal and Cody Robertson. | ||
1.19.1: | ||
@@ -4,0 +9,0 @@ |
@@ -11,3 +11,3 @@ 'use strict'; | ||
var srcset = require('srcset'); | ||
var postcss = require('postcss'); | ||
var csstree = require('css-tree'); | ||
var url = require('url'); | ||
@@ -342,7 +342,21 @@ | ||
try { | ||
var abstractSyntaxTree = postcss.parse(name + " {" + value + "}"); | ||
var filteredAST = filterCss(abstractSyntaxTree, options.allowedStyles); | ||
var ast = csstree.parse(name + " {" + value + "}"); | ||
var selectors = rulesForSelector(name, options.allowedStyles || {}); | ||
value = stringifyStyleAttributes(filteredAST); | ||
csstree.walk(ast, function (node, item, list) { | ||
if (node.type === 'Declaration' && list) { | ||
var value = csstree.generate(node.value); | ||
var rules = selectors[node.property]; | ||
if (rules !== undefined && rules.every(function (rule) { | ||
return !value.match(rule); | ||
})) { | ||
list.remove(item); | ||
} | ||
} | ||
}); | ||
value = csstree.generate(ast).slice(name.length + 1); | ||
value = value.slice(0, value.length - 1); | ||
if (value.length === 0) { | ||
@@ -352,2 +366,5 @@ delete frame.attribs[a]; | ||
} | ||
// preserve the final semicolon | ||
value += ';'; | ||
} catch (e) { | ||
@@ -509,22 +526,6 @@ delete frame.attribs[a]; | ||
/** | ||
* Filters user input css properties by whitelisted regex attributes. | ||
* | ||
* @param {object} abstractSyntaxTree - Object representation of CSS attributes. | ||
* @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }. | ||
* @param {object} allowedStyles - Keys are properties (i.e color), value is list of permitted regex rules (i.e /green/i). | ||
* @return {object} - Abstract Syntax Tree with filtered style attributes. | ||
*/ | ||
function filterCss(abstractSyntaxTree, allowedStyles) { | ||
if (!allowedStyles) { | ||
return abstractSyntaxTree; | ||
} | ||
var filteredAST = cloneDeep(abstractSyntaxTree); | ||
var astRules = abstractSyntaxTree.nodes[0]; | ||
var selectedRule; | ||
function rulesForSelector(selector, allowedStyles) { | ||
// Merge global and tag-specific styles into new AST. | ||
if (allowedStyles[astRules.selector] && allowedStyles['*']) { | ||
selectedRule = mergeWith(cloneDeep(allowedStyles[astRules.selector]), allowedStyles['*'], function (objValue, srcValue) { | ||
if (allowedStyles[selector] && allowedStyles['*']) { | ||
return mergeWith(cloneDeep(allowedStyles[selector]), allowedStyles['*'], function (objValue, srcValue) { | ||
if (Array.isArray(objValue)) { | ||
@@ -534,55 +535,7 @@ return objValue.concat(srcValue); | ||
}); | ||
} else { | ||
selectedRule = allowedStyles[astRules.selector] || allowedStyles['*']; | ||
} | ||
if (selectedRule) { | ||
filteredAST.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []); | ||
} | ||
return filteredAST; | ||
return allowedStyles[selector] || allowedStyles['*'] || {}; | ||
} | ||
/** | ||
* Extracts the style attribues from an AbstractSyntaxTree and formats those | ||
* values in the inline style attribute format. | ||
* | ||
* @param {AbstractSyntaxTree} filteredAST | ||
* @return {string} - Example: "color:yellow;text-align:center;font-family:helvetica;" | ||
*/ | ||
function stringifyStyleAttributes(filteredAST) { | ||
return filteredAST.nodes[0].nodes.reduce(function (extractedAttributes, attributeObject) { | ||
extractedAttributes.push(attributeObject.prop + ':' + attributeObject.value + ';'); | ||
return extractedAttributes; | ||
}, []).join(''); | ||
} | ||
/** | ||
* Filters the existing attributes for the given property. Discards any attributes | ||
* which don't match the whitelist. | ||
* | ||
* @param {object} selectedRule - Example: { color: red, font-family: helvetica } | ||
* @param {array} allowedDeclarationsList - List of declarations which pass whitelisting. | ||
* @param {object} attributeObject - Object representing the current css property. | ||
* @property {string} attributeObject.type - Typically 'declaration'. | ||
* @property {string} attributeObject.prop - The CSS property, i.e 'color'. | ||
* @property {string} attributeObject.value - The corresponding value to the css property, i.e 'red'. | ||
* @return {function} - When used in Array.reduce, will return an array of Declaration objects | ||
*/ | ||
function filterDeclarations(selectedRule) { | ||
return function (allowedDeclarationsList, attributeObject) { | ||
// If this property is whitelisted... | ||
if (selectedRule.hasOwnProperty(attributeObject.prop)) { | ||
var matchesRegex = selectedRule[attributeObject.prop].some(function (regularExpression) { | ||
return regularExpression.test(attributeObject.value); | ||
}); | ||
if (matchesRegex) { | ||
allowedDeclarationsList.push(attributeObject); | ||
} | ||
} | ||
return allowedDeclarationsList; | ||
}; | ||
} | ||
function filterClasses(classes, allowed) { | ||
@@ -589,0 +542,0 @@ if (!allowed) { |
{ | ||
"name": "sanitize-html", | ||
"version": "1.19.1", | ||
"version": "1.19.2", | ||
"description": "Clean up user-submitted HTML, preserving whitelisted elements and whitelisted attributes on a per-element basis", | ||
@@ -28,4 +28,5 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"chalk": "^2.3.0", | ||
"htmlparser2": "^3.9.0", | ||
"chalk": "^2.4.1", | ||
"css-tree": "^1.0.0-alpha.29", | ||
"htmlparser2": "^3.10.0", | ||
"lodash.clonedeep": "^4.5.0", | ||
@@ -35,15 +36,14 @@ "lodash.escaperegexp": "^4.1.2", | ||
"lodash.isstring": "^4.0.1", | ||
"lodash.mergewith": "^4.6.0", | ||
"postcss": "^6.0.14", | ||
"lodash.mergewith": "^4.6.1", | ||
"srcset": "^1.0.0", | ||
"xtend": "^4.0.0" | ||
"xtend": "^4.0.1" | ||
}, | ||
"devDependencies": { | ||
"babel-cli": "^6.26.0", | ||
"babel-preset-env": "^1.6.1", | ||
"babelify": "^8.0.0", | ||
"browserify": "^13.0.1", | ||
"mocha": "^2.5.3", | ||
"uglify-js": "^2.6.2" | ||
"babel-preset-env": "^1.7.0", | ||
"babelify": "^10.0.0", | ||
"browserify": "^16.2.3", | ||
"mocha": "^5.2.0", | ||
"uglify-js": "^3.4.9" | ||
} | ||
} |
@@ -113,3 +113,4 @@ # sanitize-html | ||
// We don't currently allow img itself by default, but this | ||
// would make sense if we did | ||
// would make sense if we did. You could add srcset here, | ||
// and if you do the URL is checked for safety | ||
img: [ 'src' ] | ||
@@ -123,4 +124,3 @@ }, | ||
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ], | ||
allowProtocolRelative: true, | ||
allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com'] | ||
allowProtocolRelative: true | ||
``` | ||
@@ -140,7 +140,7 @@ | ||
Also simple! Set your `allowedTag` and `allowedAttributes` to empty arrays (`[]`). | ||
Also simple! Set `allowedTags` to `[]` and `allowedAttributes` to `{}`. | ||
```js | ||
allowedTags: [], | ||
allowedAttributes: [] | ||
allowedAttributes: {} | ||
``` | ||
@@ -147,0 +147,0 @@ |
102
src/index.js
@@ -9,3 +9,3 @@ var htmlparser = require('htmlparser2'); | ||
var srcset = require('srcset'); | ||
var postcss = require('postcss'); | ||
var csstree = require('css-tree'); | ||
var url = require('url'); | ||
@@ -303,7 +303,19 @@ | ||
try { | ||
var abstractSyntaxTree = postcss.parse(name + " {" + value + "}"); | ||
var filteredAST = filterCss(abstractSyntaxTree, options.allowedStyles); | ||
var ast = csstree.parse(name + " {" + value + "}"); | ||
var selectors = rulesForSelector(name, options.allowedStyles || {}); | ||
value = stringifyStyleAttributes(filteredAST); | ||
csstree.walk(ast, function(node, item, list) { | ||
if (node.type === 'Declaration' && list) { | ||
var value = csstree.generate(node.value); | ||
var rules = selectors[node.property]; | ||
if (rules !== undefined && rules.every(function (rule) { return !value.match(rule); })) { | ||
list.remove(item) | ||
} | ||
} | ||
}) | ||
value = csstree.generate(ast).slice(name.length + 1); | ||
value = value.slice(0, value.length - 1); | ||
if(value.length === 0) { | ||
@@ -313,2 +325,6 @@ delete frame.attribs[a]; | ||
} | ||
// preserve the final semicolon | ||
value += ';'; | ||
} catch (e) { | ||
@@ -471,23 +487,7 @@ delete frame.attribs[a]; | ||
/** | ||
* Filters user input css properties by whitelisted regex attributes. | ||
* | ||
* @param {object} abstractSyntaxTree - Object representation of CSS attributes. | ||
* @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }. | ||
* @param {object} allowedStyles - Keys are properties (i.e color), value is list of permitted regex rules (i.e /green/i). | ||
* @return {object} - Abstract Syntax Tree with filtered style attributes. | ||
*/ | ||
function filterCss(abstractSyntaxTree, allowedStyles) { | ||
if (!allowedStyles) { | ||
return abstractSyntaxTree; | ||
} | ||
var filteredAST = cloneDeep(abstractSyntaxTree); | ||
var astRules = abstractSyntaxTree.nodes[0]; | ||
var selectedRule; | ||
function rulesForSelector(selector, allowedStyles) { | ||
// Merge global and tag-specific styles into new AST. | ||
if (allowedStyles[astRules.selector] && allowedStyles['*']) { | ||
selectedRule = mergeWith( | ||
cloneDeep(allowedStyles[astRules.selector]), | ||
if (allowedStyles[selector] && allowedStyles['*']) { | ||
return mergeWith( | ||
cloneDeep(allowedStyles[selector]), | ||
allowedStyles['*'], | ||
@@ -500,59 +500,7 @@ function(objValue, srcValue) { | ||
); | ||
} else { | ||
selectedRule = allowedStyles[astRules.selector] || allowedStyles['*']; | ||
} | ||
if (selectedRule) { | ||
filteredAST.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []); | ||
} | ||
return filteredAST; | ||
return allowedStyles[selector] || allowedStyles['*'] || {}; | ||
} | ||
/** | ||
* Extracts the style attribues from an AbstractSyntaxTree and formats those | ||
* values in the inline style attribute format. | ||
* | ||
* @param {AbstractSyntaxTree} filteredAST | ||
* @return {string} - Example: "color:yellow;text-align:center;font-family:helvetica;" | ||
*/ | ||
function stringifyStyleAttributes(filteredAST) { | ||
return filteredAST.nodes[0].nodes | ||
.reduce(function(extractedAttributes, attributeObject) { | ||
extractedAttributes.push( | ||
attributeObject.prop + ':' + attributeObject.value + ';' | ||
); | ||
return extractedAttributes; | ||
}, []) | ||
.join(''); | ||
} | ||
/** | ||
* Filters the existing attributes for the given property. Discards any attributes | ||
* which don't match the whitelist. | ||
* | ||
* @param {object} selectedRule - Example: { color: red, font-family: helvetica } | ||
* @param {array} allowedDeclarationsList - List of declarations which pass whitelisting. | ||
* @param {object} attributeObject - Object representing the current css property. | ||
* @property {string} attributeObject.type - Typically 'declaration'. | ||
* @property {string} attributeObject.prop - The CSS property, i.e 'color'. | ||
* @property {string} attributeObject.value - The corresponding value to the css property, i.e 'red'. | ||
* @return {function} - When used in Array.reduce, will return an array of Declaration objects | ||
*/ | ||
function filterDeclarations(selectedRule) { | ||
return function (allowedDeclarationsList, attributeObject) { | ||
// If this property is whitelisted... | ||
if (selectedRule.hasOwnProperty(attributeObject.prop)) { | ||
var matchesRegex = selectedRule[attributeObject.prop].some(function(regularExpression) { | ||
return regularExpression.test(attributeObject.value); | ||
}); | ||
if (matchesRegex) { | ||
allowedDeclarationsList.push(attributeObject); | ||
} | ||
} | ||
return allowedDeclarationsList; | ||
}; | ||
} | ||
function filterClasses(classes, allowed) { | ||
@@ -559,0 +507,0 @@ if (!allowed) { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
1315904
24323
10
22
+ Addedcss-tree@^1.0.0-alpha.29
+ Addedcss-tree@1.1.3(transitive)
+ Addedmdn-data@2.0.14(transitive)
- Removedpostcss@^6.0.14
- Removedpostcss@6.0.23(transitive)
Updatedchalk@^2.4.1
Updatedhtmlparser2@^3.10.0
Updatedlodash.mergewith@^4.6.1
Updatedxtend@^4.0.1