diff --git a/chrome/content/zotero/HiddenBrowser.jsm b/chrome/content/zotero/HiddenBrowser.jsm index 6d841911cc..5fbc2447ea 100644 --- a/chrome/content/zotero/HiddenBrowser.jsm +++ b/chrome/content/zotero/HiddenBrowser.jsm @@ -62,6 +62,7 @@ const HiddenBrowser = { * @param {Boolean} [options.allowJavaScript] * @param {Object} [options.docShell] Fields to set on Browser.docShell * @param {Boolean} [options.requireSuccessfulStatus] + * @param {Boolean} [options.blockRemoteResources] Block all remote (non-file:) resources * @param {Zotero.CookieSandbox} [options.cookieSandbox] */ async create(source, options = {}) { @@ -108,6 +109,10 @@ const HiddenBrowser = { }, 1000 * 60); } + if (options.blockRemoteResources) { + RemoteResourceBlockingObserver.watch(browser); + } + // Next bit adapted from Mozilla's HeadlessShell.jsm const principal = Services.scriptSecurityManager.getSystemPrincipal(); try { @@ -229,9 +234,43 @@ const HiddenBrowser = { destroy(browser) { var frame = browserFrameMap.get(browser); if (frame) { + RemoteResourceBlockingObserver.unwatch(browser); frame.destroy(); Zotero.debug("Deleted hidden browser"); browserFrameMap.delete(browser); } } }; + +const RemoteResourceBlockingObserver = { + _observerAdded: false, + _ids: new Set(), + + observe(subject) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + let id = Zotero.platformMajorVersion > 102 ? channel.browserId : channel.topBrowsingContextId; + if (this._ids.has(id) && channel.URI.scheme !== 'file') { + channel.cancel(Cr.NS_BINDING_ABORTED); + } + }, + + watch(browser) { + let id = Zotero.platformMajorVersion > 102 ? browser.browserId : browser.browsingContext.id; + this._ids.add(id); + if (!this._observerAdded) { + Services.obs.addObserver(this, 'http-on-modify-request'); + Zotero.debug('RemoteResourceBlockingObserver: Added observer'); + this._observerAdded = true; + } + }, + + unwatch(browser) { + let id = Zotero.platformMajorVersion > 102 ? browser.browserId : browser.browsingContext.id; + this._ids.delete(id); + if (this._observerAdded && !this._ids.size) { + Services.obs.removeObserver(this, 'http-on-modify-request'); + Zotero.debug('RemoteResourceBlockingObserver: Removed observer'); + this._observerAdded = false; + } + } +}; diff --git a/chrome/content/zotero/xpcom/fulltext.js b/chrome/content/zotero/xpcom/fulltext.js index 2550105b24..a37a805b03 100644 --- a/chrome/content/zotero/xpcom/fulltext.js +++ b/chrome/content/zotero/xpcom/fulltext.js @@ -1541,7 +1541,7 @@ Zotero.Fulltext = Zotero.FullText = new function(){ var pageData; try { let url = Zotero.File.pathToFileURI(path); - browser = await HiddenBrowser.create(url); + browser = await HiddenBrowser.create(url, { blockRemoteResources: true }); pageData = await HiddenBrowser.getPageData(browser, ['characterSet', 'bodyText']); } finally { diff --git a/test/tests/HiddenBrowserTest.js b/test/tests/HiddenBrowserTest.js index d47a79ba12..07b473853c 100644 --- a/test/tests/HiddenBrowserTest.js +++ b/test/tests/HiddenBrowserTest.js @@ -7,12 +7,30 @@ describe("HiddenBrowser", function() { var httpd; var port = 16213; var baseURL = `http://127.0.0.1:${port}/`; + + var pngRequested = false; before(function () { Cu.import("resource://zotero-unit/httpd.js"); httpd = new HttpServer(); httpd.start(port); }); + + beforeEach(async function () { + pngRequested = false; + httpd.registerPathHandler( + '/remote.png', + { + handle: function (request, response) { + Zotero.debug('Something loaded the image') + response.setHeader('Content-Type', 'image/png', false); + response.setStatusLine(null, 200, 'OK'); + response.write(''); + pngRequested = true; + } + } + ); + }); after(async function () { await new Promise(resolve => httpd.stop(resolve)); @@ -22,6 +40,22 @@ describe("HiddenBrowser", function() { let e = await getPromiseError(HiddenBrowser.create(baseURL + 'nonexistent', { requireSuccessfulStatus: true })); assert.instanceOf(e, Zotero.HTTP.UnexpectedStatusException); }); + + it("should prevent a remote request with blockRemoteResources", async function () { + let path = OS.Path.join(getTestDataDirectory().path, 'test-hidden.html'); + let browser = await HiddenBrowser.create(path, { blockRemoteResources: true }); + await HiddenBrowser.getPageData(browser, ['characterSet', 'bodyText']); + HiddenBrowser.destroy(browser); + assert.isFalse(pngRequested); + }); + + it("should allow a remote request without blockRemoteResources", async function () { + let path = OS.Path.join(getTestDataDirectory().path, 'test-hidden.html'); + let browser = await HiddenBrowser.create(path, { blockRemoteResources: false }); + await HiddenBrowser.getPageData(browser, ['characterSet', 'bodyText']); + HiddenBrowser.destroy(browser); + assert.isTrue(pngRequested); + }); }); describe("#getPageData()", function () { diff --git a/test/tests/data/test-hidden.html b/test/tests/data/test-hidden.html index 5fbad3f92e..1f8fc495c4 100644 --- a/test/tests/data/test-hidden.html +++ b/test/tests/data/test-hidden.html @@ -3,6 +3,7 @@
+This is a test.