FIX: pretty text allow list (#10977)
Reword whitelist to allowlist in pretty-text. This library is used by plugins so we need deprecation notice.
This commit is contained in:
parent
632942e697
commit
dbec3792b7
|
@ -268,7 +268,7 @@ export default Controller.extend(ModalFunctionality, {
|
||||||
} else {
|
} else {
|
||||||
const opts = {
|
const opts = {
|
||||||
features: { editHistory: true, historyOneboxes: true },
|
features: { editHistory: true, historyOneboxes: true },
|
||||||
whiteListed: {
|
allowListed: {
|
||||||
editHistory: { custom: (tag, attr) => attr === "class" },
|
editHistory: { custom: (tag, attr) => attr === "class" },
|
||||||
historyOneboxes: ["header", "article", "div[style]"],
|
historyOneboxes: ["header", "article", "div[style]"],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { getURLWithCDN } from "discourse-common/lib/get-url";
|
import { getURLWithCDN } from "discourse-common/lib/get-url";
|
||||||
import PrettyText, { buildOptions } from "pretty-text/pretty-text";
|
import PrettyText, { buildOptions } from "pretty-text/pretty-text";
|
||||||
import { performEmojiUnescape, buildEmojiUrl } from "pretty-text/emoji";
|
import { performEmojiUnescape, buildEmojiUrl } from "pretty-text/emoji";
|
||||||
import WhiteLister from "pretty-text/white-lister";
|
import AllowLister from "pretty-text/allow-lister";
|
||||||
import { sanitize as textSanitize } from "pretty-text/sanitizer";
|
import { sanitize as textSanitize } from "pretty-text/sanitizer";
|
||||||
import loadScript from "discourse/lib/load-script";
|
import loadScript from "discourse/lib/load-script";
|
||||||
import { formatUsername } from "discourse/lib/utilities";
|
import { formatUsername } from "discourse/lib/utilities";
|
||||||
|
@ -49,7 +49,7 @@ export function generateCookFunction(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitize(text, options) {
|
export function sanitize(text, options) {
|
||||||
return textSanitize(text, new WhiteLister(options));
|
return textSanitize(text, new AllowLister(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeAsync(text, options) {
|
export function sanitizeAsync(text, options) {
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
import { test, module } from "qunit";
|
import { test, module } from "qunit";
|
||||||
import WhiteLister from "pretty-text/white-lister";
|
import AllowLister from "pretty-text/allow-lister";
|
||||||
|
|
||||||
module("lib:whiteLister");
|
module("lib:allowLister");
|
||||||
|
|
||||||
test("whiteLister", (assert) => {
|
test("allowLister", (assert) => {
|
||||||
const whiteLister = new WhiteLister();
|
const allowLister = new AllowLister();
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
Object.keys(whiteLister.getWhiteList().tagList).length > 1,
|
Object.keys(allowLister.getAllowList().tagList).length > 1,
|
||||||
"should have some defaults"
|
"should have some defaults"
|
||||||
);
|
);
|
||||||
|
|
||||||
whiteLister.disable("default");
|
allowLister.disable("default");
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
Object.keys(whiteLister.getWhiteList().tagList).length === 0,
|
Object.keys(allowLister.getAllowList().tagList).length === 0,
|
||||||
"should have no defaults if disabled"
|
"should have no defaults if disabled"
|
||||||
);
|
);
|
||||||
|
|
||||||
whiteLister.whiteListFeature("test", [
|
allowLister.allowListFeature("test", [
|
||||||
"custom.foo",
|
"custom.foo",
|
||||||
"custom.baz",
|
"custom.baz",
|
||||||
"custom[data-*]",
|
"custom[data-*]",
|
||||||
"custom[rel=nofollow]",
|
"custom[rel=nofollow]",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
whiteLister.whiteListFeature("test", ["custom[rel=test]"]);
|
allowLister.allowListFeature("test", ["custom[rel=test]"]);
|
||||||
|
|
||||||
whiteLister.enable("test");
|
allowLister.enable("test");
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
whiteLister.getWhiteList(),
|
allowLister.getAllowList(),
|
||||||
{
|
{
|
||||||
tagList: {
|
tagList: {
|
||||||
custom: [],
|
custom: [],
|
||||||
|
@ -46,10 +46,10 @@ test("whiteLister", (assert) => {
|
||||||
"Expecting a correct white list"
|
"Expecting a correct white list"
|
||||||
);
|
);
|
||||||
|
|
||||||
whiteLister.disable("test");
|
allowLister.disable("test");
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
whiteLister.getWhiteList(),
|
allowLister.getAllowList(),
|
||||||
{
|
{
|
||||||
tagList: {},
|
tagList: {},
|
||||||
attrList: {},
|
attrList: {},
|
|
@ -6,6 +6,7 @@
|
||||||
//= require ./pretty-text/addon/emoji
|
//= require ./pretty-text/addon/emoji
|
||||||
//= require ./pretty-text/addon/engines/discourse-markdown-it
|
//= require ./pretty-text/addon/engines/discourse-markdown-it
|
||||||
//= require xss.min
|
//= require xss.min
|
||||||
|
//= require ./pretty-text/addon/allow-lister
|
||||||
//= require ./pretty-text/addon/white-lister
|
//= require ./pretty-text/addon/white-lister
|
||||||
//= require ./pretty-text/addon/sanitizer
|
//= require ./pretty-text/addon/sanitizer
|
||||||
//= require ./pretty-text/addon/oneboxer
|
//= require ./pretty-text/addon/oneboxer
|
||||||
|
|
|
@ -0,0 +1,246 @@
|
||||||
|
import deprecated from "discourse-common/lib/deprecated";
|
||||||
|
// to match:
|
||||||
|
// abcd
|
||||||
|
// abcd[test]
|
||||||
|
// abcd[test=bob]
|
||||||
|
const ALLOWLIST_REGEX = /([^\[]+)(\[([^=]+)(=(.*))?\])?/;
|
||||||
|
|
||||||
|
export default class AllowLister {
|
||||||
|
constructor(options) {
|
||||||
|
this._enabled = { default: true };
|
||||||
|
this._allowedHrefSchemes = (options && options.allowedHrefSchemes) || [];
|
||||||
|
this._allowedIframes = (options && options.allowedIframes) || [];
|
||||||
|
this._rawFeatures = [["default", DEFAULT_LIST]];
|
||||||
|
|
||||||
|
this._cache = null;
|
||||||
|
|
||||||
|
if (options && options.features) {
|
||||||
|
Object.keys(options.features).forEach((f) => {
|
||||||
|
if (options.features[f]) {
|
||||||
|
this._enabled[f] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowListFeature(feature, info) {
|
||||||
|
this._rawFeatures.push([feature, info]);
|
||||||
|
}
|
||||||
|
|
||||||
|
whiteListFeature(feature, info) {
|
||||||
|
deprecated("`whiteListFeature` has been replaced with `allowListFeature`", {
|
||||||
|
since: "2.6.0.beta.4",
|
||||||
|
dropFrom: "2.7.0",
|
||||||
|
});
|
||||||
|
this.allowListFeature(feature, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
disable(feature) {
|
||||||
|
this._enabled[feature] = false;
|
||||||
|
this._cache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
enable(feature) {
|
||||||
|
this._enabled[feature] = true;
|
||||||
|
this._cache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildCache() {
|
||||||
|
const tagList = {};
|
||||||
|
const attrList = {};
|
||||||
|
const custom = [];
|
||||||
|
|
||||||
|
this._rawFeatures.forEach(([name, info]) => {
|
||||||
|
if (!this._enabled[name]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.custom) {
|
||||||
|
custom.push(info.custom);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof info === "string") {
|
||||||
|
info = [info];
|
||||||
|
}
|
||||||
|
|
||||||
|
(info || []).forEach((tag) => {
|
||||||
|
const classes = tag.split(".");
|
||||||
|
const tagWithAttr = classes.shift();
|
||||||
|
|
||||||
|
const m = ALLOWLIST_REGEX.exec(tagWithAttr);
|
||||||
|
if (m) {
|
||||||
|
const [, tagname, , attr, , val] = m;
|
||||||
|
tagList[tagname] = [];
|
||||||
|
|
||||||
|
let attrs = (attrList[tagname] = attrList[tagname] || {});
|
||||||
|
if (classes.length > 0) {
|
||||||
|
attrs["class"] = (attrs["class"] || []).concat(classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr) {
|
||||||
|
let attrInfo = (attrs[attr] = attrs[attr] || []);
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
attrInfo.push(val);
|
||||||
|
} else {
|
||||||
|
attrs[attr] = ["*"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this._cache = { custom, allowList: { tagList, attrList } };
|
||||||
|
}
|
||||||
|
|
||||||
|
_ensureCache() {
|
||||||
|
if (!this._cache) {
|
||||||
|
this._buildCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllowList() {
|
||||||
|
this._ensureCache();
|
||||||
|
return this._cache.allowList;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWhiteList() {
|
||||||
|
deprecated("`getWhiteList` has been replaced with `getAllowList`", {
|
||||||
|
since: "2.6.0.beta.4",
|
||||||
|
dropFrom: "2.7.0",
|
||||||
|
});
|
||||||
|
return this.getAllowList();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustom() {
|
||||||
|
this._ensureCache();
|
||||||
|
return this._cache.custom;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllowedHrefSchemes() {
|
||||||
|
return this._allowedHrefSchemes;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllowedIframes() {
|
||||||
|
return this._allowedIframes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add to `default` when you always want your allowlist to occur. In other words,
|
||||||
|
// don't change this for a plugin or a feature that can be disabled
|
||||||
|
export const DEFAULT_LIST = [
|
||||||
|
"a.attachment",
|
||||||
|
"a.hashtag",
|
||||||
|
"a.mention",
|
||||||
|
"a.mention-group",
|
||||||
|
"a.onebox",
|
||||||
|
`a.inline-onebox`,
|
||||||
|
`a.inline-onebox-loading`,
|
||||||
|
"a[data-bbcode]",
|
||||||
|
"a[name]",
|
||||||
|
"a[rel=nofollow]",
|
||||||
|
"a[rel=ugc]",
|
||||||
|
"a[target=_blank]",
|
||||||
|
"a[title]",
|
||||||
|
"abbr[title]",
|
||||||
|
"aside.quote",
|
||||||
|
"aside[data-*]",
|
||||||
|
"audio",
|
||||||
|
"audio[controls]",
|
||||||
|
"audio[preload]",
|
||||||
|
"b",
|
||||||
|
"big",
|
||||||
|
"blockquote",
|
||||||
|
"br",
|
||||||
|
"code",
|
||||||
|
"dd",
|
||||||
|
"del",
|
||||||
|
"div",
|
||||||
|
"div.quote-controls",
|
||||||
|
"div.title",
|
||||||
|
"div[align]",
|
||||||
|
"div[lang]",
|
||||||
|
"div[data-*]" /* This may seem a bit much but polls does
|
||||||
|
it anyway and this is needed for themes,
|
||||||
|
special code in sanitizer handles data-*
|
||||||
|
nothing exists for data-theme-* and we
|
||||||
|
don't want to slow sanitize for this case
|
||||||
|
*/,
|
||||||
|
"div[dir]",
|
||||||
|
"dl",
|
||||||
|
"dt",
|
||||||
|
"em",
|
||||||
|
"h1",
|
||||||
|
"h2",
|
||||||
|
"h3",
|
||||||
|
"h4",
|
||||||
|
"h5",
|
||||||
|
"h6",
|
||||||
|
"hr",
|
||||||
|
"i",
|
||||||
|
"iframe",
|
||||||
|
"iframe[frameborder]",
|
||||||
|
"iframe[height]",
|
||||||
|
"iframe[marginheight]",
|
||||||
|
"iframe[marginwidth]",
|
||||||
|
"iframe[width]",
|
||||||
|
"iframe[allowfullscreen]",
|
||||||
|
"img[alt]",
|
||||||
|
"img[height]",
|
||||||
|
"img[title]",
|
||||||
|
"img[width]",
|
||||||
|
"ins",
|
||||||
|
"kbd",
|
||||||
|
"li",
|
||||||
|
"ol",
|
||||||
|
"ol[start]",
|
||||||
|
"p",
|
||||||
|
"p[lang]",
|
||||||
|
"picture",
|
||||||
|
"pre",
|
||||||
|
"s",
|
||||||
|
"small",
|
||||||
|
"span[lang]",
|
||||||
|
"span.excerpt",
|
||||||
|
"div.excerpt",
|
||||||
|
"div.video-container",
|
||||||
|
"div.onebox-placeholder-container",
|
||||||
|
"span.placeholder-icon video",
|
||||||
|
"span.hashtag",
|
||||||
|
"span.mention",
|
||||||
|
"strike",
|
||||||
|
"strong",
|
||||||
|
"sub",
|
||||||
|
"sup",
|
||||||
|
"source[data-orig-src]",
|
||||||
|
"source[src]",
|
||||||
|
"source[srcset]",
|
||||||
|
"source[type]",
|
||||||
|
"track",
|
||||||
|
"track[default]",
|
||||||
|
"track[label]",
|
||||||
|
"track[kind]",
|
||||||
|
"track[src]",
|
||||||
|
"track[srclang]",
|
||||||
|
"ul",
|
||||||
|
"video",
|
||||||
|
"video[autoplay]",
|
||||||
|
"video[controls]",
|
||||||
|
"video[controlslist]",
|
||||||
|
"video[crossorigin]",
|
||||||
|
"video[height]",
|
||||||
|
"video[loop]",
|
||||||
|
"video[muted]",
|
||||||
|
"video[playsinline]",
|
||||||
|
"video[poster]",
|
||||||
|
"video[preload]",
|
||||||
|
"video[width]",
|
||||||
|
"ruby",
|
||||||
|
"ruby[lang]",
|
||||||
|
"rb",
|
||||||
|
"rb[lang]",
|
||||||
|
"rp",
|
||||||
|
"rt",
|
||||||
|
"rt[lang]",
|
||||||
|
];
|
|
@ -1,6 +1,7 @@
|
||||||
import WhiteLister from "pretty-text/white-lister";
|
import AllowLister from "pretty-text/allow-lister";
|
||||||
import { sanitize } from "pretty-text/sanitizer";
|
import { sanitize } from "pretty-text/sanitizer";
|
||||||
import guid from "pretty-text/guid";
|
import guid from "pretty-text/guid";
|
||||||
|
import deprecated from "discourse-common/lib/deprecated";
|
||||||
|
|
||||||
export const ATTACHMENT_CSS_CLASS = "attachment";
|
export const ATTACHMENT_CSS_CLASS = "attachment";
|
||||||
|
|
||||||
|
@ -23,11 +24,19 @@ function createHelper(
|
||||||
optionCallbacks,
|
optionCallbacks,
|
||||||
pluginCallbacks,
|
pluginCallbacks,
|
||||||
getOptions,
|
getOptions,
|
||||||
whiteListed
|
allowListed
|
||||||
) {
|
) {
|
||||||
let helper = {};
|
let helper = {};
|
||||||
helper.markdownIt = true;
|
helper.markdownIt = true;
|
||||||
helper.whiteList = (info) => whiteListed.push([featureName, info]);
|
helper.allowList = (info) => allowListed.push([featureName, info]);
|
||||||
|
helper.whiteList = (info) => {
|
||||||
|
deprecated("`whiteList` has been replaced with `allowList`", {
|
||||||
|
since: "2.6.0.beta.4",
|
||||||
|
dropFrom: "2.7.0",
|
||||||
|
});
|
||||||
|
helper.allowList(info);
|
||||||
|
};
|
||||||
|
|
||||||
helper.registerInline = deprecate(featureName, "registerInline");
|
helper.registerInline = deprecate(featureName, "registerInline");
|
||||||
helper.replaceBlock = deprecate(featureName, "replaceBlock");
|
helper.replaceBlock = deprecate(featureName, "replaceBlock");
|
||||||
helper.addPreProcessor = deprecate(featureName, "addPreProcessor");
|
helper.addPreProcessor = deprecate(featureName, "addPreProcessor");
|
||||||
|
@ -296,7 +305,7 @@ export function setup(opts, siteSettings, state) {
|
||||||
|
|
||||||
const check = /discourse-markdown\/|markdown-it\//;
|
const check = /discourse-markdown\/|markdown-it\//;
|
||||||
let features = [];
|
let features = [];
|
||||||
let whiteListed = [];
|
let allowListed = [];
|
||||||
|
|
||||||
Object.keys(require._eak_seen).forEach((entry) => {
|
Object.keys(require._eak_seen).forEach((entry) => {
|
||||||
if (check.test(entry)) {
|
if (check.test(entry)) {
|
||||||
|
@ -319,13 +328,13 @@ export function setup(opts, siteSettings, state) {
|
||||||
optionCallbacks,
|
optionCallbacks,
|
||||||
pluginCallbacks,
|
pluginCallbacks,
|
||||||
getOptions,
|
getOptions,
|
||||||
whiteListed
|
allowListed
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.entries(state.whiteListed || {}).forEach((entry) => {
|
Object.entries(state.allowListed || {}).forEach((entry) => {
|
||||||
whiteListed.push(entry);
|
allowListed.push(entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
optionCallbacks.forEach(([, callback]) => {
|
optionCallbacks.forEach(([, callback]) => {
|
||||||
|
@ -393,14 +402,14 @@ export function setup(opts, siteSettings, state) {
|
||||||
opts.setup = true;
|
opts.setup = true;
|
||||||
|
|
||||||
if (!opts.discourse.sanitizer || !opts.sanitizer) {
|
if (!opts.discourse.sanitizer || !opts.sanitizer) {
|
||||||
const whiteLister = new WhiteLister(opts.discourse);
|
const allowLister = new AllowLister(opts.discourse);
|
||||||
|
|
||||||
whiteListed.forEach(([feature, info]) => {
|
allowListed.forEach(([feature, info]) => {
|
||||||
whiteLister.whiteListFeature(feature, info);
|
allowLister.allowListFeature(feature, info);
|
||||||
});
|
});
|
||||||
|
|
||||||
opts.sanitizer = opts.discourse.sanitizer = !!opts.discourse.sanitize
|
opts.sanitizer = opts.discourse.sanitizer = !!opts.discourse.sanitize
|
||||||
? (a) => sanitize(a, whiteLister)
|
? (a) => sanitize(a, allowLister)
|
||||||
: (a) => a;
|
: (a) => a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ export function hrefAllowed(href, extraHrefMatchers) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitize(text, whiteLister) {
|
export function sanitize(text, allowLister) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -79,9 +79,9 @@ 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 allowList = allowLister.getAllowList(),
|
||||||
allowedHrefSchemes = whiteLister.getAllowedHrefSchemes(),
|
allowedHrefSchemes = allowLister.getAllowedHrefSchemes(),
|
||||||
allowedIframes = whiteLister.getAllowedIframes();
|
allowedIframes = allowLister.getAllowedIframes();
|
||||||
let extraHrefMatchers = null;
|
let extraHrefMatchers = null;
|
||||||
|
|
||||||
if (allowedHrefSchemes && allowedHrefSchemes.length > 0) {
|
if (allowedHrefSchemes && allowedHrefSchemes.length > 0) {
|
||||||
|
@ -94,12 +94,12 @@ export function sanitize(text, whiteLister) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = xss(text, {
|
let result = xss(text, {
|
||||||
whiteList: whiteList.tagList,
|
whiteList: allowList.tagList,
|
||||||
stripIgnoreTag: true,
|
stripIgnoreTag: true,
|
||||||
stripIgnoreTagBody: ["script", "table"],
|
stripIgnoreTagBody: ["script", "table"],
|
||||||
|
|
||||||
onIgnoreTagAttr(tag, name, value) {
|
onIgnoreTagAttr(tag, name, value) {
|
||||||
const forTag = whiteList.attrList[tag];
|
const forTag = allowList.attrList[tag];
|
||||||
if (forTag) {
|
if (forTag) {
|
||||||
const forAttr = forTag[name];
|
const forAttr = forTag[name];
|
||||||
if (
|
if (
|
||||||
|
@ -134,7 +134,7 @@ export function sanitize(text, whiteLister) {
|
||||||
return attr(name, value);
|
return attr(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const custom = whiteLister.getCustom();
|
const custom = allowLister.getCustom();
|
||||||
for (let i = 0; i < custom.length; i++) {
|
for (let i = 0; i < custom.length; i++) {
|
||||||
const fn = custom[i];
|
const fn = custom[i];
|
||||||
if (fn(tag, name, value)) {
|
if (fn(tag, name, value)) {
|
||||||
|
|
|
@ -1,229 +1,15 @@
|
||||||
// to match:
|
import deprecated from "discourse-common/lib/deprecated";
|
||||||
// abcd
|
import AllowLister from "pretty-text/allow-lister";
|
||||||
// abcd[test]
|
import { DEFAULT_LIST as NEW_DEFAULT_LIST } from "pretty-text/allow-lister";
|
||||||
// abcd[test=bob]
|
|
||||||
const WHITELIST_REGEX = /([^\[]+)(\[([^=]+)(=(.*))?\])?/;
|
|
||||||
|
|
||||||
export default class WhiteLister {
|
export default class WhiteLister extends AllowLister {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this._enabled = { default: true };
|
deprecated("`WhiteLister` has been replaced with `AllowLister`", {
|
||||||
this._allowedHrefSchemes = (options && options.allowedHrefSchemes) || [];
|
since: "2.6.0.beta.4",
|
||||||
this._allowedIframes = (options && options.allowedIframes) || [];
|
dropFrom: "2.7.0",
|
||||||
this._rawFeatures = [["default", DEFAULT_LIST]];
|
|
||||||
|
|
||||||
this._cache = null;
|
|
||||||
|
|
||||||
if (options && options.features) {
|
|
||||||
Object.keys(options.features).forEach((f) => {
|
|
||||||
if (options.features[f]) {
|
|
||||||
this._enabled[f] = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
super(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
whiteListFeature(feature, info) {
|
export const DEFAULT_LIST = NEW_DEFAULT_LIST;
|
||||||
this._rawFeatures.push([feature, info]);
|
|
||||||
}
|
|
||||||
|
|
||||||
disable(feature) {
|
|
||||||
this._enabled[feature] = false;
|
|
||||||
this._cache = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
enable(feature) {
|
|
||||||
this._enabled[feature] = true;
|
|
||||||
this._cache = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_buildCache() {
|
|
||||||
const tagList = {};
|
|
||||||
const attrList = {};
|
|
||||||
const custom = [];
|
|
||||||
|
|
||||||
this._rawFeatures.forEach(([name, info]) => {
|
|
||||||
if (!this._enabled[name]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.custom) {
|
|
||||||
custom.push(info.custom);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof info === "string") {
|
|
||||||
info = [info];
|
|
||||||
}
|
|
||||||
|
|
||||||
(info || []).forEach((tag) => {
|
|
||||||
const classes = tag.split(".");
|
|
||||||
const tagWithAttr = classes.shift();
|
|
||||||
|
|
||||||
const m = WHITELIST_REGEX.exec(tagWithAttr);
|
|
||||||
if (m) {
|
|
||||||
const [, tagname, , attr, , val] = m;
|
|
||||||
tagList[tagname] = [];
|
|
||||||
|
|
||||||
let attrs = (attrList[tagname] = attrList[tagname] || {});
|
|
||||||
if (classes.length > 0) {
|
|
||||||
attrs["class"] = (attrs["class"] || []).concat(classes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attr) {
|
|
||||||
let attrInfo = (attrs[attr] = attrs[attr] || []);
|
|
||||||
|
|
||||||
if (val) {
|
|
||||||
attrInfo.push(val);
|
|
||||||
} else {
|
|
||||||
attrs[attr] = ["*"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this._cache = { custom, whiteList: { tagList, attrList } };
|
|
||||||
}
|
|
||||||
|
|
||||||
_ensureCache() {
|
|
||||||
if (!this._cache) {
|
|
||||||
this._buildCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getWhiteList() {
|
|
||||||
this._ensureCache();
|
|
||||||
return this._cache.whiteList;
|
|
||||||
}
|
|
||||||
|
|
||||||
getCustom() {
|
|
||||||
this._ensureCache();
|
|
||||||
return this._cache.custom;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllowedHrefSchemes() {
|
|
||||||
return this._allowedHrefSchemes;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllowedIframes() {
|
|
||||||
return this._allowedIframes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only add to `default` when you always want your allowlist to occur. In other words,
|
|
||||||
// don't change this for a plugin or a feature that can be disabled
|
|
||||||
export const DEFAULT_LIST = [
|
|
||||||
"a.attachment",
|
|
||||||
"a.hashtag",
|
|
||||||
"a.mention",
|
|
||||||
"a.mention-group",
|
|
||||||
"a.onebox",
|
|
||||||
`a.inline-onebox`,
|
|
||||||
`a.inline-onebox-loading`,
|
|
||||||
"a[data-bbcode]",
|
|
||||||
"a[name]",
|
|
||||||
"a[rel=nofollow]",
|
|
||||||
"a[rel=ugc]",
|
|
||||||
"a[target=_blank]",
|
|
||||||
"a[title]",
|
|
||||||
"abbr[title]",
|
|
||||||
"aside.quote",
|
|
||||||
"aside[data-*]",
|
|
||||||
"audio",
|
|
||||||
"audio[controls]",
|
|
||||||
"audio[preload]",
|
|
||||||
"b",
|
|
||||||
"big",
|
|
||||||
"blockquote",
|
|
||||||
"br",
|
|
||||||
"code",
|
|
||||||
"dd",
|
|
||||||
"del",
|
|
||||||
"div",
|
|
||||||
"div.quote-controls",
|
|
||||||
"div.title",
|
|
||||||
"div[align]",
|
|
||||||
"div[lang]",
|
|
||||||
"div[data-*]" /* This may seem a bit much but polls does
|
|
||||||
it anyway and this is needed for themes,
|
|
||||||
special code in sanitizer handles data-*
|
|
||||||
nothing exists for data-theme-* and we
|
|
||||||
don't want to slow sanitize for this case
|
|
||||||
*/,
|
|
||||||
"div[dir]",
|
|
||||||
"dl",
|
|
||||||
"dt",
|
|
||||||
"em",
|
|
||||||
"h1",
|
|
||||||
"h2",
|
|
||||||
"h3",
|
|
||||||
"h4",
|
|
||||||
"h5",
|
|
||||||
"h6",
|
|
||||||
"hr",
|
|
||||||
"i",
|
|
||||||
"iframe",
|
|
||||||
"iframe[frameborder]",
|
|
||||||
"iframe[height]",
|
|
||||||
"iframe[marginheight]",
|
|
||||||
"iframe[marginwidth]",
|
|
||||||
"iframe[width]",
|
|
||||||
"iframe[allowfullscreen]",
|
|
||||||
"img[alt]",
|
|
||||||
"img[height]",
|
|
||||||
"img[title]",
|
|
||||||
"img[width]",
|
|
||||||
"ins",
|
|
||||||
"kbd",
|
|
||||||
"li",
|
|
||||||
"ol",
|
|
||||||
"ol[start]",
|
|
||||||
"p",
|
|
||||||
"p[lang]",
|
|
||||||
"picture",
|
|
||||||
"pre",
|
|
||||||
"s",
|
|
||||||
"small",
|
|
||||||
"span[lang]",
|
|
||||||
"span.excerpt",
|
|
||||||
"div.excerpt",
|
|
||||||
"div.video-container",
|
|
||||||
"div.onebox-placeholder-container",
|
|
||||||
"span.placeholder-icon video",
|
|
||||||
"span.hashtag",
|
|
||||||
"span.mention",
|
|
||||||
"strike",
|
|
||||||
"strong",
|
|
||||||
"sub",
|
|
||||||
"sup",
|
|
||||||
"source[data-orig-src]",
|
|
||||||
"source[src]",
|
|
||||||
"source[srcset]",
|
|
||||||
"source[type]",
|
|
||||||
"track",
|
|
||||||
"track[default]",
|
|
||||||
"track[label]",
|
|
||||||
"track[kind]",
|
|
||||||
"track[src]",
|
|
||||||
"track[srclang]",
|
|
||||||
"ul",
|
|
||||||
"video",
|
|
||||||
"video[autoplay]",
|
|
||||||
"video[controls]",
|
|
||||||
"video[controlslist]",
|
|
||||||
"video[crossorigin]",
|
|
||||||
"video[height]",
|
|
||||||
"video[loop]",
|
|
||||||
"video[muted]",
|
|
||||||
"video[playsinline]",
|
|
||||||
"video[poster]",
|
|
||||||
"video[preload]",
|
|
||||||
"video[width]",
|
|
||||||
"ruby",
|
|
||||||
"ruby[lang]",
|
|
||||||
"rb",
|
|
||||||
"rb[lang]",
|
|
||||||
"rp",
|
|
||||||
"rt",
|
|
||||||
"rt[lang]",
|
|
||||||
];
|
|
||||||
|
|
|
@ -147,7 +147,7 @@ function processBBCode(state, silent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setup(helper) {
|
export function setup(helper) {
|
||||||
helper.whiteList([
|
helper.allowList([
|
||||||
"span.bbcode-b",
|
"span.bbcode-b",
|
||||||
"span.bbcode-i",
|
"span.bbcode-i",
|
||||||
"span.bbcode-u",
|
"span.bbcode-u",
|
||||||
|
|
|
@ -41,7 +41,7 @@ export function setup(helper) {
|
||||||
.concat(["auto", "nohighlight"]);
|
.concat(["auto", "nohighlight"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.whiteList({
|
helper.allowList({
|
||||||
custom(tag, name, value) {
|
custom(tag, name, value) {
|
||||||
if (tag === "code" && name === "class") {
|
if (tag === "code" && name === "class") {
|
||||||
const m = /^lang\-(.+)$/.exec(value);
|
const m = /^lang\-(.+)$/.exec(value);
|
||||||
|
|
|
@ -68,5 +68,5 @@ export function setup(helper) {
|
||||||
md.block.bbcode.ruler.push("block-wrap", blockRule);
|
md.block.bbcode.ruler.push("block-wrap", blockRule);
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.whiteList([`div.${WRAP_CLASS}`, `span.${WRAP_CLASS}`, "span[data-*]"]);
|
helper.allowList([`div.${WRAP_CLASS}`, `span.${WRAP_CLASS}`, "span[data-*]"]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,7 +339,7 @@ export function setup(helper) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.whiteList([
|
helper.allowList([
|
||||||
"img[class=emoji]",
|
"img[class=emoji]",
|
||||||
"img[class=emoji emoji-custom]",
|
"img[class=emoji emoji-custom]",
|
||||||
"img[class=emoji emoji-custom only-emoji]",
|
"img[class=emoji emoji-custom only-emoji]",
|
||||||
|
|
|
@ -2,7 +2,7 @@ export function setup(helper) {
|
||||||
const opts = helper.getOptions();
|
const opts = helper.getOptions();
|
||||||
|
|
||||||
if (opts.previewing && opts.injectLineNumbersToPreview) {
|
if (opts.previewing && opts.injectLineNumbersToPreview) {
|
||||||
helper.whiteList([
|
helper.allowList([
|
||||||
"p.preview-sync-line",
|
"p.preview-sync-line",
|
||||||
"p[data-line-number]",
|
"p[data-line-number]",
|
||||||
"h1.preview-sync-line",
|
"h1.preview-sync-line",
|
||||||
|
|
|
@ -168,8 +168,8 @@ export function setup(helper) {
|
||||||
md.block.bbcode.ruler.push("quotes", rule);
|
md.block.bbcode.ruler.push("quotes", rule);
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.whiteList(["img[class=avatar]"]);
|
helper.allowList(["img[class=avatar]"]);
|
||||||
helper.whiteList({
|
helper.allowList({
|
||||||
custom(tag, name, value) {
|
custom(tag, name, value) {
|
||||||
if (tag === "aside" && name === "class") {
|
if (tag === "aside" && name === "class") {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -71,7 +71,7 @@ export const priority = 1;
|
||||||
export function setup(helper) {
|
export function setup(helper) {
|
||||||
const opts = helper.getOptions();
|
const opts = helper.getOptions();
|
||||||
if (opts.previewing) {
|
if (opts.previewing) {
|
||||||
helper.whiteList([
|
helper.allowList([
|
||||||
"span.image-wrapper",
|
"span.image-wrapper",
|
||||||
"span.button-wrapper",
|
"span.button-wrapper",
|
||||||
"span[class=scale-btn]",
|
"span[class=scale-btn]",
|
||||||
|
|
|
@ -10,7 +10,7 @@ export function setup(helper) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// we need a custom callback for style handling
|
// we need a custom callback for style handling
|
||||||
helper.whiteList({
|
helper.allowList({
|
||||||
custom: function (tag, attr, val) {
|
custom: function (tag, attr, val) {
|
||||||
if (tag !== "th" && tag !== "td") {
|
if (tag !== "th" && tag !== "td") {
|
||||||
return false;
|
return false;
|
||||||
|
@ -28,7 +28,7 @@ export function setup(helper) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.whiteList([
|
helper.allowList([
|
||||||
"table",
|
"table",
|
||||||
"tbody",
|
"tbody",
|
||||||
"thead",
|
"thead",
|
||||||
|
|
|
@ -91,10 +91,10 @@ function rule(state) {
|
||||||
export function setup(helper) {
|
export function setup(helper) {
|
||||||
const opts = helper.getOptions();
|
const opts = helper.getOptions();
|
||||||
if (opts.previewing) {
|
if (opts.previewing) {
|
||||||
helper.whiteList(["img.resizable"]);
|
helper.allowList(["img.resizable"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.whiteList([
|
helper.allowList([
|
||||||
"img[data-orig-src]",
|
"img[data-orig-src]",
|
||||||
"img[data-base62-sha1]",
|
"img[data-base62-sha1]",
|
||||||
"a[data-orig-href]",
|
"a[data-orig-href]",
|
||||||
|
|
|
@ -90,6 +90,7 @@ module PrettyText
|
||||||
|
|
||||||
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/get-url")
|
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/get-url")
|
||||||
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/object")
|
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/object")
|
||||||
|
apply_es6_file(ctx, root_path, "discourse-common/addon/lib/deprecated")
|
||||||
apply_es6_file(ctx, root_path, "discourse/app/lib/to-markdown")
|
apply_es6_file(ctx, root_path, "discourse/app/lib/to-markdown")
|
||||||
apply_es6_file(ctx, root_path, "discourse/app/lib/utilities")
|
apply_es6_file(ctx, root_path, "discourse/app/lib/utilities")
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ const rule = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function setup(helper) {
|
export function setup(helper) {
|
||||||
helper.whiteList([
|
helper.allowList([
|
||||||
"summary",
|
"summary",
|
||||||
"summary[title]",
|
"summary[title]",
|
||||||
"details",
|
"details",
|
||||||
|
|
|
@ -138,7 +138,7 @@ function closeBuffer(buffer, state, text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setup(helper) {
|
export function setup(helper) {
|
||||||
helper.whiteList([
|
helper.allowList([
|
||||||
"span.discourse-local-date",
|
"span.discourse-local-date",
|
||||||
"span[data-*]",
|
"span[data-*]",
|
||||||
"span[aria-label]",
|
"span[aria-label]",
|
||||||
|
|
|
@ -272,7 +272,7 @@ function newApiInit(helper) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setup(helper) {
|
export function setup(helper) {
|
||||||
helper.whiteList([
|
helper.allowList([
|
||||||
"div.poll",
|
"div.poll",
|
||||||
"div.poll-info",
|
"div.poll-info",
|
||||||
"div.poll-container",
|
"div.poll-container",
|
||||||
|
|
Loading…
Reference in New Issue