FEATURE: add a setting to allow url schemes other than http(s)
This commit is contained in:
parent
19e2eec219
commit
761cc688b4
|
@ -19,7 +19,7 @@ export function cook(text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitize(text) {
|
export function sanitize(text) {
|
||||||
return textSanitize(text, new WhiteLister(getOpts().features));
|
return textSanitize(text, new WhiteLister(getOpts()));
|
||||||
}
|
}
|
||||||
|
|
||||||
function emojiOptions() {
|
function emojiOptions() {
|
||||||
|
|
|
@ -389,7 +389,7 @@ export function cook(raw, opts) {
|
||||||
|
|
||||||
preProcessors.forEach(p => raw = p(raw));
|
preProcessors.forEach(p => raw = p(raw));
|
||||||
|
|
||||||
const whiteLister = new WhiteLister(opts.features);
|
const whiteLister = new WhiteLister(opts);
|
||||||
|
|
||||||
const tree = parser.toHTMLTree(raw, 'Discourse');
|
const tree = parser.toHTMLTree(raw, 'Discourse');
|
||||||
let result = opts.sanitizer(parser.renderJsonML(parseTree(tree, opts)), whiteLister);
|
let result = opts.sanitizer(parser.renderJsonML(parseTree(tree, opts)), whiteLister);
|
||||||
|
|
|
@ -48,6 +48,7 @@ export function buildOptions(state) {
|
||||||
getCurrentUser,
|
getCurrentUser,
|
||||||
currentUser,
|
currentUser,
|
||||||
mentionLookup: state.mentionLookup,
|
mentionLookup: state.mentionLookup,
|
||||||
|
allowedHrefSchemes: siteSettings.allowed_href_schemes ? siteSettings.allowed_href_schemes.split('|') : null
|
||||||
};
|
};
|
||||||
|
|
||||||
_registerFns.forEach(fn => fn(siteSettings, options, state));
|
_registerFns.forEach(fn => fn(siteSettings, options, state));
|
||||||
|
@ -71,6 +72,6 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitize(html) {
|
sanitize(html) {
|
||||||
return this.opts.sanitizer(html, new WhiteLister(this.opts.features));
|
return this.opts.sanitizer(html, new WhiteLister(this.opts));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -42,7 +42,7 @@ export function escape(string) {
|
||||||
return string.replace(BAD_CHARS, escapeChar);
|
return string.replace(BAD_CHARS, escapeChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hrefAllowed(href) {
|
export function hrefAllowed(href, extraHrefMatchers) {
|
||||||
// escape single quotes
|
// escape single quotes
|
||||||
href = href.replace(/'/g, "%27");
|
href = href.replace(/'/g, "%27");
|
||||||
|
|
||||||
|
@ -54,6 +54,12 @@ export function hrefAllowed(href) {
|
||||||
if (/^#[\w\.\-]+/i.test(href)) { return href; }
|
if (/^#[\w\.\-]+/i.test(href)) { return href; }
|
||||||
// mailtos
|
// mailtos
|
||||||
if (/^mailto:[\w\.\-@]+/i.test(href)) { return href; }
|
if (/^mailto:[\w\.\-@]+/i.test(href)) { return href; }
|
||||||
|
|
||||||
|
if (extraHrefMatchers && extraHrefMatchers.length > 0) {
|
||||||
|
for (let i=0; i<extraHrefMatchers.length; i++) {
|
||||||
|
if (extraHrefMatchers[i].test(href)) { return href; }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitize(text, whiteLister) {
|
export function sanitize(text, whiteLister) {
|
||||||
|
@ -62,7 +68,13 @@ export function sanitize(text, whiteLister) {
|
||||||
// Allow things like <3 and <_<
|
// Allow things like <3 and <_<
|
||||||
text = text.replace(/<([^A-Za-z\/\!]|$)/g, "<$1");
|
text = text.replace(/<([^A-Za-z\/\!]|$)/g, "<$1");
|
||||||
|
|
||||||
const whiteList = whiteLister.getWhiteList();
|
const whiteList = whiteLister.getWhiteList(),
|
||||||
|
allowedHrefSchemes = whiteLister.getAllowedHrefSchemes();
|
||||||
|
let extraHrefMatchers = null;
|
||||||
|
|
||||||
|
if (allowedHrefSchemes && allowedHrefSchemes.length > 0) {
|
||||||
|
extraHrefMatchers = [new RegExp('^(' + allowedHrefSchemes.join('|') + '):\/\/[\\w\\.\\-]+','i')];
|
||||||
|
}
|
||||||
|
|
||||||
let result = xss(text, {
|
let result = xss(text, {
|
||||||
whiteList: whiteList.tagList,
|
whiteList: whiteList.tagList,
|
||||||
|
@ -75,8 +87,8 @@ export function sanitize(text, whiteLister) {
|
||||||
const forAttr = forTag[name];
|
const forAttr = forTag[name];
|
||||||
if ((forAttr && (forAttr.indexOf('*') !== -1 || forAttr.indexOf(value) !== -1)) ||
|
if ((forAttr && (forAttr.indexOf('*') !== -1 || forAttr.indexOf(value) !== -1)) ||
|
||||||
(name.indexOf('data-') === 0 && forTag['data-*']) ||
|
(name.indexOf('data-') === 0 && forTag['data-*']) ||
|
||||||
((tag === 'a' && name === 'href') && hrefAllowed(value)) ||
|
((tag === 'a' && name === 'href') && hrefAllowed(value, extraHrefMatchers)) ||
|
||||||
(tag === 'img' && name === 'src' && (/^data:image.*$/i.test(value) || hrefAllowed(value))) ||
|
(tag === 'img' && name === 'src' && (/^data:image.*$/i.test(value) || hrefAllowed(value, extraHrefMatchers))) ||
|
||||||
(tag === 'iframe' && name === 'src' && _validIframes.some(i => i.test(value)))) {
|
(tag === 'iframe' && name === 'src' && _validIframes.some(i => i.test(value)))) {
|
||||||
return attr(name, value);
|
return attr(name, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,13 @@ function concatUniq(src, elems) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class WhiteLister {
|
export default class WhiteLister {
|
||||||
constructor(features) {
|
constructor(options) {
|
||||||
features.default = true;
|
options.features.default = true;
|
||||||
|
|
||||||
this._featureKeys = Object.keys(features).filter(f => features[f]);
|
this._featureKeys = Object.keys(options.features).filter(f => options.features[f]);
|
||||||
this._key = this._featureKeys.join(':');
|
this._key = this._featureKeys.join(':');
|
||||||
this._features = features;
|
this._features = options.features;
|
||||||
|
this._options = options||{};
|
||||||
}
|
}
|
||||||
|
|
||||||
getCustom() {
|
getCustom() {
|
||||||
|
@ -54,6 +55,10 @@ export default class WhiteLister {
|
||||||
}
|
}
|
||||||
return _whiteLists[this._key];
|
return _whiteLists[this._key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllowedHrefSchemes() {
|
||||||
|
return this._options.allowedHrefSchemes || [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds our object that represents whether something is sanitized for a particular feature.
|
// Builds our object that represents whether something is sanitized for a particular feature.
|
||||||
|
|
|
@ -1332,6 +1332,7 @@ en:
|
||||||
embed_username_key_from_feed: "Key to pull discourse username from feed."
|
embed_username_key_from_feed: "Key to pull discourse username from feed."
|
||||||
embed_title_scrubber: "Regular expression for scrubbing embeddable titles."
|
embed_title_scrubber: "Regular expression for scrubbing embeddable titles."
|
||||||
embed_truncate: "Truncate the embedded posts."
|
embed_truncate: "Truncate the embedded posts."
|
||||||
|
allowed_href_schemes: "Schemes allowed in links in addition to http and https."
|
||||||
embed_post_limit: "Maximum number of posts to embed."
|
embed_post_limit: "Maximum number of posts to embed."
|
||||||
embed_username_required: "The username for topic creation is required."
|
embed_username_required: "The username for topic creation is required."
|
||||||
embed_whitelist_selector: "CSS selector for elements that are allowed in embeds."
|
embed_whitelist_selector: "CSS selector for elements that are allowed in embeds."
|
||||||
|
|
|
@ -567,6 +567,10 @@ posting:
|
||||||
- code-fences
|
- code-fences
|
||||||
embed_truncate:
|
embed_truncate:
|
||||||
default: true
|
default: true
|
||||||
|
allowed_href_schemes:
|
||||||
|
client: true
|
||||||
|
default: ''
|
||||||
|
type: list
|
||||||
|
|
||||||
email:
|
email:
|
||||||
email_time_window_mins:
|
email_time_window_mins:
|
||||||
|
|
|
@ -37,9 +37,9 @@ function sanitizer(result, whiteLister) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function md(input, expected, text) {
|
function md(input, expected, text, settings) {
|
||||||
|
|
||||||
const opts = buildOptions({ siteSettings: {} });
|
const opts = buildOptions({ siteSettings: settings||{} });
|
||||||
opts.traditionalMarkdownLinebreaks = true;
|
opts.traditionalMarkdownLinebreaks = true;
|
||||||
opts.sanitizer = sanitizer;
|
opts.sanitizer = sanitizer;
|
||||||
|
|
||||||
|
@ -77,3 +77,11 @@ test("first", function(){
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<%= mdtest_suite %>
|
<%= mdtest_suite %>
|
||||||
|
|
||||||
|
test("whitelisted url scheme", function() {
|
||||||
|
md("[Steam URL Scheme](steam://store/452530)", '<p><a href="steam://store/452530">Steam URL Scheme</a></p>', 'whitelists the steam url', {allowed_href_schemes: "macappstore|steam"});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("forbidden url scheme", function() {
|
||||||
|
md("[Steam URL Scheme](steam://store/452530)", '<p><a>Steam URL Scheme</a></p>', 'removes the href', {allowed_href_schemes: "macappstore|itunes"});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue