Fix WebDAV downloads with non-ASCII characters in the password
Some checks are pending
CI / Detect changes (push) Waiting to run
CI / Test () (push) Blocked by required conditions
CI / Test (macOS NFS) (push) Blocked by required conditions
CI / Utilities Tests (push) Waiting to run
CI / Build, Upload (push) Waiting to run

HTTP.download() built the Basic auth header with btoa(), which throws
on code points above 255, so every file download failed immediately
with a TypeError. Requests that go through XMLHttpRequest were
unaffected, since Necko builds the header itself, UTF-8 encoded.

https://forums.zotero.org/discussion/133454/synchronization-error-after-upgrading-to-10-0-1

(cherry picked from commit 3b93d33b35)
This commit is contained in:
Dan Stillman 2026-08-27 10:24:22 -04:00
parent 6befe6827d
commit 32be699862
2 changed files with 25 additions and 8 deletions

View file

@ -694,7 +694,9 @@ Zotero.HTTP = new function () {
// Build headers
let headers = new Zotero.HTTP.CasePreservingHeaders(options.headers || {});
if (ctx.username) {
let encoded = btoa(ctx.username + ':' + (ctx.password || ''));
// Encode as UTF-8 before base64, since btoa() only accepts code points below 256
let bytes = new TextEncoder().encode(ctx.username + ':' + (ctx.password || ''));
let encoded = btoa(String.fromCharCode(...bytes));
headers.set('Authorization', `Basic ${encoded}`);
}
fetchOptions.headers = headers;

View file

@ -454,15 +454,16 @@ describe("Zotero.HTTP", function () {
'/download/auth',
{
handle: function (request, response) {
let val;
try {
val = request.getHeader("Authorization");
}
catch (e) {
val = "";
if (!request.hasHeader("Authorization")) {
response.setStatusLine(null, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", 'Basic realm="Test"', false);
response.write("denied");
return;
}
response.setStatusLine(null, 200, "OK");
response.setHeader("X-Echo-Auth", val, false);
response.setHeader(
"X-Echo-Auth", request.getHeader("Authorization"), false
);
response.write("ok");
}
}
@ -610,6 +611,20 @@ describe("Zotero.HTTP", function () {
assert.equal(req.status, 200);
assert.equal(req.headers.get("X-Echo-Auth"), expected);
});
it("should encode non-ASCII credentials the same way as request()", async function () {
let url = `http://user:p%C3%A4ssw%E2%82%ACrd`
+ `@127.0.0.1:${port}/download/auth`;
let dest = PathUtils.join(tmpDir, "auth-utf8.bin");
let req = await Zotero.HTTP.download(Services.io.newURI(url), dest);
assert.equal(req.status, 200);
let xmlhttp = await Zotero.HTTP.request("GET", url);
assert.equal(
req.headers.get("X-Echo-Auth"),
xmlhttp.getResponseHeader("X-Echo-Auth")
);
});
});