diff --git a/js/preload/default.js b/js/preload/default.js index 7b767285f..665403823 100644 --- a/js/preload/default.js +++ b/js/preload/default.js @@ -61,4 +61,20 @@ window.addEventListener('message', function (e) { if (e.data?.message === 'downloadFile') { ipc.send('downloadFile', e.data.url) } + + if (e.data?.message === 'readLocalPDF') { + var requestId = e.data.requestId + + if (typeof e.data.url !== 'string') { + window.postMessage({ message: 'readLocalPDFResult', requestId, error: 'invalid PDF URL' }, 'min://app') + return + } + + ipc.invoke('readLocalPDF', e.data.url).then(function (fileData) { + var data = new Uint8Array(fileData) + window.postMessage({ message: 'readLocalPDFResult', requestId, data: data.buffer }, 'min://app') + }).catch(function (err) { + window.postMessage({ message: 'readLocalPDFResult', requestId, error: err?.message || 'unable to read local PDF' }, 'min://app') + }) + } }) diff --git a/main/download.js b/main/download.js index 0400bbb5a..e82092a0b 100644 --- a/main/download.js +++ b/main/download.js @@ -1,5 +1,64 @@ const currrentDownloadItems = {} +function isPDFViewerURL (url) { + return typeof url === 'string' && url.startsWith('min://app/pages/pdfViewer/index.html') +} + +function getViewerSourceURL (viewerURL) { + if (!isPDFViewerURL(viewerURL)) { + return null + } + + try { + var sourceURL = new URLSearchParams(new URL(viewerURL).search).get('url') + if (!sourceURL) { + return null + } + + return new URL(sourceURL) + } catch (e) { + return null + } +} + +function getAllowedLocalPDFPath (senderFrameURL, requestedURL) { + if (!isPDFViewerURL(senderFrameURL)) { + throw new Error('blocked local PDF read request') + } + + if (typeof requestedURL !== 'string') { + throw new Error('invalid PDF URL') + } + + var requestedFileURL + try { + requestedFileURL = new URL(requestedURL) + } catch (e) { + throw new Error('invalid PDF URL') + } + + if (requestedFileURL.protocol !== 'file:') { + throw new Error('invalid PDF URL protocol') + } + + var sourceURL = getViewerSourceURL(senderFrameURL) + + if (!sourceURL || sourceURL.protocol !== 'file:' || sourceURL.toString() !== requestedFileURL.toString()) { + throw new Error('blocked local PDF read request') + } + + return require('url').fileURLToPath(requestedFileURL) +} + +ipc.handle('readLocalPDF', async function (event, requestedURL) { + if (!event.senderFrame) { + throw new Error('blocked local PDF read request') + } + + var filePath = getAllowedLocalPDFPath(event.senderFrame.url, requestedURL) + return fs.promises.readFile(filePath) +}) + ipc.on('cancelDownload', function (e, path) { if (currrentDownloadItems[path]) { currrentDownloadItems[path].cancel() diff --git a/pages/pdfViewer/viewer.js b/pages/pdfViewer/viewer.js index cc6106b17..625e308b3 100644 --- a/pages/pdfViewer/viewer.js +++ b/pages/pdfViewer/viewer.js @@ -4,6 +4,7 @@ import '../../node_modules/pdfjs-dist/web/pdf_viewer.mjs' pdfjsLib.GlobalWorkerOptions.workerSrc = '../../node_modules/pdfjs-dist/build/pdf.worker.mjs' var url = new URLSearchParams(window.location.search.replace('?', '')).get('url') +var localPDFRequestId = 0 var eventBus = new pdfjsViewer.EventBus() @@ -239,7 +240,61 @@ function setUpPageAnnotationLayer (pageView) { } } -pdfjsLib.getDocument({ url: url, withCredentials: true }).promise.then(async function (_pdf) { +function requestLocalPDFData (fileURL) { + return new Promise(function (resolve, reject) { + var requestId = ++localPDFRequestId + + function onResponse (e) { + if (e.origin !== 'min://app') { + return + } + + if (e.data?.message !== 'readLocalPDFResult' || e.data.requestId !== requestId) { + return + } + + window.removeEventListener('message', onResponse) + + if (e.data.error) { + reject(new Error(e.data.error)) + return + } + + if (!e.data.data) { + reject(new Error('missing local PDF data')) + return + } + + resolve(new Uint8Array(e.data.data)) + } + + window.addEventListener('message', onResponse) + window.postMessage({ message: 'readLocalPDF', requestId, url: fileURL }, 'min://app') + }) +} + +function getPDFDocumentRequest (targetURL, requestLocalPDFData) { + if (typeof targetURL !== 'string') { + return Promise.reject(new Error('invalid PDF URL')) + } + + if (targetURL.startsWith('file://')) { + return requestLocalPDFData(targetURL).then(function (fileData) { + return { + data: fileData + } + }) + } + + return Promise.resolve({ + url: targetURL, + withCredentials: true + }) +} + +getPDFDocumentRequest(url, requestLocalPDFData).then(function (request) { + return pdfjsLib.getDocument(request).promise +}).then(async function (_pdf) { pdf = _pdf pageCount = pdf.numPages