You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-11-29 22:48:10 +02:00
Desktop: Rich Text Editor: Disallow inline event handlers (#12106)
This commit is contained in:
@@ -43,6 +43,7 @@ import useKeyboardRefocusHandler from './utils/useKeyboardRefocusHandler';
|
||||
import useDocument from '../../../hooks/useDocument';
|
||||
import useEditDialog from './utils/useEditDialog';
|
||||
import useEditDialogEventListeners from './utils/useEditDialogEventListeners';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import useTextPatternsLookup from './utils/useTextPatternsLookup';
|
||||
|
||||
const logger = Logger.create('TinyMCE');
|
||||
@@ -728,6 +729,25 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
language_url: ['en_US', 'en_GB'].includes(language) ? undefined : `${bridge().vendorDir()}/lib/tinymce/langs/${language}`,
|
||||
toolbar: toolbar.join(' '),
|
||||
localization_function: _,
|
||||
// See https://www.tiny.cloud/docs/tinymce/latest/tinymce-and-csp/#content_security_policy
|
||||
content_security_policy: Setting.value('featureFlag.richText.useStrictContentSecurityPolicy') ? [
|
||||
// Media: *: Allow users to include images and videos from the internet (e.g. ).
|
||||
// Media: blob: Allow loading images/videos/audio from blob URLs. The Rich Text Editor
|
||||
// replaces certain base64 URLs with blob URLs.
|
||||
// Media: data: Allow loading images and other media from data: URLs
|
||||
'default-src \'self\'',
|
||||
'img-src \'self\' blob: data: *', // Images
|
||||
'media-src \'self\' blob: data: *', // Audio and video players
|
||||
|
||||
// Disallow certain unused features
|
||||
'child-src \'none\'', // Should not contain sub-frames
|
||||
'object-src \'none\'', // Objects can be used for script injection
|
||||
'form-action \'none\'', // No submitting forms
|
||||
|
||||
// Styles: unsafe-inline: TinyMCE uses inline style="" styles.
|
||||
// Styles: *: Allow users to include styles from the internet (e.g. <style src="https://example.com/style.css">)
|
||||
'style-src \'self\' \'unsafe-inline\' * data:',
|
||||
].join(' ; ') : undefined,
|
||||
contextmenu: false,
|
||||
browser_spellcheck: true,
|
||||
|
||||
|
||||
@@ -2,72 +2,37 @@ import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import { useEffect } from 'react';
|
||||
import { Editor } from 'tinymce';
|
||||
|
||||
const useWebViewApi = (editor: Editor, window: Window) => {
|
||||
interface WebViewApi {
|
||||
postMessage: (contentScriptId: string, message: unknown)=> Promise<unknown>;
|
||||
}
|
||||
|
||||
interface ExtendedWindow extends Window {
|
||||
webviewApi: WebViewApi;
|
||||
}
|
||||
|
||||
const useWebViewApi = (editor: Editor, containerWindow: Window) => {
|
||||
useEffect(() => {
|
||||
if (!editor) return ()=>{};
|
||||
if (!window) return ()=>{};
|
||||
if (!containerWindow) return ()=>{};
|
||||
|
||||
const scriptElement = window.document.createElement('script');
|
||||
const channelId = `plugin-post-message-${Math.random()}`;
|
||||
scriptElement.appendChild(window.document.createTextNode(`
|
||||
window.webviewApi = {
|
||||
postMessage: (contentScriptId, message) => {
|
||||
const channelId = ${JSON.stringify(channelId)};
|
||||
const messageId = Math.random();
|
||||
window.parent.postMessage({
|
||||
channelId,
|
||||
messageId,
|
||||
contentScriptId,
|
||||
message,
|
||||
}, '*');
|
||||
|
||||
const waitForResponse = async () => {
|
||||
while (true) {
|
||||
const messageEvent = await new Promise(resolve => {
|
||||
window.addEventListener('message', event => {
|
||||
resolve(event);
|
||||
}, {once: true});
|
||||
});
|
||||
|
||||
if (messageEvent.source !== window.parent || messageEvent.data.messageId !== messageId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = messageEvent.data;
|
||||
return data.response;
|
||||
}
|
||||
};
|
||||
|
||||
return waitForResponse();
|
||||
},
|
||||
};
|
||||
`));
|
||||
const editorWindow = editor.getWin();
|
||||
editorWindow.document.head.appendChild(scriptElement);
|
||||
|
||||
const onMessageHandler = async (event: MessageEvent) => {
|
||||
if (event.source !== editorWindow || event.data.channelId !== channelId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentScriptId = event.data.contentScriptId;
|
||||
const pluginService = PluginService.instance();
|
||||
const plugin = pluginService.pluginById(
|
||||
pluginService.pluginIdByContentScriptId(contentScriptId),
|
||||
);
|
||||
const result = await plugin.emitContentScriptMessage(contentScriptId, event.data.message);
|
||||
editorWindow.postMessage({
|
||||
messageId: event.data.messageId,
|
||||
response: result,
|
||||
}, '*');
|
||||
const editorWindow = editor.getWin() as ExtendedWindow;
|
||||
const webviewApi: WebViewApi = {
|
||||
postMessage: async (contentScriptId: string, message: unknown) => {
|
||||
const pluginService = PluginService.instance();
|
||||
const plugin = pluginService.pluginById(
|
||||
pluginService.pluginIdByContentScriptId(contentScriptId),
|
||||
);
|
||||
return await plugin.emitContentScriptMessage(contentScriptId, message);
|
||||
},
|
||||
};
|
||||
window.addEventListener('message', onMessageHandler);
|
||||
editorWindow.webviewApi = webviewApi;
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', onMessageHandler);
|
||||
scriptElement.remove();
|
||||
if (editorWindow.webviewApi === webviewApi) {
|
||||
editorWindow.webviewApi = undefined;
|
||||
}
|
||||
};
|
||||
}, [editor, window]);
|
||||
}, [editor, containerWindow]);
|
||||
};
|
||||
|
||||
export default useWebViewApi;
|
||||
|
||||
Reference in New Issue
Block a user