Skip to content

Commit

Permalink
MDL-78714 editor_tiny: Add xss_sanitize option to TinyMCE
Browse files Browse the repository at this point in the history
To address a potential data loss issue, a feature introduced in TinyMCE
6.4.0 to disable client-side XSS sanitisation must be backported.
  • Loading branch information
andrewnicols committed Aug 9, 2023
1 parent 3c9a558 commit 8e58a8f
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 15 deletions.
7 changes: 6 additions & 1 deletion lib/editor/tiny/js/tinymce/plugins/media/plugin.js
Expand Up @@ -1044,8 +1044,13 @@
};

const parseAndSanitize = (editor, context, html) => {
const getEditorOption = editor.options.get;
const sanitize = getEditorOption('xss_sanitization');
const validate = shouldFilterHtml(editor);
return Parser(editor.schema, { validate }).parse(html, { context });
return Parser(editor.schema, {
sanitize,
validate
}).parse(html, { context });
};

const setup$1 = editor => {
Expand Down
2 changes: 1 addition & 1 deletion lib/editor/tiny/js/tinymce/plugins/media/plugin.min.js

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions lib/editor/tiny/js/tinymce/readme_moodle.md
Expand Up @@ -5,6 +5,13 @@ Please note that we have a clone of the official TinyMCE repository which contai
Each Moodle branch has a similar branch in the https://github.com/moodlehq/tinymce.
The Moodle `master` branch is named as the upcoming STABLE branch name, for example during the development of Moodle 4.2.0, the upcoming STABLE branch name will be MOODLE_402_STABLE.

## Patches included in this release

- MDL-78714: Add support for disabling XSS Sanitisation (TINY-9600)

Please note: TinyMCE issue numbers are related to bugs in their private issue
tracker. See git history of their repository for relevant information.

## Upgrade procedure for TinyMCE Editor

1. Store an environment variable to the Tiny directory in the Moodle repository (the current directory).
Expand Down
9 changes: 6 additions & 3 deletions lib/editor/tiny/js/tinymce/tinymce.d.ts
Expand Up @@ -1300,18 +1300,19 @@ interface DomParserSettings {
allow_html_in_named_anchor?: boolean;
allow_script_urls?: boolean;
allow_unsafe_link_target?: boolean;
blob_cache?: BlobCache;
convert_fonts_to_spans?: boolean;
document?: Document;
fix_list_elements?: boolean;
font_size_legacy_values?: string;
forced_root_block?: boolean | string;
forced_root_block_attrs?: Record<string, string>;
inline_styles?: boolean;
preserve_cdata?: boolean;
remove_trailing_brs?: boolean;
root_name?: string;
sanitize?: boolean;
validate?: boolean;
inline_styles?: boolean;
blob_cache?: BlobCache;
document?: Document;
}
interface DomParser {
schema: Schema;
Expand Down Expand Up @@ -1904,6 +1905,7 @@ interface BaseEditorOptions {
visual_anchor_class?: string;
visual_table_class?: string;
width?: number | string;
xss_sanitization?: boolean;
disable_nodechange?: boolean;
forced_plugins?: string | string[];
plugin_base_urls?: Record<string, string>;
Expand Down Expand Up @@ -1993,6 +1995,7 @@ interface EditorOptions extends NormalizedEditorOptions {
visual_anchor_class: string;
visual_table_class: string;
width: number | string;
xss_sanitization: boolean;
}
declare type StyleMap = Record<string, string | number>;
interface StylesSettings {
Expand Down
28 changes: 19 additions & 9 deletions lib/editor/tiny/js/tinymce/tinymce.js
Expand Up @@ -7124,6 +7124,10 @@
processor: 'boolean',
default: true
});
registerOption('xss_sanitization', {
processor: 'boolean',
default: true
});
editor.on('ScriptsLoaded', () => {
registerOption('directionality', {
processor: 'string',
Expand Down Expand Up @@ -7223,6 +7227,7 @@
const getEditableClass = option('editable_class');
const getNonEditableRegExps = option('noneditable_regexp');
const shouldPreserveCData = option('preserve_cdata');
const shouldSanitizeXss = option('xss_sanitization');
const hasTextPatternsLookup = editor => editor.options.isSet('text_patterns_lookup');
const getFontStyleValues = editor => Tools.explode(editor.options.get('font_size_style_values'));
const getFontSizeClasses = editor => Tools.explode(editor.options.get('font_size_classes'));
Expand Down Expand Up @@ -16725,6 +16730,7 @@
const defaultedSettings = {
validate: true,
root_name: 'body',
sanitize: true,
...settings
};
const parser = new DOMParser();
Expand All @@ -16735,8 +16741,10 @@
const content = isSpecialRoot ? `<${ rootName }>${ html }</${ rootName }>` : html;
const wrappedHtml = format === 'xhtml' ? `<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>${ content }</body></html>` : `<body>${ content }</body>`;
const body = parser.parseFromString(wrappedHtml, mimeType).body;
purify.sanitize(body, getPurifyConfig(defaultedSettings, mimeType));
purify.removed = [];
if (defaultedSettings.sanitize) {
purify.sanitize(body, getPurifyConfig(defaultedSettings, mimeType));
purify.removed = [];
}
return isSpecialRoot ? body.firstChild : body;
};
const addNodeFilter = nodeFilterRegistry.addFilter;
Expand Down Expand Up @@ -16855,7 +16863,7 @@
};

const serializeContent = content => isTreeNode(content) ? HtmlSerializer({ validate: false }).serialize(content) : content;
const withSerializedContent = (content, fireEvent) => {
const withSerializedContent = (content, fireEvent, sanitize) => {
const serializedContent = serializeContent(content);
const eventArgs = fireEvent(serializedContent);
if (eventArgs.isDefaultPrevented()) {
Expand All @@ -16864,7 +16872,8 @@
if (eventArgs.content !== serializedContent) {
const rootNode = DomParser({
validate: false,
forced_root_block: false
forced_root_block: false,
sanitize
}).parse(eventArgs.content, { context: content.name });
return {
...eventArgs,
Expand Down Expand Up @@ -16899,10 +16908,10 @@
if (args.no_events) {
return content;
} else {
const processedEventArgs = withSerializedContent(content, c => fireGetContent(editor, {
const processedEventArgs = withSerializedContent(content, content => fireGetContent(editor, {
...args,
content: c
}));
content
}), shouldSanitizeXss(editor));
return processedEventArgs.content;
}
};
Expand All @@ -16913,7 +16922,7 @@
const processedEventArgs = withSerializedContent(args.content, content => fireBeforeSetContent(editor, {
...args,
content
}));
}), shouldSanitizeXss(editor));
if (processedEventArgs.isDefaultPrevented()) {
fireSetContent(editor, processedEventArgs);
return Result.error(undefined);
Expand Down Expand Up @@ -24566,7 +24575,7 @@
};

const preProcess = (editor, html) => {
const parser = DomParser({}, editor.schema);
const parser = DomParser({ sanitize: shouldSanitizeXss(editor) }, editor.schema);
parser.addNodeFilter('meta', nodes => {
Tools.each(nodes, node => {
node.remove();
Expand Down Expand Up @@ -26859,6 +26868,7 @@
remove_trailing_brs: getOption('remove_trailing_brs'),
inline_styles: getOption('inline_styles'),
root_name: getRootName(editor),
sanitize: getOption('xss_sanitization'),
validate: true,
blob_cache: blobCache,
document: editor.getDoc()
Expand Down
2 changes: 1 addition & 1 deletion lib/editor/tiny/js/tinymce/tinymce.min.js

Large diffs are not rendered by default.

0 comments on commit 8e58a8f

Please sign in to comment.