rewrite sanitizeHTML to support global attributes
This commit is contained in:
parent
5f807a3ad4
commit
fee4339d11
3 changed files with 165 additions and 87 deletions
|
@ -28,11 +28,11 @@ Ox.Editable = function(options, self) {
|
||||||
editable: true,
|
editable: true,
|
||||||
editing: false,
|
editing: false,
|
||||||
format: null,
|
format: null,
|
||||||
|
globalAttributes: [],
|
||||||
height: 0,
|
height: 0,
|
||||||
highlight: null,
|
highlight: null,
|
||||||
maxHeight: void 0,
|
maxHeight: void 0,
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
replaceTags: {},
|
|
||||||
submitOnBlur: true,
|
submitOnBlur: true,
|
||||||
tags: null,
|
tags: null,
|
||||||
tooltip: '',
|
tooltip: '',
|
||||||
|
@ -230,7 +230,7 @@ Ox.Editable = function(options, self) {
|
||||||
return (
|
return (
|
||||||
self.options.type == 'input'
|
self.options.type == 'input'
|
||||||
? Ox.encodeHTMLEntities(value)
|
? Ox.encodeHTMLEntities(value)
|
||||||
: Ox.sanitizeHTML(value, self.options.tags, self.options.replaceTags)
|
: Ox.sanitizeHTML(value, self.options.tags, self.options.globalAttributes)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,9 @@ Ox.EditableContent = function(options, self) {
|
||||||
editable: true,
|
editable: true,
|
||||||
editing: false,
|
editing: false,
|
||||||
format: null,
|
format: null,
|
||||||
|
globalAttributes: [],
|
||||||
highlight: null,
|
highlight: null,
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
replaceTags: {},
|
|
||||||
submitOnBlur: true,
|
submitOnBlur: true,
|
||||||
tags: null,
|
tags: null,
|
||||||
tooltip: '',
|
tooltip: '',
|
||||||
|
@ -190,7 +190,7 @@ Ox.EditableContent = function(options, self) {
|
||||||
return (
|
return (
|
||||||
self.options.type == 'input'
|
self.options.type == 'input'
|
||||||
? Ox.encodeHTMLEntities(value)
|
? Ox.encodeHTMLEntities(value)
|
||||||
: Ox.sanitizeHTML(value, self.options.tags, self.options.replaceTags)
|
: Ox.sanitizeHTML(value, self.options.tags, self.options.globalAttributes)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,80 @@
|
||||||
|
|
||||||
var defaultTags = [
|
var defaultTags = [
|
||||||
// inline formatting
|
// inline formatting
|
||||||
'b', 'bdi', 'code', 'em', 'i', 'q', 's', 'span', 'strong', 'sub', 'sup', 'u',
|
{'name': 'b'},
|
||||||
|
{'name': 'bdi'},
|
||||||
|
{'name': 'code'},
|
||||||
|
{'name': 'em'},
|
||||||
|
{'name': 'i'},
|
||||||
|
{'name': 'q'},
|
||||||
|
{'name': 's'},
|
||||||
|
{'name': 'span'},
|
||||||
|
{'name': 'strong'},
|
||||||
|
{'name': 'sub'},
|
||||||
|
{'name': 'sup'},
|
||||||
|
{'name': 'u'},
|
||||||
// block formatting
|
// block formatting
|
||||||
'blockquote', 'cite', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre',
|
{'name': 'blockquote'},
|
||||||
|
{'name': 'cite'},
|
||||||
|
{
|
||||||
|
'name': 'div',
|
||||||
|
'optional': ['style'],
|
||||||
|
'validation': {
|
||||||
|
'style': /^direction: rtl$/
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{'name': 'h1'},
|
||||||
|
{'name': 'h2'},
|
||||||
|
{'name': 'h3'},
|
||||||
|
{'name': 'h4'},
|
||||||
|
{'name': 'h5'},
|
||||||
|
{'name': 'h6'},
|
||||||
|
{'name': 'p'},
|
||||||
|
{'name': 'pre'},
|
||||||
// lists
|
// lists
|
||||||
'li', 'ol', 'ul',
|
{'name': 'li'},
|
||||||
|
{'name': 'ol'},
|
||||||
|
{'name': 'ul'},
|
||||||
// tables
|
// tables
|
||||||
'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr',
|
{'name': 'table'},
|
||||||
|
{'name': 'tbody'},
|
||||||
|
{'name': 'td'},
|
||||||
|
{'name': 'tfoot'},
|
||||||
|
{'name': 'th'},
|
||||||
|
{'name': 'thead'},
|
||||||
|
{'name': 'tr'},
|
||||||
// other
|
// other
|
||||||
'a', 'br', 'figure', 'figcaption', 'iframe', 'img',
|
{'name': '[]'},
|
||||||
// special
|
{
|
||||||
'rtl', '[]'
|
'name': 'a',
|
||||||
|
'required': ['href'],
|
||||||
|
'validation': {
|
||||||
|
'href': /^((https?:\/\/|\/|mailto:).*?)/
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{'name': 'br'},
|
||||||
|
{
|
||||||
|
'name': 'iframe',
|
||||||
|
'optional': ['width', 'height'],
|
||||||
|
'required': ['src'],
|
||||||
|
'validation': {
|
||||||
|
'width': /^\d+$/,
|
||||||
|
'height': /^\d+$/,
|
||||||
|
'src': /^((https?:\/\/|\/|mailto:).*?)/
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'img',
|
||||||
|
'optional': ['width', 'height'],
|
||||||
|
'required': ['src'],
|
||||||
|
'validation': {
|
||||||
|
'width': /^\d+$/,
|
||||||
|
'height': /^\d+$/,
|
||||||
|
'src': /^((https?:\/\/|\/|mailto:).*?)/
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{'name': 'figure'},
|
||||||
|
{'name': 'figcaption'}
|
||||||
],
|
],
|
||||||
htmlEntities = {
|
htmlEntities = {
|
||||||
'"': '"', '&': '&', "'": ''', '<': '<', '>': '>'
|
'"': '"', '&': '&', "'": ''', '<': '<', '>': '>'
|
||||||
|
@ -26,56 +89,6 @@
|
||||||
'a', 'b', 'br', 'code', 'i', 's', 'span', 'u'
|
'a', 'b', 'br', 'code', 'i', 's', 'span', 'u'
|
||||||
].join('|') + ')\\/?>', 'gi')
|
].join('|') + ')\\/?>', 'gi')
|
||||||
},
|
},
|
||||||
replace = {
|
|
||||||
a: [
|
|
||||||
[
|
|
||||||
/<a [^<>]*?href="((\/|https?:\/\/|mailto:).*?)".*?>/gi,
|
|
||||||
'<a href="{1}">',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
/<\/a>/gi,
|
|
||||||
'</a>'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
iframe: [
|
|
||||||
[
|
|
||||||
/<iframe [^<>]*?width="(\d+)" height="(\d+)"[^<>]*?src="((\/|https?:\/\/).+?)".*?>/gi,
|
|
||||||
'<iframe width="{1}" height="{2}" src="{3}">'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
/<iframe [^<>]*?src="((\/|https?:\/\/).+?)".*?>/gi,
|
|
||||||
'<iframe src="{1}">'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
/<\/iframe>/gi,
|
|
||||||
'</iframe>'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
img: [
|
|
||||||
[
|
|
||||||
/<img [^<>]*?src="((\/|https?:\/\/).+?)".*?>/gi,
|
|
||||||
'<img src="{1}">'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
rtl: [
|
|
||||||
[
|
|
||||||
/<rtl>/gi,
|
|
||||||
'<div style="direction: rtl">'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
/<\/rtl>/gi,
|
|
||||||
'</div>'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'*': function(tag) {
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
new RegExp('</?' + tag + ' ?/?>', 'gi'),
|
|
||||||
'{0}'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
salt = Ox.range(2).map(function(){
|
salt = Ox.range(2).map(function(){
|
||||||
return Ox.range(16).map(function() {
|
return Ox.range(16).map(function() {
|
||||||
return Ox.char(65 + Ox.random(26));
|
return Ox.char(65 + Ox.random(26));
|
||||||
|
@ -491,16 +504,16 @@
|
||||||
> Ox.sanitizeHTML('<a href="http://foo.com" onclick="alert()">foo</a>')
|
> Ox.sanitizeHTML('<a href="http://foo.com" onclick="alert()">foo</a>')
|
||||||
'<a href="http://foo.com">foo</a>'
|
'<a href="http://foo.com">foo</a>'
|
||||||
> Ox.sanitizeHTML('<a href="javascript:alert()">foo</a>')
|
> Ox.sanitizeHTML('<a href="javascript:alert()">foo</a>')
|
||||||
'<a href="javascript:alert()">foo'
|
'<a href="javascript:alert()">foo</a>'
|
||||||
> Ox.sanitizeHTML('<a href="foo">foo</a>')
|
> Ox.sanitizeHTML('<a href="foo">foo</a>')
|
||||||
'<a href="foo">foo'
|
'<a href="foo">foo</a>'
|
||||||
> Ox.sanitizeHTML('<a href="/foo">foo</a>')
|
> Ox.sanitizeHTML('<a href="/foo">foo</a>')
|
||||||
'<a href="/foo">foo</a>'
|
'<a href="/foo">foo</a>'
|
||||||
> Ox.sanitizeHTML('<a href="/">foo</a>')
|
> Ox.sanitizeHTML('<a href="/">foo</a>')
|
||||||
'<a href="/">foo</a>'
|
'<a href="/">foo</a>'
|
||||||
> Ox.sanitizeHTML('[http://foo.com foo]')
|
> Ox.sanitizeHTML('[http://foo.com foo]')
|
||||||
'<a href="http://foo.com">foo</a>'
|
'<a href="http://foo.com">foo</a>'
|
||||||
> Ox.sanitizeHTML('<rtl>foo</rtl>')
|
> Ox.sanitizeHTML('<div style="direction: rtl">foo</div>')
|
||||||
'<div style="direction: rtl">foo</div>'
|
'<div style="direction: rtl">foo</div>'
|
||||||
> Ox.sanitizeHTML('<script>alert()</script>')
|
> Ox.sanitizeHTML('<script>alert()</script>')
|
||||||
'<script>alert()</script>'
|
'<script>alert()</script>'
|
||||||
|
@ -514,40 +527,105 @@
|
||||||
'&&'
|
'&&'
|
||||||
> Ox.sanitizeHTML('<http://foo.com>')
|
> Ox.sanitizeHTML('<http://foo.com>')
|
||||||
'<<a href="http://foo.com">http://foo.com</a>>'
|
'<<a href="http://foo.com">http://foo.com</a>>'
|
||||||
|
> Ox.sanitizeHTML('<foo value="http://foo.com"></foo>')
|
||||||
|
'"<foo value="http://foo.com"></foo>"'
|
||||||
@*/
|
@*/
|
||||||
Ox.sanitizeHTML = function(html, tags, replaceTags) {
|
Ox.sanitizeHTML = function(html, tags, globalAttributes) {
|
||||||
var matches = [];
|
|
||||||
tags = tags || defaultTags;
|
tags = tags || defaultTags;
|
||||||
replaceTags = replaceTags || {};
|
globalAttributes = globalAttributes || [];
|
||||||
|
var escaped = {},
|
||||||
|
level = 0,
|
||||||
|
matches = [],
|
||||||
|
nonClosingTags = ['img', 'br'],
|
||||||
|
validAttributes = {}, requiredAttributes = {}, validation = {},
|
||||||
|
validTags = tags.map(function(tag) {
|
||||||
|
validAttributes[tag.name] = globalAttributes
|
||||||
|
.concat(tag.required || [])
|
||||||
|
.concat(tag.optional || []);
|
||||||
|
requiredAttributes[tag.name] = tag.required || [];
|
||||||
|
validation[tag.name] = tag.validation || {};
|
||||||
|
return tag.name;
|
||||||
|
});
|
||||||
|
|
||||||
// html = Ox.clean(html); fixme: can this be a parameter?
|
// html = Ox.clean(html); fixme: can this be a parameter?
|
||||||
if (tags.indexOf('[]') > -1) {
|
if (validTags.indexOf('[]') > -1) {
|
||||||
html = html.replace(
|
html = html.replace(
|
||||||
/\[((\/|https?:\/\/|mailto:).+?) (.+?)\]/gi,
|
/\[((\/|https?:\/\/|mailto:).+?) (.+?)\]/gi,
|
||||||
'<a href="$1">$3</a>'
|
'<a href="$1">$3</a>'
|
||||||
);
|
);
|
||||||
tags = tags.filter(function(tag) {
|
validTags = validTags.filter(function(tag) {
|
||||||
return tag != '[]';
|
return tag != '[]';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
tags.forEach(function(tag) {
|
|
||||||
var array = replaceTags[tag] || replace[tag] || replace['*'](tag);
|
html = splitHTMLTags(html).map(function(string, i) {
|
||||||
Ox.forEach(array, function(value) {
|
var attributes,
|
||||||
html = html.replace(value[0], function() {
|
attrs = {},
|
||||||
var match;
|
attrRegexp = /([^=\ ]+)="([^"]+)"/g,
|
||||||
if (Ox.isFunction(value[1])) {
|
isClosing,
|
||||||
match = value[1].apply(null, arguments);
|
isTag = i % 2,
|
||||||
} else {
|
isValid = true,
|
||||||
match = Ox.formatString(value[1], arguments);
|
name,
|
||||||
|
match,
|
||||||
|
tag,
|
||||||
|
tagRegexp = /<(\/)?([^\ \/]+)(.*?)(\/)?>/g;
|
||||||
|
|
||||||
|
if (isTag) {
|
||||||
|
tag = tagRegexp.exec(string);
|
||||||
|
if (tag) {
|
||||||
|
isClosing = !Ox.isUndefined(tag[1]);
|
||||||
|
name = tag[2];
|
||||||
|
attributes = tag[3].trim();
|
||||||
|
while(match = attrRegexp.exec(attributes)) {
|
||||||
|
if (validAttributes[name] && validAttributes[name].indexOf(match[1]) > -1) {
|
||||||
|
attrs[match[1]] = match[2];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
matches.push(match);
|
if (!isClosing && nonClosingTags.indexOf(name) == -1) {
|
||||||
return salt.join(matches.length - 1);
|
level++;
|
||||||
});
|
}
|
||||||
});
|
if (Ox.isEmpty(attrs) && attributes.length || validTags.indexOf(name) == -1) {
|
||||||
});
|
isValid = false;
|
||||||
html = Ox.encodeHTMLEntities(Ox.decodeHTMLEntities(html));
|
} else if(!isClosing && requiredAttributes[name]) {
|
||||||
matches.forEach(function(match, i) {
|
requiredAttributes[name].forEach(function(attr) {
|
||||||
html = html.replace(new RegExp(salt.join(i)), match);
|
if (Ox.isUndefined(attrs[attr])) {
|
||||||
});
|
isValid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isValid && !Ox.isEmpty(attrs)) {
|
||||||
|
Ox.forEach(attrs, function(value, key) {
|
||||||
|
if (!Ox.isUndefined(validation[name][key])
|
||||||
|
&& !validation[name][key].exec(value)) {
|
||||||
|
isValid = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isValid && isClosing) {
|
||||||
|
isValid = !escaped[level];
|
||||||
|
} else {
|
||||||
|
escaped[level] = !isValid;
|
||||||
|
}
|
||||||
|
if (isClosing) {
|
||||||
|
level --;
|
||||||
|
}
|
||||||
|
if (isValid) {
|
||||||
|
return '<'
|
||||||
|
+ (isClosing ? '/' : '')
|
||||||
|
+ name
|
||||||
|
+ (!isClosing && !Ox.isEmpty(attrs)
|
||||||
|
? ' ' + Ox.values(Ox.map(attrs, function(value, key) {
|
||||||
|
return key + '="' + value + '"';
|
||||||
|
})).join(' ')
|
||||||
|
: '')
|
||||||
|
+ '>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ox.encodeHTMLEntities(Ox.decodeHTMLEntities(string));
|
||||||
|
}).join('');
|
||||||
|
//FIXME: dont add links to urls inside of escaped tags
|
||||||
html = Ox.addLinks(html, true);
|
html = Ox.addLinks(html, true);
|
||||||
html = html.replace(/\n\n/g, '<br/><br/>');
|
html = html.replace(/\n\n/g, '<br/><br/>');
|
||||||
// Close extra opening and remove extra closing tags.
|
// Close extra opening and remove extra closing tags.
|
||||||
|
|
Loading…
Add table
Reference in a new issue