Skip to content

Commit

Permalink
Revert "Merge pull request #267 from jbraithwaite/jb/css-tree"
Browse files Browse the repository at this point in the history
This reverts commit e91765c, reversing
changes made to bec8ad2.
  • Loading branch information
Tom Boutell committed Dec 5, 2018
1 parent 4294e7c commit 4a73a96
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 26 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
"license": "MIT",
"dependencies": {
"chalk": "^2.4.1",
"css-tree": "^1.0.0-alpha.29",
"htmlparser2": "^3.10.0",
"lodash.clonedeep": "^4.5.0",
"lodash.escaperegexp": "^4.1.2",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.mergewith": "^4.6.1",
"postcss": "^7.0.5",
"srcset": "^1.0.0",
"xtend": "^4.0.1"
},
Expand Down
102 changes: 77 additions & 25 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ var mergeWith = require('lodash.mergewith');
var isString = require('lodash.isstring');
var isPlainObject = require('lodash.isplainobject');
var srcset = require('srcset');
var csstree = require('css-tree');
var postcss = require('postcss');
var url = require('url');

function each(obj, cb) {
Expand Down Expand Up @@ -299,31 +299,15 @@ function sanitizeHtml(html, options, _recursing) {
}
if (a === 'style') {
try {
var ast = csstree.parse(name + " {" + value + "}");
var selectors = rulesForSelector(name, options.allowedStyles || {});
var abstractSyntaxTree = postcss.parse(name + " {" + value + "}");
var filteredAST = filterCss(abstractSyntaxTree, options.allowedStyles);

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);
value = stringifyStyleAttributes(filteredAST);

if(value.length === 0) {
delete frame.attribs[a];
return;
}

// preserve the final semicolon
value += ';';

} catch (e) {
delete frame.attribs[a];
return;
Expand Down Expand Up @@ -481,21 +465,89 @@ function sanitizeHtml(html, options, _recursing) {
return !options.allowedSchemes || options.allowedSchemes.indexOf(scheme) === -1;
}

function rulesForSelector(selector, allowedStyles) {
/**
* 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;

// Merge global and tag-specific styles into new AST.
if (allowedStyles[selector] && allowedStyles['*']) {
return mergeWith(
cloneDeep(allowedStyles[selector]),
if (allowedStyles[astRules.selector] && allowedStyles['*']) {
selectedRule = mergeWith(
cloneDeep(allowedStyles[astRules.selector]),
allowedStyles['*'],
function(objValue, srcValue) {
if (Array.isArray(objValue)) {
return objValue.concat(srcValue);
}
}
);
} else {
selectedRule = allowedStyles[astRules.selector] || allowedStyles['*'];
}

return allowedStyles[selector] || allowedStyles['*'] || {};
if (selectedRule) {
filteredAST.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []);
}

return filteredAST;
}

/**
* 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) {
Expand Down

0 comments on commit 4a73a96

Please sign in to comment.