mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 17:46:05 -05:00
Upgraded and refactored Sanitizing. Much less crap should get through now!
Conflicts: app/assets/javascripts/discourse/components/syntax_highlighting.js
This commit is contained in:
parent
e0e79cae73
commit
5281b7f80c
16 changed files with 175 additions and 174 deletions
|
@ -11,11 +11,6 @@
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
|
||||||
// Sam: made some modifications to pass jshint and protect against global namespace pollution
|
|
||||||
|
|
||||||
|
|
||||||
window.sanitizeHtml = (function() {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
|
@ -88,7 +83,7 @@ function encodeIfExists(unescapedPart) {
|
||||||
return encodeURIComponent(unescapedPart);
|
return encodeURIComponent(unescapedPart);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
};
|
||||||
/**
|
/**
|
||||||
* if unescapedPart is non null, then escapes any characters in it that aren't
|
* if unescapedPart is non null, then escapes any characters in it that aren't
|
||||||
* valid characters in a url and also escapes any special characters that
|
* valid characters in a url and also escapes any special characters that
|
||||||
|
@ -159,7 +154,7 @@ var EXTRA_PARENT_PATHS_RE = /^(?:\.\.\/)*(?:\.\.$)?/;
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
function collapse_dots(path) {
|
function collapse_dots(path) {
|
||||||
if (path == null) { return null; }
|
if (path === null) { return null; }
|
||||||
var p = normPath(path);
|
var p = normPath(path);
|
||||||
// Only /../ left to flatten
|
// Only /../ left to flatten
|
||||||
var r = PARENT_DIRECTORY_HANDLER_RE;
|
var r = PARENT_DIRECTORY_HANDLER_RE;
|
||||||
|
@ -743,9 +738,14 @@ URI.utils = {
|
||||||
return URI;
|
return URI;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// Exports for closure compiler.
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window['URI'] = URI;
|
||||||
|
}
|
||||||
|
;
|
||||||
// Copyright Google Inc.
|
// Copyright Google Inc.
|
||||||
// Licensed under the Apache Licence Version 2.0
|
// Licensed under the Apache Licence Version 2.0
|
||||||
// Autogenerated at Wed Feb 20 13:32:22 EST 2013
|
// Autogenerated at Fri Oct 11 16:16:32 EDT 2013
|
||||||
// @overrides window
|
// @overrides window
|
||||||
// @provides html4
|
// @provides html4
|
||||||
var html4 = {};
|
var html4 = {};
|
||||||
|
@ -765,6 +765,7 @@ html4.atype = {
|
||||||
'FRAME_TARGET': 10,
|
'FRAME_TARGET': 10,
|
||||||
'MEDIA_QUERY': 13
|
'MEDIA_QUERY': 13
|
||||||
};
|
};
|
||||||
|
html4[ 'atype' ] = html4.atype;
|
||||||
html4.ATTRIBS = {
|
html4.ATTRIBS = {
|
||||||
'*::class': 9,
|
'*::class': 9,
|
||||||
'*::dir': 0,
|
'*::dir': 0,
|
||||||
|
@ -780,6 +781,7 @@ html4.ATTRIBS = {
|
||||||
'*::onchange': 2,
|
'*::onchange': 2,
|
||||||
'*::onclick': 2,
|
'*::onclick': 2,
|
||||||
'*::ondblclick': 2,
|
'*::ondblclick': 2,
|
||||||
|
'*::onerror': 2,
|
||||||
'*::onfocus': 2,
|
'*::onfocus': 2,
|
||||||
'*::onkeydown': 2,
|
'*::onkeydown': 2,
|
||||||
'*::onkeypress': 2,
|
'*::onkeypress': 2,
|
||||||
|
@ -825,6 +827,7 @@ html4.ATTRIBS = {
|
||||||
'audio::mediagroup': 5,
|
'audio::mediagroup': 5,
|
||||||
'audio::muted': 0,
|
'audio::muted': 0,
|
||||||
'audio::preload': 0,
|
'audio::preload': 0,
|
||||||
|
'audio::src': 1,
|
||||||
'bdo::dir': 0,
|
'bdo::dir': 0,
|
||||||
'blockquote::cite': 1,
|
'blockquote::cite': 1,
|
||||||
'br::clear': 0,
|
'br::clear': 0,
|
||||||
|
@ -1066,8 +1069,10 @@ html4.ATTRIBS = {
|
||||||
'video::muted': 0,
|
'video::muted': 0,
|
||||||
'video::poster': 1,
|
'video::poster': 1,
|
||||||
'video::preload': 0,
|
'video::preload': 0,
|
||||||
|
'video::src': 1,
|
||||||
'video::width': 0
|
'video::width': 0
|
||||||
};
|
};
|
||||||
|
html4[ 'ATTRIBS' ] = html4.ATTRIBS;
|
||||||
html4.eflags = {
|
html4.eflags = {
|
||||||
'OPTIONAL_ENDTAG': 1,
|
'OPTIONAL_ENDTAG': 1,
|
||||||
'EMPTY': 2,
|
'EMPTY': 2,
|
||||||
|
@ -1079,6 +1084,7 @@ html4.eflags = {
|
||||||
'STYLE': 128,
|
'STYLE': 128,
|
||||||
'VIRTUALIZED': 256
|
'VIRTUALIZED': 256
|
||||||
};
|
};
|
||||||
|
html4[ 'eflags' ] = html4.eflags;
|
||||||
html4.ELEMENTS = {
|
html4.ELEMENTS = {
|
||||||
'a': 0,
|
'a': 0,
|
||||||
'abbr': 0,
|
'abbr': 0,
|
||||||
|
@ -1202,6 +1208,7 @@ html4.ELEMENTS = {
|
||||||
'video': 0,
|
'video': 0,
|
||||||
'wbr': 2
|
'wbr': 2
|
||||||
};
|
};
|
||||||
|
html4[ 'ELEMENTS' ] = html4.ELEMENTS;
|
||||||
html4.ELEMENT_DOM_INTERFACES = {
|
html4.ELEMENT_DOM_INTERFACES = {
|
||||||
'a': 'HTMLAnchorElement',
|
'a': 'HTMLAnchorElement',
|
||||||
'abbr': 'HTMLElement',
|
'abbr': 'HTMLElement',
|
||||||
|
@ -1325,14 +1332,17 @@ html4.ELEMENT_DOM_INTERFACES = {
|
||||||
'video': 'HTMLVideoElement',
|
'video': 'HTMLVideoElement',
|
||||||
'wbr': 'HTMLElement'
|
'wbr': 'HTMLElement'
|
||||||
};
|
};
|
||||||
|
html4[ 'ELEMENT_DOM_INTERFACES' ] = html4.ELEMENT_DOM_INTERFACES;
|
||||||
html4.ueffects = {
|
html4.ueffects = {
|
||||||
'NOT_LOADED': 0,
|
'NOT_LOADED': 0,
|
||||||
'SAME_DOCUMENT': 1,
|
'SAME_DOCUMENT': 1,
|
||||||
'NEW_DOCUMENT': 2
|
'NEW_DOCUMENT': 2
|
||||||
};
|
};
|
||||||
|
html4[ 'ueffects' ] = html4.ueffects;
|
||||||
html4.URIEFFECTS = {
|
html4.URIEFFECTS = {
|
||||||
'a::href': 2,
|
'a::href': 2,
|
||||||
'area::href': 2,
|
'area::href': 2,
|
||||||
|
'audio::src': 1,
|
||||||
'blockquote::cite': 0,
|
'blockquote::cite': 0,
|
||||||
'command::icon': 1,
|
'command::icon': 1,
|
||||||
'del::cite': 0,
|
'del::cite': 0,
|
||||||
|
@ -1341,16 +1351,20 @@ html4.URIEFFECTS = {
|
||||||
'input::src': 1,
|
'input::src': 1,
|
||||||
'ins::cite': 0,
|
'ins::cite': 0,
|
||||||
'q::cite': 0,
|
'q::cite': 0,
|
||||||
'video::poster': 1
|
'video::poster': 1,
|
||||||
|
'video::src': 1
|
||||||
};
|
};
|
||||||
|
html4[ 'URIEFFECTS' ] = html4.URIEFFECTS;
|
||||||
html4.ltypes = {
|
html4.ltypes = {
|
||||||
'UNSANDBOXED': 2,
|
'UNSANDBOXED': 2,
|
||||||
'SANDBOXED': 1,
|
'SANDBOXED': 1,
|
||||||
'DATA': 0
|
'DATA': 0
|
||||||
};
|
};
|
||||||
|
html4[ 'ltypes' ] = html4.ltypes;
|
||||||
html4.LOADERTYPES = {
|
html4.LOADERTYPES = {
|
||||||
'a::href': 2,
|
'a::href': 2,
|
||||||
'area::href': 2,
|
'area::href': 2,
|
||||||
|
'audio::src': 2,
|
||||||
'blockquote::cite': 2,
|
'blockquote::cite': 2,
|
||||||
'command::icon': 1,
|
'command::icon': 1,
|
||||||
'del::cite': 2,
|
'del::cite': 2,
|
||||||
|
@ -1359,8 +1373,15 @@ html4.LOADERTYPES = {
|
||||||
'input::src': 1,
|
'input::src': 1,
|
||||||
'ins::cite': 2,
|
'ins::cite': 2,
|
||||||
'q::cite': 2,
|
'q::cite': 2,
|
||||||
'video::poster': 1
|
'video::poster': 1,
|
||||||
|
'video::src': 2
|
||||||
};
|
};
|
||||||
|
html4[ 'LOADERTYPES' ] = html4.LOADERTYPES;
|
||||||
|
// export for Closure Compiler
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window['html4'] = html4;
|
||||||
|
}
|
||||||
|
;
|
||||||
// Copyright (C) 2006 Google Inc.
|
// Copyright (C) 2006 Google Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -1397,7 +1418,7 @@ html4.LOADERTYPES = {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// The Turkish i seems to be a non-issue, but abort in case it is.
|
// The Turkish i seems to be a non-issue, but abort in case it is.
|
||||||
// if ('I'.toLowerCase() !== 'i') { throw 'I/i problem'; } # Sam ... screwing up in turkish browsers seems a silly idea
|
if ('I'.toLowerCase() !== 'i') { throw 'I/i problem'; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \@namespace
|
* \@namespace
|
||||||
|
@ -1407,9 +1428,9 @@ var html = (function(html4) {
|
||||||
// For closure compiler
|
// For closure compiler
|
||||||
var parseCssDeclarations, sanitizeCssProperty, cssSchema;
|
var parseCssDeclarations, sanitizeCssProperty, cssSchema;
|
||||||
if ('undefined' !== typeof window) {
|
if ('undefined' !== typeof window) {
|
||||||
parseCssDeclarations = window.parseCssDeclarations;
|
parseCssDeclarations = window['parseCssDeclarations'];
|
||||||
sanitizeCssProperty = window.sanitizeCssProperty;
|
sanitizeCssProperty = window['sanitizeCssProperty'];
|
||||||
cssSchema = window.cssSchema;
|
cssSchema = window['cssSchema'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// The keys of this object must be 'quoted' or JSCompiler will mangle them!
|
// The keys of this object must be 'quoted' or JSCompiler will mangle them!
|
||||||
|
@ -1439,7 +1460,8 @@ var html = (function(html4) {
|
||||||
// TODO(kpreid): This retrieval is a kludge and leads to silent loss of
|
// TODO(kpreid): This retrieval is a kludge and leads to silent loss of
|
||||||
// functionality if the document isn't available.
|
// functionality if the document isn't available.
|
||||||
var entityLookupElement =
|
var entityLookupElement =
|
||||||
('undefined' !== typeof window && window.document) ? window.document.createElement('textarea') : null;
|
('undefined' !== typeof window && window['document'])
|
||||||
|
? window['document'].createElement('textarea') : null;
|
||||||
/**
|
/**
|
||||||
* Decodes an HTML entity.
|
* Decodes an HTML entity.
|
||||||
*
|
*
|
||||||
|
@ -1608,7 +1630,7 @@ var html = (function(html4) {
|
||||||
var splitWillCapture = ('a,b'.split(/(,)/).length === 3);
|
var splitWillCapture = ('a,b'.split(/(,)/).length === 3);
|
||||||
|
|
||||||
// bitmask for tags with special parsing, like <script> and <textarea>
|
// bitmask for tags with special parsing, like <script> and <textarea>
|
||||||
var EFLAGS_TEXT = html4.eflags.CDATA | html4.eflags.RCDATA;
|
var EFLAGS_TEXT = html4.eflags['CDATA'] | html4.eflags['RCDATA'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a SAX-like event handler, produce a function that feeds those
|
* Given a SAX-like event handler, produce a function that feeds those
|
||||||
|
@ -1714,7 +1736,7 @@ var html = (function(html4) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '<\/':
|
case '<\/':
|
||||||
if (m = /^([-\w:]+)[^\'\"]*/.exec(next)) {
|
if ((m = /^([-\w:]+)[^\'\"]*/.exec(next))) {
|
||||||
if (m[0].length === next.length && parts[pos + 1] === '>') {
|
if (m[0].length === next.length && parts[pos + 1] === '>') {
|
||||||
// fast case, no attribute parsing needed
|
// fast case, no attribute parsing needed
|
||||||
pos += 2;
|
pos += 2;
|
||||||
|
@ -1875,7 +1897,7 @@ var html = (function(html4) {
|
||||||
var parts = [];
|
var parts = [];
|
||||||
var lastPos = 0;
|
var lastPos = 0;
|
||||||
var m;
|
var m;
|
||||||
while ((m = re.exec(str)) != null) {
|
while ((m = re.exec(str)) !== null) {
|
||||||
parts.push(str.substring(lastPos, m.index));
|
parts.push(str.substring(lastPos, m.index));
|
||||||
parts.push(m[0]);
|
parts.push(m[0]);
|
||||||
lastPos = m.index + m[0].length;
|
lastPos = m.index + m[0].length;
|
||||||
|
@ -2085,7 +2107,7 @@ var html = (function(html4) {
|
||||||
for (var i = 0, n = attribs.length; i < n; i += 2) {
|
for (var i = 0, n = attribs.length; i < n; i += 2) {
|
||||||
var attribName = attribs[i],
|
var attribName = attribs[i],
|
||||||
value = attribs[i + 1];
|
value = attribs[i + 1];
|
||||||
if (value != null && value !== void 0) {
|
if (value !== null && value !== void 0) {
|
||||||
out.push(' ', attribName, '="', escapeAttrib(value), '"');
|
out.push(' ', attribName, '="', escapeAttrib(value), '"');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2241,7 +2263,7 @@ var html = (function(html4) {
|
||||||
html4.ATTRIBS.hasOwnProperty(attribKey))) {
|
html4.ATTRIBS.hasOwnProperty(attribKey))) {
|
||||||
atype = html4.ATTRIBS[attribKey];
|
atype = html4.ATTRIBS[attribKey];
|
||||||
}
|
}
|
||||||
if (atype != null) {
|
if (atype !== null) {
|
||||||
switch (atype) {
|
switch (atype) {
|
||||||
case html4.atype['NONE']: break;
|
case html4.atype['NONE']: break;
|
||||||
case html4.atype['SCRIPT']:
|
case html4.atype['SCRIPT']:
|
||||||
|
@ -2262,14 +2284,10 @@ var html = (function(html4) {
|
||||||
parseCssDeclarations(
|
parseCssDeclarations(
|
||||||
value,
|
value,
|
||||||
{
|
{
|
||||||
declaration: function (property, tokens) {
|
'declaration': function (property, tokens) {
|
||||||
var normProp = property.toLowerCase();
|
var normProp = property.toLowerCase();
|
||||||
var schema = cssSchema[normProp];
|
|
||||||
if (!schema) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sanitizeCssProperty(
|
sanitizeCssProperty(
|
||||||
normProp, schema, tokens,
|
normProp, tokens,
|
||||||
opt_naiveUriRewriter
|
opt_naiveUriRewriter
|
||||||
? function (url) {
|
? function (url) {
|
||||||
return safeUri(
|
return safeUri(
|
||||||
|
@ -2281,7 +2299,10 @@ var html = (function(html4) {
|
||||||
}, opt_naiveUriRewriter);
|
}, opt_naiveUriRewriter);
|
||||||
}
|
}
|
||||||
: null);
|
: null);
|
||||||
sanitizedDeclarations.push(property + ': ' + tokens.join(' '));
|
if (tokens.length) {
|
||||||
|
sanitizedDeclarations.push(
|
||||||
|
normProp + ': ' + tokens.join(' '));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
value = sanitizedDeclarations.length > 0 ?
|
value = sanitizedDeclarations.length > 0 ?
|
||||||
|
@ -2318,7 +2339,7 @@ var html = (function(html4) {
|
||||||
if (value && '#' === value.charAt(0)) {
|
if (value && '#' === value.charAt(0)) {
|
||||||
value = value.substring(1); // remove the leading '#'
|
value = value.substring(1); // remove the leading '#'
|
||||||
value = opt_nmTokenPolicy ? opt_nmTokenPolicy(value) : value;
|
value = opt_nmTokenPolicy ? opt_nmTokenPolicy(value) : value;
|
||||||
if (value != null && value !== void 0) {
|
if (value !== null && value !== void 0) {
|
||||||
value = '#' + value; // restore the leading '#'
|
value = '#' + value; // restore the leading '#'
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -2336,9 +2357,11 @@ var html = (function(html4) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
value = null;
|
if (!/^data\-/.test(attribName)) {
|
||||||
if (opt_logger) {
|
value = null;
|
||||||
log(opt_logger, tagName, attribName, oldValue, value);
|
if (opt_logger) {
|
||||||
|
log(opt_logger, tagName, attribName, oldValue, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
attribs[i + 1] = value;
|
attribs[i + 1] = value;
|
||||||
|
@ -2418,7 +2441,11 @@ var html = (function(html4) {
|
||||||
return html;
|
return html;
|
||||||
})(html4);
|
})(html4);
|
||||||
|
|
||||||
|
var html_sanitize = html['sanitize'];
|
||||||
|
|
||||||
return function(s){return html.sanitize(s, function(uri){return uri;})};
|
// Exports for Closure compiler. Note this file is also cajoled
|
||||||
})();
|
// for domado and run in an environment without 'window'
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window['html'] = html;
|
||||||
|
window['html_sanitize'] = html_sanitize;
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,23 @@
|
||||||
**/
|
**/
|
||||||
Discourse.Markdown = {
|
Discourse.Markdown = {
|
||||||
|
|
||||||
|
validClasses: {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Whitelists classes for sanitization
|
||||||
|
|
||||||
|
@method whiteListClass
|
||||||
|
@param {String} val The value to whitelist. Can supply more than one argument
|
||||||
|
**/
|
||||||
|
whiteListClass: function() {
|
||||||
|
var args = Array.prototype.slice.call(arguments),
|
||||||
|
validClasses = Discourse.Markdown.validClasses;
|
||||||
|
|
||||||
|
args.forEach(function (a) {
|
||||||
|
validClasses[a] = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convert a raw string to a cooked markdown string.
|
Convert a raw string to a cooked markdown string.
|
||||||
|
|
||||||
|
@ -85,6 +102,41 @@ Discourse.Markdown = {
|
||||||
return new Markdown.Editor(markdownConverter, undefined, editorOptions);
|
return new Markdown.Editor(markdownConverter, undefined, editorOptions);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks to see if a URL is allowed in the cooked content
|
||||||
|
|
||||||
|
@method urlAllowed
|
||||||
|
@param {String} url Url to check
|
||||||
|
@return {String} url to insert in the cooked content
|
||||||
|
**/
|
||||||
|
urlAllowed: function (url) {
|
||||||
|
if(/^https?:\/\//.test(url)) { return url; }
|
||||||
|
if(/^\/\/?[\w\.\-]+/.test(url)) { return url; }
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks to see if a name, class or id is allowed in the cooked content
|
||||||
|
|
||||||
|
@method nameIdClassAllowed
|
||||||
|
@param {String} val The name, class or id to check
|
||||||
|
@return {String} val the transformed name class or id
|
||||||
|
**/
|
||||||
|
nameIdClassAllowed: function(val) {
|
||||||
|
if (Discourse.Markdown.validClasses[val]) { return val; }
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sanitize text using the sanitizer
|
||||||
|
|
||||||
|
@method sanitize
|
||||||
|
@param {String} text The text to sanitize
|
||||||
|
@return {String} text The sanitized text
|
||||||
|
**/
|
||||||
|
sanitize: function(text) {
|
||||||
|
if (!window.html_sanitize) return "";
|
||||||
|
return window.html_sanitize(text, Discourse.Markdown.urlAllowed, Discourse.Markdown.nameIdClassAllowed);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates a Markdown.Converter that we we can use for formatting
|
Creates a Markdown.Converter that we we can use for formatting
|
||||||
|
|
||||||
|
@ -101,8 +153,7 @@ Discourse.Markdown = {
|
||||||
if (!text) return "";
|
if (!text) return "";
|
||||||
|
|
||||||
if (opts.sanitize) {
|
if (opts.sanitize) {
|
||||||
if (!window.sanitizeHtml) return "";
|
text = Discourse.Markdown.sanitize(text);
|
||||||
text = window.sanitizeHtml(text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
@ -112,3 +163,5 @@ Discourse.Markdown = {
|
||||||
|
|
||||||
};
|
};
|
||||||
RSVP.EventTarget.mixin(Discourse.Markdown);
|
RSVP.EventTarget.mixin(Discourse.Markdown);
|
||||||
|
|
||||||
|
Discourse.Markdown.whiteListClass("attachment");
|
||||||
|
|
|
@ -16,12 +16,10 @@ Discourse.SyntaxHighlighting = {
|
||||||
@param {jQuery.selector} $elem The element we want to apply our highlighting to
|
@param {jQuery.selector} $elem The element we want to apply our highlighting to
|
||||||
**/
|
**/
|
||||||
apply: function($elem) {
|
apply: function($elem) {
|
||||||
return $('pre code[class]', $elem).each(function(i, e) {
|
$('pre code[class]', $elem).each(function(i, e) {
|
||||||
return $LAB.script("/javascripts/highlight-handlebars.pack.js").wait(function() {
|
return $LAB.script("/javascripts/highlight-handlebars.pack.js").wait(function() {
|
||||||
return hljs.highlightBlock(e);
|
return hljs.highlightBlock(e);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -93,15 +93,6 @@ replaceBBCodeParams("size", function(param, contents) {
|
||||||
return ['span', {'class': "bbcode-size-" + param}].concat(contents);
|
return ['span', {'class': "bbcode-size-" + param}].concat(contents);
|
||||||
});
|
});
|
||||||
|
|
||||||
replaceBBCodeParams("color", function(param, contents) {
|
|
||||||
// Only allow valid HTML colors.
|
|
||||||
if (/^(\#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)|(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow)$/.test(param)) {
|
|
||||||
return ['span', {style: "color: " + param}].concat(contents);
|
|
||||||
} else {
|
|
||||||
return ['span'].concat(contents);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handles `[code] ... [/code]` blocks
|
// Handles `[code] ... [/code]` blocks
|
||||||
Discourse.Dialect.replaceBlock({
|
Discourse.Dialect.replaceBlock({
|
||||||
start: /(\[code\])([\s\S]*)/igm,
|
start: /(\[code\])([\s\S]*)/igm,
|
||||||
|
@ -112,3 +103,8 @@ Discourse.Dialect.replaceBlock({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Discourse.Markdown.whiteListClass("bbcode-b", "bbcode-i", "bbcode-u", "bbcode-s", "spoiler");
|
||||||
|
|
||||||
|
for(var i=1; i<=40; i++) {
|
||||||
|
Discourse.Markdown.whiteListClass("bbcode-size-" + i);
|
||||||
|
}
|
|
@ -18,3 +18,12 @@ Discourse.Dialect.replaceBlock({
|
||||||
Discourse.Dialect.postProcessTag('code', function (contents) {
|
Discourse.Dialect.postProcessTag('code', function (contents) {
|
||||||
return Handlebars.Utils.escapeExpression(contents);
|
return Handlebars.Utils.escapeExpression(contents);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Discourse.Markdown.whiteListClass(
|
||||||
|
"lang-auto", "1c", "actionscript", "apache", "applescript", "avrasm", "axapta", "bash", "brainfuck",
|
||||||
|
"clojure", "cmake", "coffeescript", "cpp", "cs", "css", "d", "delphi", "diff", "xml", "django", "dos",
|
||||||
|
"erlang-repl", "erlang", "glsl", "go", "handlebars", "haskell", "http", "ini", "java", "javascript",
|
||||||
|
"json", "lisp", "lua", "markdown", "matlab", "mel", "nginx", "objectivec", "parser3", "perl", "php",
|
||||||
|
"profile", "python", "r", "rib", "rsl", "ruby", "rust", "scala", "smalltalk", "sql", "tex", "text",
|
||||||
|
"vala", "vbscript", "vhdl"
|
||||||
|
);
|
||||||
|
|
|
@ -18,4 +18,6 @@ Discourse.Dialect.inlineRegexp({
|
||||||
return ['span', {'class': 'mention'}, username];
|
return ['span', {'class': 'mention'}, username];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Discourse.Markdown.whiteListClass('mention');
|
|
@ -75,4 +75,6 @@ Discourse.Dialect.on("parseNode", function(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Discourse.Markdown.whiteListClass("onebox", "onebox-result", "onebox-result-body", "source", "clearfix", "thumbnail", "info");
|
|
@ -67,4 +67,6 @@ Discourse.Dialect.on("parseNode", function(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Discourse.Markdown.whiteListClass("quote", "title", "quote-controls", "avatar");
|
|
@ -4,7 +4,6 @@
|
||||||
//= require LAB.js
|
//= require LAB.js
|
||||||
//= require Markdown.Converter.js
|
//= require Markdown.Converter.js
|
||||||
//= require Markdown.Editor.js
|
//= require Markdown.Editor.js
|
||||||
//= require Markdown.Sanitizer.js
|
|
||||||
//= require better_markdown.js
|
//= require better_markdown.js
|
||||||
//= require bootbox.js
|
//= require bootbox.js
|
||||||
//= require bootstrap-alert.js
|
//= require bootstrap-alert.js
|
||||||
|
@ -43,6 +42,7 @@
|
||||||
// Stuff we need to load first
|
// Stuff we need to load first
|
||||||
//= require ./discourse/mixins/scrolling
|
//= require ./discourse/mixins/scrolling
|
||||||
//= require_tree ./discourse/mixins
|
//= require_tree ./discourse/mixins
|
||||||
|
//= require ./discourse/components/markdown
|
||||||
//= require ./discourse/components/computed
|
//= require ./discourse/components/computed
|
||||||
//= require ./discourse/views/view
|
//= require ./discourse/views/view
|
||||||
//= require ./discourse/views/container_view
|
//= require ./discourse/views/container_view
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
require 'v8'
|
require 'v8'
|
||||||
require 'nokogiri'
|
require 'nokogiri'
|
||||||
require_dependency 'excerpt_parser'
|
require_dependency 'excerpt_parser'
|
||||||
|
require 'post'
|
||||||
|
|
||||||
module PrettyText
|
module PrettyText
|
||||||
|
|
||||||
|
@ -108,6 +109,7 @@ module PrettyText
|
||||||
|
|
||||||
ctx_load(ctx,
|
ctx_load(ctx,
|
||||||
"vendor/assets/javascripts/better_markdown.js",
|
"vendor/assets/javascripts/better_markdown.js",
|
||||||
|
"app/assets/javascripts/defer/html-sanitizer-bundle.js",
|
||||||
"app/assets/javascripts/discourse/dialects/dialect.js",
|
"app/assets/javascripts/discourse/dialects/dialect.js",
|
||||||
"app/assets/javascripts/discourse/components/utilities.js",
|
"app/assets/javascripts/discourse/components/utilities.js",
|
||||||
"app/assets/javascripts/discourse/components/markdown.js")
|
"app/assets/javascripts/discourse/components/markdown.js")
|
||||||
|
@ -164,8 +166,19 @@ module PrettyText
|
||||||
context = v8
|
context = v8
|
||||||
# we need to do this to work in a multi site environment, many sites, many settings
|
# we need to do this to work in a multi site environment, many sites, many settings
|
||||||
decorate_context(context)
|
decorate_context(context)
|
||||||
context['opts'] = opts || {}
|
|
||||||
|
context_opts = opts || {}
|
||||||
|
context_opts[:sanitize] ||= true
|
||||||
|
context['opts'] = context_opts
|
||||||
|
|
||||||
context['raw'] = text
|
context['raw'] = text
|
||||||
|
|
||||||
|
if Post.white_listed_image_classes.present?
|
||||||
|
Post.white_listed_image_classes.each do |klass|
|
||||||
|
context.eval("Discourse.Markdown.whiteListClass('#{klass}')")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context.eval('opts["mentionLookup"] = function(u){return helpers.is_username_valid(u);}')
|
context.eval('opts["mentionLookup"] = function(u){return helpers.is_username_valid(u);}')
|
||||||
context.eval('opts["lookupAvatar"] = function(p){return Discourse.Utilities.avatarImg({size: "tiny", avatarTemplate: helpers.avatar_template(p)});}')
|
context.eval('opts["lookupAvatar"] = function(p){return Discourse.Utilities.avatarImg({size: "tiny", avatarTemplate: helpers.avatar_template(p)});}')
|
||||||
baked = context.eval('Discourse.Markdown.markdownConverter(opts).makeHtml(raw)')
|
baked = context.eval('Discourse.Markdown.markdownConverter(opts).makeHtml(raw)')
|
||||||
|
|
|
@ -32,7 +32,7 @@ describe PrettyText do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should sanitize the html" do
|
it "should sanitize the html" do
|
||||||
PrettyText.cook("<script>alert(42)</script>").should match_html "alert(42)"
|
PrettyText.cook("<script>alert(42)</script>").should match_html "<p></p>"
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should allow for @mentions to have punctuation' do
|
it 'should allow for @mentions to have punctuation' do
|
||||||
|
|
|
@ -27,13 +27,6 @@ test('lists', function() {
|
||||||
format("[ol][li]option one[/li][/ol]", "<ol><li>option one</li></ol>", "creates an ol");
|
format("[ol][li]option one[/li][/ol]", "<ol><li>option one</li></ol>", "creates an ol");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('color', function() {
|
|
||||||
format("[color=#00f]blue[/color]", "<span style=\"color: #00f\">blue</span>", "supports [color=] with a short hex value");
|
|
||||||
format("[color=#ffff00]yellow[/color]", "<span style=\"color: #ffff00\">yellow</span>", "supports [color=] with a long hex value");
|
|
||||||
format("[color=red]red[/color]", "<span style=\"color: red\">red</span>", "supports [color=] with an html color");
|
|
||||||
format("[color=javascript:alert('wat')]noop[/color]", "<span>noop</span>", "it performs a noop on invalid input");
|
|
||||||
});
|
|
||||||
|
|
||||||
test('tags with arguments', function() {
|
test('tags with arguments', function() {
|
||||||
format("[size=35]BIG [b]whoop[/b][/size]", "<span class=\"bbcode-size-35\">BIG <span class=\"bbcode-b\">whoop</span></span>", "supports [size=]");
|
format("[size=35]BIG [b]whoop[/b][/size]", "<span class=\"bbcode-size-35\">BIG <span class=\"bbcode-b\">whoop</span></span>", "supports [size=]");
|
||||||
format("[url=http://bettercallsaul.com]better call![/url]", "<a href=\"http://bettercallsaul.com\">better call!</a>", "supports [url] with a title");
|
format("[url=http://bettercallsaul.com]better call![/url]", "<a href=\"http://bettercallsaul.com\">better call!</a>", "supports [url] with a title");
|
||||||
|
@ -42,7 +35,6 @@ test('tags with arguments', function() {
|
||||||
format("[b]first[/b] [b]second[/b]", "<span class=\"bbcode-b\">first</span> <span class=\"bbcode-b\">second</span>", "can bold two things on the same line");
|
format("[b]first[/b] [b]second[/b]", "<span class=\"bbcode-b\">first</span> <span class=\"bbcode-b\">second</span>", "can bold two things on the same line");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("quotes", function() {
|
test("quotes", function() {
|
||||||
|
|
||||||
var post = Discourse.Post.create({
|
var post = Discourse.Post.create({
|
||||||
|
|
|
@ -278,10 +278,12 @@ test("Code Blocks", function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("SanitizeHTML", function() {
|
test("sanitize", function() {
|
||||||
|
var sanitize = Discourse.Markdown.sanitize;
|
||||||
|
|
||||||
equal(sanitizeHtml("<div><script>alert('hi');</script></div>"), "<div></div>");
|
equal(sanitize("<i class=\"icon-bug icon-spin\">bug</i>"), "<i>bug</i>");
|
||||||
equal(sanitizeHtml("<div><p class=\"funky\" wrong='1'>hello</p></div>"), "<div><p class=\"funky\">hello</p></div>");
|
equal(sanitize("<div><script>alert('hi');</script></div>"), "<div></div>");
|
||||||
|
equal(sanitize("<div><p class=\"funky\" wrong='1'>hello</p></div>"), "<div><p>hello</p></div>");
|
||||||
cooked("hello<script>alert(42)</script>", "<p>hello</p>", "it sanitizes while cooking");
|
cooked("hello<script>alert(42)</script>", "<p>hello</p>", "it sanitizes while cooking");
|
||||||
|
|
||||||
cooked("<a href='http://disneyland.disney.go.com/'>disney</a> <a href='http://reddit.com'>reddit</a>",
|
cooked("<a href='http://disneyland.disney.go.com/'>disney</a> <a href='http://reddit.com'>reddit</a>",
|
||||||
|
@ -305,3 +307,15 @@ test("URLs in BBCode tags", function() {
|
||||||
"named links are properly parsed");
|
"named links are properly parsed");
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("urlAllowed", function() {
|
||||||
|
var allowed = function(url, msg) {
|
||||||
|
equal(Discourse.Markdown.urlAllowed(url), url, msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
allowed("/foo/bar.html", "allows relative urls");
|
||||||
|
allowed("http://eviltrout.com/evil/trout", "allows full urls");
|
||||||
|
allowed("https://eviltrout.com/evil/trout", "allows https urls");
|
||||||
|
allowed("//eviltrout.com/evil/trout", "allows protocol relative urls");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
//= require LAB.js
|
//= require LAB.js
|
||||||
//= require Markdown.Converter.js
|
//= require Markdown.Converter.js
|
||||||
//= require Markdown.Editor.js
|
//= require Markdown.Editor.js
|
||||||
//= require Markdown.Sanitizer.js
|
|
||||||
//= require better_markdown.js
|
//= require better_markdown.js
|
||||||
//= require bootbox.js
|
//= require bootbox.js
|
||||||
//= require bootstrap-alert.js
|
//= require bootstrap-alert.js
|
||||||
|
|
108
vendor/assets/javascripts/Markdown.Sanitizer.js
vendored
108
vendor/assets/javascripts/Markdown.Sanitizer.js
vendored
|
@ -1,108 +0,0 @@
|
||||||
(function () {
|
|
||||||
var output, Converter;
|
|
||||||
if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module
|
|
||||||
output = exports;
|
|
||||||
Converter = require("./Markdown.Converter").Converter;
|
|
||||||
} else {
|
|
||||||
output = window.Markdown;
|
|
||||||
Converter = output.Converter;
|
|
||||||
}
|
|
||||||
|
|
||||||
output.getSanitizingConverter = function () {
|
|
||||||
var converter = new Converter();
|
|
||||||
converter.hooks.chain("postConversion", sanitizeHtml);
|
|
||||||
converter.hooks.chain("postConversion", balanceTags);
|
|
||||||
return converter;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeHtml(html) {
|
|
||||||
return html.replace(/<[^>]*>?/gi, sanitizeTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// (tags that can be opened/closed) | (tags that stand alone)
|
|
||||||
var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i;
|
|
||||||
// <a href="url..." optional title>|</a>
|
|
||||||
var a_white = /^(<a\shref="((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\stitle="[^"<>]+")?\s?>|<\/a>)$/i;
|
|
||||||
|
|
||||||
// <img src="url..." optional width optional height optional alt optional title
|
|
||||||
var img_white = /^(<img\ssrc="(https?:\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\swidth="\d{1,3}")?(\sheight="\d{1,3}")?(\salt="[^"<>]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i;
|
|
||||||
|
|
||||||
function sanitizeTag(tag) {
|
|
||||||
if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white))
|
|
||||||
return tag;
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// attempt to balance HTML tags in the html string
|
|
||||||
/// by removing any unmatched opening or closing tags
|
|
||||||
/// IMPORTANT: we *assume* HTML has *already* been
|
|
||||||
/// sanitized and is safe/sane before balancing!
|
|
||||||
///
|
|
||||||
/// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593
|
|
||||||
/// </summary>
|
|
||||||
function balanceTags(html) {
|
|
||||||
|
|
||||||
if (html == "")
|
|
||||||
return "";
|
|
||||||
|
|
||||||
var re = /<\/?\w+[^>]*(\s|$|>)/g;
|
|
||||||
// convert everything to lower case; this makes
|
|
||||||
// our case insensitive comparisons easier
|
|
||||||
var tags = html.toLowerCase().match(re);
|
|
||||||
|
|
||||||
// no HTML tags present? nothing to do; exit now
|
|
||||||
var tagcount = (tags || []).length;
|
|
||||||
if (tagcount == 0)
|
|
||||||
return html;
|
|
||||||
|
|
||||||
var tagname, tag;
|
|
||||||
var ignoredtags = "<p><img><br><li><hr>";
|
|
||||||
var match;
|
|
||||||
var tagpaired = [];
|
|
||||||
var tagremove = [];
|
|
||||||
var needsRemoval = false;
|
|
||||||
|
|
||||||
// loop through matched tags in forward order
|
|
||||||
for (var ctag = 0; ctag < tagcount; ctag++) {
|
|
||||||
tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1");
|
|
||||||
// skip any already paired tags
|
|
||||||
// and skip tags in our ignore list; assume they're self-closed
|
|
||||||
if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
tag = tags[ctag];
|
|
||||||
match = -1;
|
|
||||||
|
|
||||||
if (!/^<\//.test(tag)) {
|
|
||||||
// this is an opening tag
|
|
||||||
// search forwards (next tags), look for closing tags
|
|
||||||
for (var ntag = ctag + 1; ntag < tagcount; ntag++) {
|
|
||||||
if (!tagpaired[ntag] && tags[ntag] == "</" + tagname + ">") {
|
|
||||||
match = ntag;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match == -1)
|
|
||||||
needsRemoval = tagremove[ctag] = true; // mark for removal
|
|
||||||
else
|
|
||||||
tagpaired[match] = true; // mark paired
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!needsRemoval)
|
|
||||||
return html;
|
|
||||||
|
|
||||||
// delete all orphaned tags from the string
|
|
||||||
|
|
||||||
var ctag = 0;
|
|
||||||
html = html.replace(re, function (match) {
|
|
||||||
var res = tagremove[ctag] ? "" : match;
|
|
||||||
ctag++;
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -108,4 +108,6 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Discourse.Markdown.whiteListClass("emoji");
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
|
Loading…
Reference in a new issue