From 9b7d3edbb34ccc71025ee2ffb737a01fa488052f Mon Sep 17 00:00:00 2001 From: Abe Jellinek Date: Mon, 6 May 2024 12:02:29 -0400 Subject: [PATCH] HiddenBrowser: Block all downloads --- chrome/content/zotero/HiddenBrowser.jsm | 67 ++++++++++++------- .../content/zotero/actors/PageDataChild.jsm | 34 ++++++++++ 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/chrome/content/zotero/HiddenBrowser.jsm b/chrome/content/zotero/HiddenBrowser.jsm index 887bae53c6..49c7cd0856 100644 --- a/chrome/content/zotero/HiddenBrowser.jsm +++ b/chrome/content/zotero/HiddenBrowser.jsm @@ -144,38 +144,51 @@ class HiddenBrowser { */ async load(source, options) { await this._createdPromise; - let url; + let uri; if (/^(file|https?|chrome|resource|blob|data):/.test(source)) { - url = source; + uri = source; } // Convert string path to file: URL else { - url = Zotero.File.pathToFileURI(source); + uri = Zotero.File.pathToFileURI(source); } - Zotero.debug(`Loading ${url} in hidden browser`); + Zotero.debug(`Loading ${uri} in hidden browser`); // Next bit adapted from Mozilla's HeadlessShell.jsm - const principal = Services.scriptSecurityManager.getSystemPrincipal(); try { - await new Promise((resolve, reject) => { + // Figure out whether the browser should be remote. We actually + // perform the load in PageDataChild, but remoteness changes + // need to happen here. + let oa = E10SUtils.predictOriginAttributes({ browser: this }); + let remoteType = E10SUtils.getRemoteTypeForURI( + uri, + true, + false, + E10SUtils.DEFAULT_REMOTE_TYPE, + null, + oa + ); + if (this.remoteType !== remoteType) { + // The following functions need to be called on the directly, + // not through our proxy (aka 'this') + if (remoteType === E10SUtils.NOT_REMOTE) { + this._browser.removeAttribute("remote"); + this._browser.removeAttribute("remoteType"); + } + else { + this._browser.setAttribute("remote", "true"); + this._browser.setAttribute("remoteType", remoteType); + } + this._browser.changeRemoteness({ remoteType }); + this._browser.construct(); + } + + let loadCompletePromise = new Promise((resolve, reject) => { // Avoid a hang if page is never loaded for some reason setTimeout(function () { reject(new Error("Page never loaded in hidden browser")); }, 5000); - let oa = E10SUtils.predictOriginAttributes({ browser: this }); - let loadURIOptions = { - triggeringPrincipal: principal, - remoteType: E10SUtils.getRemoteTypeForURI( - url, - true, - false, - E10SUtils.DEFAULT_REMOTE_TYPE, - null, - oa - ) - }; - this.loadURI(Services.io.newURI(url), loadURIOptions); let { webProgress } = this; let progressListener = { @@ -189,7 +202,7 @@ class HiddenBrowser { return; } // Ignore the initial about:blank, unless about:blank is requested - if (location.spec == "about:blank" && url != "about:blank") { + if (location.spec == "about:blank" && uri != "about:blank") { return; } progressListeners.delete(progressListener); @@ -208,6 +221,14 @@ class HiddenBrowser { Ci.nsIWebProgress.NOTIFY_LOCATION ); }); + + let loadURISuccess = await this.browsingContext.currentWindowGlobal.getActor("PageData") + .sendQuery("loadURI", { uri }); + if (!loadURISuccess) { + Zotero.logError(new Error("Load failed")); + return false; + } + await loadCompletePromise; } catch (e) { Zotero.logError(e); @@ -218,15 +239,15 @@ class HiddenBrowser { let { channelInfo } = await this.getPageData(['channelInfo']); if (channelInfo && (channelInfo.responseStatus < 200 || channelInfo.responseStatus >= 400)) { let response = `${channelInfo.responseStatus} ${channelInfo.responseStatusText}`; - Zotero.debug(`HiddenBrowser.load: ${url} failed with ${response}`, 2); + Zotero.debug(`HiddenBrowser.load: ${uri} failed with ${response}`, 2); // HiddenBrowser will never get returned so we need to clean it up here this.destroy() throw new Zotero.HTTP.UnexpectedStatusException( { status: channelInfo.responseStatus }, - url, - `Invalid response ${response} for ${url}` + uri, + `Invalid response ${response} for ${uri}` ); } } diff --git a/chrome/content/zotero/actors/PageDataChild.jsm b/chrome/content/zotero/actors/PageDataChild.jsm index f0fafb401c..e4eb4a0f88 100644 --- a/chrome/content/zotero/actors/PageDataChild.jsm +++ b/chrome/content/zotero/actors/PageDataChild.jsm @@ -2,6 +2,12 @@ var EXPORTED_SYMBOLS = ["PageDataChild"]; class PageDataChild extends JSWindowActorChild { async receiveMessage(message) { + // Special case for loadURI: don't wait for document to be ready, + // since we haven't loaded anything yet + if (message.name === "loadURI") { + return this.loadURI(message.data.uri); + } + let window = this.contentWindow; let document = window.document; @@ -40,6 +46,34 @@ class PageDataChild extends JSWindowActorChild { } } + loadURI(uri) { + // https://searchfox.org/mozilla-central/rev/e69f323af80c357d287fb6314745e75c62eab92a/toolkit/actors/BackgroundThumbnailsChild.sys.mjs#44-85 + let docShell = this.docShell.QueryInterface(Ci.nsIWebNavigation); + // Don't allow downloads/external apps + docShell.allowContentRetargeting = false; + + // Get the document to force a content viewer to be created, otherwise + // the first load can fail. + if (!this.document) { + return false; + } + + let loadURIOptions = { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }; + + try { + docShell.loadURI( + Services.io.newURI(uri), + loadURIOptions + ); + return true; + } + catch (e) { + return false; + } + } + // From Mozilla's ScreenshotsComponentChild.jsm documentIsReady() { const contentWindow = this.contentWindow;