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

Added URI restriction whitelist #4

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
5 changes: 0 additions & 5 deletions ATTRIBUTIONS.md

This file was deleted.

107 changes: 64 additions & 43 deletions HtmlSanitizer.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,31 @@
//JavaScript HTML Sanitizer, (c) Alexander Yumashev, Jitbit Software.
/*
* JavaScript HTML Sanitizer, (c) Alexander Yumashev, Jitbit Software.
* homepage https://github.com/jitbit/HtmlSanitizer
* License: GNU GPL v3 https://github.com/jitbit/HtmlSanitizer/blob/master/LICENSE
*/

//homepage https://github.com/jitbit/HtmlSanitizer
var HtmlSanitizer = (function () {

//License: GNU GPL v3 https://github.com/jitbit/HtmlSanitizer/blob/master/LICENSE

console.log('Sanitizer loading');

var HtmlSanitizer = new (function () {

var tagWhitelist_ = {
var _tagWhitelist = {
'A': true, 'ABBR': true, 'B': true, 'BLOCKQUOTE': true, 'BODY': true, 'BR': true, 'CENTER': true, 'CODE': true, 'DIV': true, 'EM': true, 'FONT': true,
'H1': true, 'H2': true, 'H3': true, 'H4': true, 'H5': true, 'H6': true, 'HR': true, 'I': true, 'IMG': true, 'LABEL': true, 'LI': true, 'OL': true, 'P': true, 'PRE': true,
'SMALL': true, 'SOURCE': true, 'SPAN': true, 'STRONG': true, 'TABLE': true, 'TBODY': true, 'TR': true, 'TD': true, 'TH': true, 'THEAD': true, 'UL': true, 'U': true, 'VIDEO': true
};
var _contentTagWhiteList = { 'FORM': true }; //tags that will be converted to DIVs
var _attributeWhitelist = { 'align': true, 'color': true, 'controls': true, 'height': true, 'href': true, 'src': true, 'style': true, 'target': true, 'title': true, 'type': true, 'width': true };
var _cssWhitelist = { 'color': true, 'background-color': true, 'font-size': true, 'text-align': true, 'text-decoration': true, 'font-weight': true };
var _schemaWhiteList = [ 'http:', 'https:', 'data:', 'm-files:', 'file:', 'ftp:' ]; //which "protocols" are allowed in "href", "src" etc
var _uriAttributes = { 'href': true, 'action': true, 'src': true };
var _uriContainsWhiteList = [ ];

var contentTagWhiteList_ = { 'FORM': true }; //tags that will be converted to DIVs

var attributeWhitelist_ = { 'align': true, 'color': true, 'controls': true, 'height': true, 'href': true, 'src': true, 'style': true, 'target': true, 'title': true, 'type': true, 'width': true };

var cssWhitelist_ = { 'color': true, 'background-color': true, 'font-size': true, 'text-align': true, 'text-decoration': true, 'font-weight': true };
var SanitizeHtml = function(input) {

var schemaWhiteList_ = [ 'http:', 'https:', 'data:', 'm-files:', 'file:', 'ftp:' ]; //which "protocols" are allowed in "href", "src" etc

var uriAttributes_ = { 'href': true, 'action': true };

this.SanitizeHtml = function(input) {
input = input.trim();
if (input == "") return ""; //to save performance and not create iframe

//firefox "bogus node" workaround
if (input == "<br>") return "";
//to save performance and not create iframe and firefox "bogus node" workaround
if (input == "" || input == "<br>") {
return ""
};

var iframe = document.createElement('iframe');
if (iframe['sandbox'] === undefined) {
Expand All @@ -40,39 +36,47 @@ var HtmlSanitizer = new (function () {
iframe.style.display = 'none';
document.body.appendChild(iframe); // necessary so the iframe contains a document
var iframedoc = iframe.contentDocument || iframe.contentWindow.document;
if (iframedoc.body == null) iframedoc.write("<body></body>"); // null in IE
if (iframedoc.body == null){
iframedoc.write("<body></body>"); // null in IE
}
iframedoc.body.innerHTML = input;

function makeSanitizedCopy(node) {

var newNode;

if (node.nodeType == Node.TEXT_NODE) {
var newNode = node.cloneNode(true);
} else if (node.nodeType == Node.ELEMENT_NODE && (tagWhitelist_[node.tagName] || contentTagWhiteList_[node.tagName])) {
newNode = node.cloneNode(true);
} else if (node.nodeType == Node.ELEMENT_NODE && (_tagWhitelist[node.tagName] || _contentTagWhiteList[node.tagName])) {

//remove useless empty spans (lots of those when pasting from MS Outlook)
if ((node.tagName == "SPAN" || node.tagName == "B" || node.tagName == "I" || node.tagName == "U")
&& node.innerHTML.trim() == "") {
return document.createDocumentFragment();
}

if (contentTagWhiteList_[node.tagName])
if (_contentTagWhiteList[node.tagName]) {
newNode = iframedoc.createElement('DIV'); //convert to DIV
else
} else {
newNode = iframedoc.createElement(node.tagName);
}

for (var i = 0; i < node.attributes.length; i++) {
var attr = node.attributes[i];
if (attributeWhitelist_[attr.name]) {
if (_attributeWhitelist[attr.name]) {
if (attr.name == "style") {
for (s = 0; s < node.style.length; s++) {
var styleName = node.style[s];
if (cssWhitelist_[styleName])
if (_cssWhitelist[styleName]){
newNode.style.setProperty(styleName, node.style.getPropertyValue(styleName));
}
}
}
else {
if (uriAttributes_[attr.name]) { //if this is a "uri" attribute, that can have "javascript:" or something
if (attr.value.indexOf(":") > -1 && !startsWithAny(attr.value, schemaWhiteList_))
if (_uriAttributes[attr.name]) { //if this is a "uri" attribute, that can have "javascript:" or something
if (attr.value.indexOf(":") > -1 && !URIstartsWithAndContains(attr.value)){
continue;
}
}
newNode.setAttribute(attr.name, attr.value);
}
Expand All @@ -86,26 +90,43 @@ var HtmlSanitizer = new (function () {
newNode = document.createDocumentFragment();
}
return newNode;
};
}

var resultElement = makeSanitizedCopy(iframedoc.body);
document.body.removeChild(iframe);
return resultElement.innerHTML
.replace(/<br[^>]*>(\S)/g, "<br>\n$1")
.replace(/div><div/g, "div>\n<div"); //replace is just for cleaner code
}
};

function startsWithAny(str, substrings) {
for (var i = 0; i < substrings.length; i++) {
if (str.indexOf(substrings[i]) == 0) {
return true;
function URIstartsWithAndContains(str) {

var flag = false;

//verify protocols
for (var i = 0; i < _schemaWhiteList.length; i++) {
if (str.indexOf(_schemaWhiteList[i]) == 0) {
flag = true;
}
}
return false;

//verify url partials
for (var k = 0; k < _uriContainsWhiteList.length; k++) {
if (str.indexOf(_uriContainsWhiteList[k]) == -1) {
flag = false;
}
}

return flag;
}

this.AllowedTags = tagWhitelist_;
this.AllowedAttributes = attributeWhitelist_;
this.AllowedCssStyles = cssWhitelist_;
this.AllowedSchemas = schemaWhiteList_;
});
return {
AllowedTags: _tagWhitelist,
AllowedAttributes: _attributeWhitelist,
AllowedCssStyles: _cssWhitelist,
AllowedSchemas: _schemaWhiteList,
AllowedAddresses: _uriContainsWhiteList,
SanitizeHtml: SanitizeHtml
};

})();
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ To add an allowed tag:
HtmlSanitizer.AllowedTags['script'] = true;
```

To allow only specific domains on "uri-attributes"

```javascript
HtmlSanitizer.AllowedAddresses.push('domain.com');
```

## Browser support

Supported by all major browsers, IE10 and higher.
Expand Down
4 changes: 4 additions & 0 deletions tests.html → index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<script src="https://code.jquery.com/qunit/qunit-2.9.1.js"></script>

<script>

//sample to add partial URI's whitlist
//HtmlSanitizer.AllowedAddresses.push('domain.com');

QUnit.test("Html Sanitizer", function( assert ) {
assert.equal(HtmlSanitizer.SanitizeHtml("<div> <script> Alert('xss!'); </scr" + "ipt> </div>"), "<div> </div>");
assert.equal(HtmlSanitizer.SanitizeHtml("<p class='MsoNormal' style='margin-bottom:10.0pt;line-height:115%'><b>Official - SBU&nbsp;</b><o:p></o:p></p>"), "<p><b>Official - SBU&nbsp;</b></p>");
Expand Down