From 12f344e03d080ad77eb9d100f71d66fb9fb1ba47 Mon Sep 17 00:00:00 2001 From: Abe Jellinek Date: Wed, 25 Jun 2025 10:26:38 -0400 Subject: [PATCH] Fix all cookies being lost during remote translation (#5358) --- .../content/zotero/actors/translation/http.js | 11 ++++ chrome/content/zotero/xpcom/cookieSandbox.js | 29 ++++++--- test/tests/RemoteTranslateTest.js | 64 +++++++++++++++++-- 3 files changed, 90 insertions(+), 14 deletions(-) diff --git a/chrome/content/zotero/actors/translation/http.js b/chrome/content/zotero/actors/translation/http.js index 6e2bf86145..131ac71918 100644 --- a/chrome/content/zotero/actors/translation/http.js +++ b/chrome/content/zotero/actors/translation/http.js @@ -23,6 +23,8 @@ ***** END LICENSE BLOCK ***** */ +Cu.import("resource://gre/modules/NetUtil.jsm"); + /** * Functions for performing HTTP requests, both via XMLHTTPRequest and using a hidden browser * @namespace @@ -106,6 +108,15 @@ Zotero.HTTP = new function() { var promise = Zotero.HTTP._attachHandlers(url, xmlhttp, options); xmlhttp.open(method, url, true); + + // Overwrite the system nsILoadInfo with one tied to our document + // so CookieSandbox can identify the source of the XHR + xmlhttp.channel.loadInfo = NetUtil.newChannel({ + uri: url, + loadingNode: document, + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT, + contentPolicyType: Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST, + }).loadInfo; for (let header in options.headers) { xmlhttp.setRequestHeader(header, options.headers[header]); diff --git a/chrome/content/zotero/xpcom/cookieSandbox.js b/chrome/content/zotero/xpcom/cookieSandbox.js index 465a7a6a61..6bd08128f6 100755 --- a/chrome/content/zotero/xpcom/cookieSandbox.js +++ b/chrome/content/zotero/xpcom/cookieSandbox.js @@ -55,7 +55,6 @@ Zotero.CookieSandbox = function (browser, uri, cookieData, userAgent) { this._observerService = Components.classes["@mozilla.org/observer-service;1"]. getService(Components.interfaces.nsIObserverService); - Zotero.CookieSandbox.Observer.register(); if (browser) { this.attachToBrowser(browser); } @@ -343,18 +342,30 @@ Zotero.CookieSandbox.Observer = new function() { // Zotero.debug(`CookieSandbox: Found the browser via doc of load group for ${channelURI}`, 5); trackedBy = this.trackedBrowsers.get(browser); } - else if (notificationCallbacks instanceof XMLHttpRequest) { - // Zotero.debug(`CookieSandbox: Found the browser via XHR for ${channelURI}`, 5); - tested = true; - } else { - // try getting as an nsIWBP + // System XHR created from TranslationChild only has browser identifiers in loadInfo try { - notificationCallbacks.QueryInterface(Components.interfaces.nsIWebBrowserPersist); - // Zotero.debug(`CookieSandbox: Found the browser via nsIWBP for ${channelURI}`, 5); - tested = true; + browser = channel.loadInfo.targetBrowsingContext.embedderElement; } catch (e) {} + if (browser) { + tested = true; + // Zotero.debug(`CookieSandbox: Found the browser via load info BrowsingContext for ${channelURI}`, 5); + trackedBy = this.trackedBrowsers.get(browser); + } + else if (notificationCallbacks instanceof XMLHttpRequest) { + // Zotero.debug(`CookieSandbox: Found the browser via XHR for ${channelURI}`, 5); + tested = true; + } + else { + // try getting as an nsIWBP + try { + notificationCallbacks.QueryInterface(Components.interfaces.nsIWebBrowserPersist); + // Zotero.debug(`CookieSandbox: Found the browser via nsIWBP for ${channelURI}`, 5); + tested = true; + } + catch (e) {} + } } } } diff --git a/test/tests/RemoteTranslateTest.js b/test/tests/RemoteTranslateTest.js index c9bffe0011..9c35cbdcba 100644 --- a/test/tests/RemoteTranslateTest.js +++ b/test/tests/RemoteTranslateTest.js @@ -6,7 +6,10 @@ const { RemoteTranslate } = ChromeUtils.import("chrome://zotero/content/RemoteTr describe("RemoteTranslate", function () { let dummyTranslator; let translatorProvider; - before(function () { + let httpd; + let port = 16213; + + before(async function () { dummyTranslator = buildDummyTranslator('web', ` function detectWeb() { Zotero.debug("test string"); @@ -36,6 +39,29 @@ describe("RemoteTranslate", function () { return translators; } }); + + var { HttpServer } = ChromeUtils.import("chrome://remote/content/server/HTTPD.jsm"); + httpd = new HttpServer(); + httpd.start(port); + httpd.registerPathHandler( + '/readCookie', + { + handle: function (request, response) { + if (request.getHeader('Cookie') === 'sessionID=1') { + response.setStatusLine(null, 200, 'OK'); + response.write('OK'); + } + else { + response.setStatusLine(null, 403, 'Forbidden'); + response.write('Header not set'); + } + } + } + ); + }); + + after(async function () { + await new Promise(resolve => httpd.stop(resolve)); }); describe("#setHandler()", function () { @@ -141,7 +167,7 @@ describe("RemoteTranslate", function () { }); it("should support DOMParser", async function () { - let domParserDummy = buildDummyTranslator('web', ` + let dummy = buildDummyTranslator('web', ` function detectWeb() { return "book"; } @@ -157,7 +183,7 @@ describe("RemoteTranslate", function () { let browser = new HiddenBrowser(); await browser.load(getTestDataUrl('test.html')); await translate.setBrowser(browser); - translate.setTranslator(domParserDummy); + translate.setTranslator(dummy); let items = await translate.translate({ libraryID: false }); assert.equal(items[0].title, 'content'); @@ -167,7 +193,7 @@ describe("RemoteTranslate", function () { }); it("should be able to access hidden prefs", async function () { - let domParserDummy = buildDummyTranslator('web', ` + let dummy = buildDummyTranslator('web', ` function detectWeb() { return "book"; } @@ -185,7 +211,7 @@ describe("RemoteTranslate", function () { let browser = new HiddenBrowser(); await browser.load(getTestDataUrl('test.html')); await translate.setBrowser(browser); - translate.setTranslator(domParserDummy); + translate.setTranslator(dummy); let items = await translate.translate({ libraryID: false }); assert.equal(items[0].title, 'Test value'); @@ -193,5 +219,33 @@ describe("RemoteTranslate", function () { browser.destroy(); translate.dispose(); }); + + it("should send cookies", async function () { + let dummy = buildDummyTranslator('web', ` + function detectWeb() { + return "book"; + } + + async function doWeb() { + let item = new Zotero.Item("book"); + item.title = await requestText("http://localhost:${port}/readCookie", { + headers: { Cookie: "sessionID=1" }, + }); + item.complete(); + } + `); + + let translate = new RemoteTranslate(); + let browser = new HiddenBrowser(); + await browser.load(getTestDataUrl('test.html')); + await translate.setBrowser(browser); + translate.setTranslator(dummy); + + let items = await translate.translate({ libraryID: false }); + assert.equal(items[0].title, 'OK'); + + browser.destroy(); + translate.dispose(); + }); }); });