From 1d4b8bc89ad3b5bace63c7548bb699ed3f034e3a Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 16 Sep 2026 09:53:07 -0400 Subject: [PATCH] Fix WebDAV auth for passwords containing '%' followed by two hex digits fd812070b6 made _parseURI() decode the URL credentials so that download() could build its own Basic Auth header from the decoded values, but request() passed the decoded credentials to xmlhttp.open(), which percent-decodes them again, so a password like "example%41pass" was sent as "exampleApass". Re-encode the credentials before passing them to open(). Fixes #6048 --- chrome/content/zotero/xpcom/http.js | 10 +++++++++- test/tests/httpTest.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js index 0c1b2b9dd8..978095c6a1 100644 --- a/chrome/content/zotero/xpcom/http.js +++ b/chrome/content/zotero/xpcom/http.js @@ -294,7 +294,15 @@ Zotero.HTTP = new function () { if (!options.foreground) { xmlhttp.mozBackgroundRequest = true; } - xmlhttp.open(method, url, true, options.username, options.password); + // Necko percent-decodes credentials passed to open(), so encode them to preserve + // literal %HH sequences in the password + xmlhttp.open( + method, + url, + true, + options.username && encodeURIComponent(options.username), + options.password && encodeURIComponent(options.password) + ); // Isolate cookies into a separate jar via userContextId if (options.userContextId && xmlhttp.setOriginAttributes) { diff --git a/test/tests/httpTest.js b/test/tests/httpTest.js index 4986a93415..3f9b6a2da5 100644 --- a/test/tests/httpTest.js +++ b/test/tests/httpTest.js @@ -625,6 +625,21 @@ describe("Zotero.HTTP", function () { xmlhttp.getResponseHeader("X-Echo-Auth") ); }); + + it("should preserve literal %HH sequences in the password in request() and download()", async function () { + let username = "user"; + let password = "example%41pass"; + let expected = "Basic " + btoa(username + ":" + password); + let url = `http://${username}:${encodeURIComponent(password)}` + + `@127.0.0.1:${port}/download/auth`; + + let xmlhttp = await Zotero.HTTP.request("GET", url); + assert.equal(xmlhttp.getResponseHeader("X-Echo-Auth"), expected); + + let dest = PathUtils.join(tmpDir, "auth-percent.bin"); + let req = await Zotero.HTTP.download(Services.io.newURI(url), dest); + assert.equal(req.headers.get("X-Echo-Auth"), expected); + }); });