Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sanitize css values #21

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 80 additions & 36 deletions lib/sanitize.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ function Sanitize(){
this.config.allow_comments = options.allow_comments ? options.allow_comments : false;
this.allowed_elements = {};
this.config.protocols = options.protocols ? options.protocols : {};
//allowed styles
this.config.styles = options.styles ? options.styles : [];
this.config.cssValues = options.cssValues ? options.cssValues : {};

//add the rgb counterparts to colors
this.hexToRgb(this.config.cssValues.color);
//add the rgb counterparts to background colors
this.hexToRgb(this.config.cssValues.backgroundColor);

this.config.add_attributes = options.add_attributes ? options.add_attributes : {};
this.dom = options.dom ? options.dom : document;
for(i=0;i<this.config.elements.length;i++) {
Expand All @@ -38,7 +47,7 @@ function Sanitize(){
this.config.remove_element_contents = {};
this.config.remove_all_contents = false;
if(options.remove_contents) {

if(options.remove_contents instanceof Array) {
for(i=0;i<options.remove_contents.length;i++) {
this.config.remove_element_contents[options.remove_contents[i]] = true;
Expand All @@ -57,25 +66,36 @@ Sanitize.REGEX_PROTOCOL = /^([A-Za-z0-9\+\-\.\&\;\*\s]*?)(?:\:|&*0*58|&*x0*3a)/i
Sanitize.RELATIVE = '__RELATIVE__';
Sanitize.ALL = '__ALL__';

Sanitize.prototype.hexToRgb = function(colorList) {
if(colorList) {
for(var i = 0; i < colorList.length; i++) {
var result = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(colorList[i]);
if(result) {
colorList.push("rgb(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ")");
}
}
}
};

Sanitize.prototype.clean_node = function(container) {
var fragment = this.dom.createDocumentFragment();
this.current_element = fragment;
this.whitelist_nodes = [];



/**
* Utility function to check if an element exists in an array
*/
function _array_index(needle, haystack) {
var i;
for(i=0; i < haystack.length; i++) {
if(haystack[i] == needle)
if(haystack[i] == needle)
return i;
}
return -1;
}

function _merge_arrays_uniq() {
var result = [];
var uniq_hash = {};
Expand All @@ -92,7 +112,7 @@ Sanitize.prototype.clean_node = function(container) {
}
return result;
}

/**
* Clean function that checks the different node types and cleans them up accordingly
* @param elem DOM Node to clean
Expand Down Expand Up @@ -125,49 +145,75 @@ Sanitize.prototype.clean_node = function(container) {
if (console && console.log) console.log("unknown node type", elem.nodeType);
break;
}

}

function _clean_element(elem) {
var i, j, clone, parent_element, name, allowed_attributes, attr, attr_name, attr_node, protocols, del, attr_ok;
var transform = _transform_element.call(this, elem);

elem = transform.node;
name = elem.nodeName.toLowerCase();

// check if element itself is allowed
parent_element = this.current_element;
if(this.allowed_elements[name] || transform.whitelist) {
this.current_element = this.dom.createElement(elem.nodeName);
parent_element.appendChild(this.current_element);
this.current_element = this.dom.createElement(elem.nodeName);
parent_element.appendChild(this.current_element);

// clean attributes
var attrs = this.config.attributes;
allowed_attributes = _merge_arrays_uniq(attrs[name], attrs[Sanitize.ALL], transform.attr_whitelist);
for(i=0;i<allowed_attributes.length;i++) {
attr_name = allowed_attributes[i];
attr = elem.attributes[attr_name];
if(attr) {
attr_ok = true;
// Check protocol attributes for valid protocol
if(this.config.protocols[name] && this.config.protocols[name][attr_name]) {
protocols = this.config.protocols[name][attr_name];
del = attr.value.toLowerCase().match(Sanitize.REGEX_PROTOCOL);
if(del) {
attr_ok = (_array_index(del[1], protocols) != -1);
attr_ok = true;
// Check protocol attributes for valid protocol
if(this.config.protocols[name] && this.config.protocols[name][attr_name]) {
protocols = this.config.protocols[name][attr_name];
del = attr.value.toLowerCase().match(Sanitize.REGEX_PROTOCOL);
if(del) {
attr_ok = (_array_index(del[1], protocols) != -1);
}
else {
attr_ok = (_array_index(Sanitize.RELATIVE, protocols) != -1);
}
}

// Check style attributes for valid style
if(attr_name == "style") {
var resultStyle = '';
for(var index in Object.keys(elem.style)) {
var styleName = elem.style[index];
//"text-decoration-line" should actually be "text-decoration"
if(styleName == "text-decoration-line") {
styleName = "text-decoration";
}
else {
attr_ok = (_array_index(Sanitize.RELATIVE, protocols) != -1);
//we only need to see if the style is white-listed if the element has that style
if(elem.style.hasOwnProperty(index) && this.config.styles.indexOf(styleName) != -1) {
//get rid of the "" and '' that sometimes surround the style values
var styleValue = elem.style[styleName].trim().toLowerCase().replace(/['"]+/g, '');
//check to see if we allow the style value
if(!(styleName == "color" && this.config.cssValues.color.indexOf(styleValue) == -1 ||
styleName == "background-color" && this.config.cssValues.backgroundColor.indexOf(styleValue) == -1 ||
styleName == "font-family" && this.config.cssValues.fontFamily.indexOf(styleValue) == -1 ||
styleName == "font-size" && this.config.cssValues.fontSize.indexOf(styleValue) == -1)) {
resultStyle += (styleName + ': ' + elem.style[styleName] + ';');
}
}
}
if(attr_ok) {
attr_node = document.createAttribute(attr_name);
attr_node.value = attr.value;
this.current_element.setAttributeNode(attr_node);
}
attr.value = resultStyle;
}

if(attr_ok) {
attr_node = document.createAttribute(attr_name);
attr_node.value = attr.value;
this.current_element.setAttributeNode(attr_node);
}
}
}

// Add attributes
if(this.config.add_attributes[name]) {
for(attr_name in this.config.add_attributes[name]) {
Expand All @@ -194,14 +240,14 @@ Sanitize.prototype.clean_node = function(container) {
_clean.call(this, elem.childNodes[i]);
}
}

// some versions of IE don't support normalize.
if(this.current_element.normalize) {
this.current_element.normalize();
}
this.current_element = parent_element;
} // END clean_element function

function _transform_element(node) {
var output = {
attr_whitelist:[],
Expand All @@ -218,7 +264,7 @@ Sanitize.prototype.clean_node = function(container) {
whitelist_nodes: this.whitelist_nodes,
dom: this.dom
});
if (transform == null)
if (transform == null)
continue;
else if(typeof transform == 'object') {
if(transform.whitelist_nodes && transform.whitelist_nodes instanceof Array) {
Expand All @@ -240,19 +286,17 @@ Sanitize.prototype.clean_node = function(container) {
}
return output;
}




for(i=0;i<container.childNodes.length;i++) {
_clean.call(this, container.childNodes[i]);
}

if(fragment.normalize) {
fragment.normalize();
}

return fragment;

};

if ( typeof define === "function" ) {
Expand Down