mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-11 18:24:43 +02:00
Electron: Fixed security issue by enabling contextIsolation and proxying IPC messages via preload script
This commit is contained in:
parent
0a2b83998c
commit
72af564382
@ -1404,6 +1404,7 @@ class NoteTextComponent extends React.Component {
|
|||||||
style={viewerStyle}
|
style={viewerStyle}
|
||||||
preload="gui/note-viewer/preload.js"
|
preload="gui/note-viewer/preload.js"
|
||||||
src="gui/note-viewer/index.html"
|
src="gui/note-viewer/index.html"
|
||||||
|
webpreferences="contextIsolation"
|
||||||
ref={(elem) => { this.webview_ref(elem); } }
|
ref={(elem) => { this.webview_ref(elem); } }
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -36,6 +36,22 @@
|
|||||||
<script>
|
<script>
|
||||||
const contentElement = document.getElementById('content');
|
const contentElement = document.getElementById('content');
|
||||||
|
|
||||||
|
const ipc = {};
|
||||||
|
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
// Here we only deal with messages that are sent from the main Electro process to the webview.
|
||||||
|
if (!event.data || event.data.target !== 'webview') return;
|
||||||
|
|
||||||
|
const callName = event.data.name;
|
||||||
|
const callData = event.data.data;
|
||||||
|
|
||||||
|
if (!ipc[callName]) {
|
||||||
|
console.warn('Missing IPC function:', event.data);
|
||||||
|
} else {
|
||||||
|
ipc[callName](callData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Handle dynamically loading HLJS when a code element is present
|
// Handle dynamically loading HLJS when a code element is present
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
@ -119,7 +135,9 @@
|
|||||||
setPercentScroll(percentScroll_);
|
setPercentScroll(percentScroll_);
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcRenderer.on('setHtml', (event, html) => {
|
ipc.setHtml = (event) => {
|
||||||
|
const html = event.html;
|
||||||
|
|
||||||
updateBodyHeight();
|
updateBodyHeight();
|
||||||
|
|
||||||
contentElement.innerHTML = html;
|
contentElement.innerHTML = html;
|
||||||
@ -158,10 +176,12 @@
|
|||||||
}
|
}
|
||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
let ignoreNextScrollEvent = false;
|
let ignoreNextScrollEvent = false;
|
||||||
ipcRenderer.on('setPercentScroll', (event, percent) => {
|
ipc.setPercentScroll = (event) => {
|
||||||
|
const percent = event.percent;
|
||||||
|
|
||||||
if (checkScrollIID_) {
|
if (checkScrollIID_) {
|
||||||
clearInterval(checkScrollIID_);
|
clearInterval(checkScrollIID_);
|
||||||
checkScrollIID_ = null;
|
checkScrollIID_ = null;
|
||||||
@ -169,7 +189,7 @@
|
|||||||
|
|
||||||
ignoreNextScrollEvent = true;
|
ignoreNextScrollEvent = true;
|
||||||
setPercentScroll(percent);
|
setPercentScroll(percent);
|
||||||
});
|
}
|
||||||
|
|
||||||
let mark_ = null;
|
let mark_ = null;
|
||||||
function setMarkers(keywords) {
|
function setMarkers(keywords) {
|
||||||
@ -184,7 +204,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let markLoaded_ = false;
|
let markLoaded_ = false;
|
||||||
ipcRenderer.on('setMarkers', (event, keywords) => {
|
ipc.setMarkers = (event) => {
|
||||||
|
const keywords = event.keywords;
|
||||||
|
|
||||||
if (!keywords.length && !markLoaded_) return;
|
if (!keywords.length && !markLoaded_) return;
|
||||||
|
|
||||||
if (!markLoaded_) {
|
if (!markLoaded_) {
|
||||||
@ -199,7 +221,7 @@
|
|||||||
} else {
|
} else {
|
||||||
setMarkers(keywords);
|
setMarkers(keywords);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
function maxScrollTop() {
|
function maxScrollTop() {
|
||||||
return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
|
return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
|
||||||
@ -210,6 +232,10 @@
|
|||||||
document.getElementById('body').style.height = window.innerHeight + 'px';
|
document.getElementById('body').style.height = window.innerHeight + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ipcProxySendToHost = (methodName, arg) => {
|
||||||
|
window.postMessage({ target: 'main', name: methodName, args: [ arg ] }, '*');
|
||||||
|
}
|
||||||
|
|
||||||
contentElement.addEventListener('scroll', function(e) {
|
contentElement.addEventListener('scroll', function(e) {
|
||||||
if (ignoreNextScrollEvent) {
|
if (ignoreNextScrollEvent) {
|
||||||
ignoreNextScrollEvent = false;
|
ignoreNextScrollEvent = false;
|
||||||
@ -218,7 +244,8 @@
|
|||||||
const m = maxScrollTop();
|
const m = maxScrollTop();
|
||||||
const percent = m ? contentElement.scrollTop / m : 0;
|
const percent = m ? contentElement.scrollTop / m : 0;
|
||||||
setPercentScroll(percent);
|
setPercentScroll(percent);
|
||||||
ipcRenderer.sendToHost('percentScroll', percent);
|
|
||||||
|
ipcProxySendToHost('percentScroll', percent);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('contextmenu', function(event) {
|
document.addEventListener('contextmenu', function(event) {
|
||||||
@ -228,7 +255,7 @@
|
|||||||
if (element && !element.getAttribute('data-resource-id')) element = element.parentElement;
|
if (element && !element.getAttribute('data-resource-id')) element = element.parentElement;
|
||||||
|
|
||||||
if (element && element.getAttribute('data-resource-id')) {
|
if (element && element.getAttribute('data-resource-id')) {
|
||||||
ipcRenderer.sendToHost('contextMenu', {
|
ipcProxySendToHost('contextMenu', {
|
||||||
type: element.getAttribute('src') ? 'image' : 'resource',
|
type: element.getAttribute('src') ? 'image' : 'resource',
|
||||||
resourceId: element.getAttribute('data-resource-id'),
|
resourceId: element.getAttribute('data-resource-id'),
|
||||||
});
|
});
|
||||||
@ -236,12 +263,12 @@
|
|||||||
const selectedText = window.getSelection().toString();
|
const selectedText = window.getSelection().toString();
|
||||||
|
|
||||||
if (selectedText) {
|
if (selectedText) {
|
||||||
ipcRenderer.sendToHost('contextMenu', {
|
ipcProxySendToHost('contextMenu', {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
textToCopy: selectedText,
|
textToCopy: selectedText,
|
||||||
});
|
});
|
||||||
} else if (event.target.getAttribute('href')) {
|
} else if (event.target.getAttribute('href')) {
|
||||||
ipcRenderer.sendToHost('contextMenu', {
|
ipcProxySendToHost('contextMenu', {
|
||||||
type: 'link',
|
type: 'link',
|
||||||
textToCopy: event.target.getAttribute('href'),
|
textToCopy: event.target.getAttribute('href'),
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,36 @@
|
|||||||
// Define here Electron objects that need to be accessed from the WebView
|
// In order to give access to the webview to certain functions of the main process, we need
|
||||||
// https://github.com/electron/electron/blob/master/docs/tutorial/security.md#2-disable-nodejs-integration-for-remote-content
|
// this bridge which listens from the main process and sends to the webview and the other
|
||||||
|
// way around. This is necessary after having enabled the "contextIsolation" option, which
|
||||||
|
// prevents the webview from accessing low-level methods in the main process.
|
||||||
|
|
||||||
window.ipcRenderer = require('electron').ipcRenderer;
|
const ipcRenderer = require('electron').ipcRenderer;
|
||||||
|
|
||||||
|
ipcRenderer.on('setHtml', (event, html) => {
|
||||||
|
window.postMessage({ target: 'webview', name: 'setHtml', data: { html: html } }, '*');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('setPercentScroll', (event, percent) => {
|
||||||
|
window.postMessage({ target: 'webview', name: 'setPercentScroll', data: { percent: percent } }, '*');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('setMarkers', (event, keywords) => {
|
||||||
|
window.postMessage({ target: 'webview', name: 'setMarkers', data: { keywords: keywords } }, '*');
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
// Here we only deal with messages that are sent from the webview to the main Electron process
|
||||||
|
if (!event.data || event.data.target !== 'main') return;
|
||||||
|
|
||||||
|
const callName = event.data.name;
|
||||||
|
const args = event.data.args;
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
ipcRenderer.sendToHost(callName);
|
||||||
|
} else if (args.length === 1) {
|
||||||
|
ipcRenderer.sendToHost(callName, args[0]);
|
||||||
|
} else if (args.length === 2) {
|
||||||
|
ipcRenderer.sendToHost(callName, args[1]);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported number of args');
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user