Compare commits
3 Commits
main
...
feature/al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92fecf39e4 | ||
|
|
2e430a161b | ||
|
|
6a0669e316 |
@ -7,9 +7,16 @@ 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._allowedHrefSchemes = options?.allowedHrefSchemes || [];
|
||||
this._allowedIframes = options?.allowedIframes || [];
|
||||
this._rawFeatures = [
|
||||
[
|
||||
"default",
|
||||
options?.htmlInlineAllowListOverride
|
||||
? options.htmlInlineAllowListOverride.concat(BASIC_LIST)
|
||||
: DEFAULT_LIST.concat(BASIC_LIST),
|
||||
],
|
||||
];
|
||||
|
||||
this._cache = null;
|
||||
|
||||
@ -110,6 +117,29 @@ export default class AllowLister {
|
||||
}
|
||||
}
|
||||
|
||||
const BASIC_LIST = [
|
||||
"em",
|
||||
"p",
|
||||
"strike",
|
||||
"strong",
|
||||
"blockquote",
|
||||
"br",
|
||||
"code",
|
||||
"pre",
|
||||
"ol",
|
||||
"ul",
|
||||
"img[alt]",
|
||||
"img[role]",
|
||||
"img[height]",
|
||||
"img[title]",
|
||||
"img[width]",
|
||||
"img[data-thumbnail]",
|
||||
// img[src] handled by sanitizer.js
|
||||
"li",
|
||||
"i",
|
||||
"b",
|
||||
];
|
||||
|
||||
// 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 = [
|
||||
@ -134,11 +164,7 @@ export const DEFAULT_LIST = [
|
||||
"audio",
|
||||
"audio[controls]",
|
||||
"audio[preload]",
|
||||
"b",
|
||||
"big",
|
||||
"blockquote",
|
||||
"br",
|
||||
"code",
|
||||
"dd",
|
||||
"del",
|
||||
"div",
|
||||
@ -155,7 +181,6 @@ export const DEFAULT_LIST = [
|
||||
"div[dir]",
|
||||
"dl",
|
||||
"dt",
|
||||
"em",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
@ -163,7 +188,6 @@ export const DEFAULT_LIST = [
|
||||
"h5",
|
||||
"h6",
|
||||
"hr",
|
||||
"i",
|
||||
"iframe",
|
||||
"iframe[frameborder]",
|
||||
"iframe[height]",
|
||||
@ -172,25 +196,14 @@ export const DEFAULT_LIST = [
|
||||
"iframe[width]",
|
||||
"iframe[allowfullscreen]",
|
||||
"iframe[allow]",
|
||||
"img[alt]",
|
||||
"img[role]",
|
||||
"img[height]",
|
||||
"img[title]",
|
||||
"img[width]",
|
||||
"img[data-thumbnail]",
|
||||
// img[src] handled by sanitizer.js
|
||||
"ins",
|
||||
"kbd",
|
||||
"li",
|
||||
"mark",
|
||||
"ol",
|
||||
"ol[reversed]",
|
||||
"ol[start]",
|
||||
"ol[type]",
|
||||
"p",
|
||||
"p[lang]",
|
||||
"picture",
|
||||
"pre",
|
||||
"s",
|
||||
"small",
|
||||
"span[lang]",
|
||||
@ -201,8 +214,6 @@ export const DEFAULT_LIST = [
|
||||
"span.placeholder-icon video",
|
||||
"span.hashtag",
|
||||
"span.mention",
|
||||
"strike",
|
||||
"strong",
|
||||
"sub",
|
||||
"sup",
|
||||
"source[data-orig-src]",
|
||||
@ -214,7 +225,6 @@ export const DEFAULT_LIST = [
|
||||
"track[kind]",
|
||||
// track[src] handled by sanitizer.js
|
||||
"track[srclang]",
|
||||
"ul",
|
||||
"video",
|
||||
// video[autoplay] handled by sanitizer.js
|
||||
"video[controls]",
|
||||
|
||||
@ -323,8 +323,18 @@ function buildCustomMarkdownCookFunction(engineOpts, defaultEngineOpts) {
|
||||
if (engineOpts.featuresOverride !== undefined) {
|
||||
overrideMarkdownFeatures(featureConfig, engineOpts.featuresOverride);
|
||||
}
|
||||
|
||||
newOpts.discourse.features = featureConfig;
|
||||
|
||||
// since the AllowLister is what decides which inline HTML to allow,
|
||||
// we need to completely recreate the sanitizer with a new AllowLister
|
||||
// with the override for this to work
|
||||
if (engineOpts.htmlInlineAllowListOverride !== undefined) {
|
||||
newOpts.discourse.htmlInlineAllowListOverride =
|
||||
engineOpts.htmlInlineAllowListOverride;
|
||||
newOpts.sanitizer = createSanitizer(newOpts);
|
||||
}
|
||||
|
||||
const markdownitOpts = {
|
||||
discourse: newOpts.discourse,
|
||||
html: defaultEngineOpts.engine.options.html,
|
||||
@ -401,18 +411,22 @@ function setupMarkdownEngine(opts, featureConfig) {
|
||||
opts.setup = true;
|
||||
|
||||
if (!opts.discourse.sanitizer || !opts.sanitizer) {
|
||||
const allowLister = new AllowLister(opts.discourse);
|
||||
|
||||
opts.allowListed.forEach(([feature, info]) => {
|
||||
allowLister.allowListFeature(feature, info);
|
||||
});
|
||||
|
||||
opts.sanitizer = opts.discourse.sanitizer = !!opts.discourse.sanitize
|
||||
? (a) => sanitize(a, allowLister)
|
||||
: (a) => a;
|
||||
opts.sanitizer = createSanitizer(opts);
|
||||
}
|
||||
}
|
||||
|
||||
function createSanitizer(opts) {
|
||||
const allowLister = new AllowLister(opts.discourse);
|
||||
|
||||
opts.allowListed.forEach(([feature, info]) => {
|
||||
allowLister.allowListFeature(feature, info);
|
||||
});
|
||||
|
||||
return (opts.discourse.sanitizer = !!opts.discourse.sanitize
|
||||
? (a) => sanitize(a, allowLister)
|
||||
: (a) => a);
|
||||
}
|
||||
|
||||
function unhoistForCooked(hoisted, cooked) {
|
||||
const keys = Object.keys(hoisted);
|
||||
if (keys.length) {
|
||||
|
||||
@ -49,6 +49,7 @@ export function buildOptions(state) {
|
||||
hashtagTypesInPriorityOrder,
|
||||
hashtagIcons,
|
||||
hashtagLookup,
|
||||
htmlInlineAllowListOverride,
|
||||
} = state;
|
||||
|
||||
let features = {};
|
||||
@ -94,6 +95,7 @@ export function buildOptions(state) {
|
||||
hashtagTypesInPriorityOrder,
|
||||
hashtagIcons,
|
||||
hashtagLookup,
|
||||
htmlInlineAllowListOverride,
|
||||
};
|
||||
|
||||
// note, this will mutate options due to the way the API is designed
|
||||
|
||||
@ -211,6 +211,10 @@ module PrettyText
|
||||
|
||||
buffer << "__optInput.topicId = #{opts[:topic_id].to_i};\n" if opts[:topic_id]
|
||||
|
||||
if opts[:html_inline_allow_list_override]
|
||||
buffer << "__optInput.htmlInlineAllowListOverride = #{opts[:html_inline_allow_list_override].to_json};\n"
|
||||
end
|
||||
|
||||
if opts[:force_quote_link]
|
||||
buffer << "__optInput.forceQuoteLink = #{opts[:force_quote_link]};\n"
|
||||
end
|
||||
|
||||
@ -144,6 +144,8 @@ class ChatMessage < ActiveRecord::Base
|
||||
where("cooked_version <> ? or cooked_version IS NULL", BAKED_VERSION)
|
||||
end
|
||||
|
||||
HTML_INLINE_ALLOW_LIST_OVERRIDE = %w[kbd]
|
||||
|
||||
MARKDOWN_FEATURES = %w[
|
||||
anchor
|
||||
bbcode-block
|
||||
@ -183,6 +185,7 @@ class ChatMessage < ActiveRecord::Base
|
||||
strikethrough
|
||||
blockquote
|
||||
emphasis
|
||||
html_inline
|
||||
]
|
||||
|
||||
def self.cook(message, opts = {})
|
||||
@ -200,6 +203,7 @@ class ChatMessage < ActiveRecord::Base
|
||||
force_quote_link: true,
|
||||
user_id: opts[:user_id],
|
||||
hashtag_context: "chat-composer",
|
||||
html_inline_allow_list_override: HTML_INLINE_ALLOW_LIST_OVERRIDE,
|
||||
)
|
||||
|
||||
result =
|
||||
|
||||
@ -133,6 +133,9 @@ export default class Chat extends Service {
|
||||
markdownItRules: Site.currentProp(
|
||||
"markdown_additional_options.chat.limited_pretty_text_markdown_rules"
|
||||
),
|
||||
htmlInlineAllowListOverride: Site.currentProp(
|
||||
"markdown_additional_options.chat.html_inline_allow_list_override"
|
||||
),
|
||||
hashtagTypesInPriorityOrder:
|
||||
this.site.hashtag_configurations["chat-composer"],
|
||||
hashtagIcons: this.site.hashtag_icons,
|
||||
|
||||
@ -251,6 +251,8 @@ export function setup(helper) {
|
||||
hashtagTypesInPriorityOrder:
|
||||
chatAdditionalOpts.hashtag_configurations["chat-composer"],
|
||||
hashtagIcons: opts.discourse.hashtagIcons,
|
||||
htmlInlineAllowListOverride:
|
||||
chatAdditionalOpts.html_inline_allow_list_override,
|
||||
},
|
||||
(customCookFn) => {
|
||||
customMarkdownCookFn = customCookFn;
|
||||
|
||||
@ -739,6 +739,7 @@ after_initialize do
|
||||
limited_pretty_text_features: ChatMessage::MARKDOWN_FEATURES,
|
||||
limited_pretty_text_markdown_rules: ChatMessage::MARKDOWN_IT_RULES,
|
||||
hashtag_configurations: HashtagAutocompleteService.contexts_with_ordered_types,
|
||||
html_inline_allow_list_override: ChatMessage::HTML_INLINE_ALLOW_LIST_OVERRIDE,
|
||||
}
|
||||
|
||||
register_user_destroyer_on_content_deletion_callback(
|
||||
|
||||
Reference in New Issue
Block a user