Compare commits

...
This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.

3 Commits

Author SHA1 Message Date
Martin Brennan
92fecf39e4 DEV: More testing 2023-01-27 14:42:29 +10:00
Martin Brennan
2e430a161b DEV: Make sure existing feature allowlists are respected 2023-01-25 15:30:30 +10:00
Martin Brennan
6a0669e316
WIP: Allow kbd tag chat 2023-01-25 13:43:25 +10:00
8 changed files with 72 additions and 32 deletions

View File

@ -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]",

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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 =

View File

@ -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,

View File

@ -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;

View File

@ -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(