Advertise byte ranges on downloads, and record the live checks

Stalwart honors a single byte range on its download endpoint but sends
no Accept-Ranges, and Chrome's PDF viewer only reads a file in pieces
when the first response says it can. The proxy now says so itself.

Checked live on 0.16.22: ContactCard/changes reports creates, updates
and destroys exactly, which the contacts store's sync relies on, and a
range the server cannot serve gets the whole file with 200, never 416.
The mock now answers ranges the same way and sends no Accept-Ranges.
This commit is contained in:
2026-09-16 12:10:20 -07:00
parent ebf678be73
commit 8a08c3d6db
4 changed files with 24 additions and 9 deletions
+4 -1
View File
@@ -127,9 +127,12 @@ test("a download passes a byte range through, for viewers that read in pieces",
assert.equal(await part.text(), "hello");
const whole = await app.request(url, { headers: { cookie } });
assert.equal(whole.status, 200);
assert.equal(whole.headers.get("accept-ranges"), "bytes", "advertised even though Stalwart does not, so a PDF viewer asks");
assert.equal(await whole.text(), "hello world");
// Past the end, Stalwart sends the whole file rather than a 416.
const beyond = await app.request(url, { headers: { cookie, range: "bytes=50-60" } });
assert.equal(beyond.status, 416);
assert.equal(beyond.status, 200);
assert.equal(await beyond.text(), "hello world");
// Anything that is not a plain byte range is not passed on.
const odd = await app.request(url, { headers: { cookie, range: "items=0-4" } });
assert.equal(odd.status, 200);
+8 -1
View File
@@ -822,7 +822,14 @@ export function createApp(basePath = config.basePath): Hono<Env> {
if (cl) headers.set("Content-Length", cl);
const partial = res.status === 206 && res.headers.get("content-range");
if (partial) headers.set("Content-Range", partial);
if (res.headers.get("accept-ranges") === "bytes") headers.set("Accept-Ranges", "bytes");
/*
* Said here because Stalwart does not say it. It honors a single byte
* range but sends no `Accept-Ranges` (0.16.22, checked live on
* 2026-09-16), and Chrome's PDF viewer only reads a file in pieces when
* the first response advertises it. A server that ignores a range sends
* the whole file, which the browser takes just as well.
*/
headers.set("Accept-Ranges", "bytes");
const safeInline = inline && isInlineSafe(type);
headers.set(
"Content-Disposition",
+10 -7
View File
@@ -139,20 +139,23 @@ export const server = createServer(async (req, res) => {
const b = blobs.get(blobId ?? "");
if (!b) { res.writeHead(404); return res.end(); }
const type = url.searchParams.get("accept") ?? b.type;
// One byte range, the way a PDF viewer or a video element asks for one.
/*
* One byte range, answered as Stalwart answers it (0.16.22, checked live
* on 2026-09-16): a 206 for a single range it can serve, and the whole
* file with a 200 for anything else -- several ranges, or one past the
* end. It never sends Accept-Ranges.
*/
const m = /^bytes=(\d*)-(\d*)$/.exec(String(req.headers.range ?? ""));
if (m && (m[1] || m[2])) {
const size = b.data.length;
const start = m[1] ? Number(m[1]) : Math.max(0, size - Number(m[2]));
const end = m[1] && m[2] ? Math.min(Number(m[2]), size - 1) : size - 1;
if (start >= size || start > end) {
res.writeHead(416, { "content-range": `bytes */${size}` });
return res.end();
if (start < size && start <= end) {
res.writeHead(206, { "content-type": type, "content-length": end - start + 1, "content-range": `bytes ${start}-${end}/${size}` });
return res.end(b.data.subarray(start, end + 1));
}
res.writeHead(206, { "content-type": type, "content-length": end - start + 1, "content-range": `bytes ${start}-${end}/${size}`, "accept-ranges": "bytes" });
return res.end(b.data.subarray(start, end + 1));
}
res.writeHead(200, { "content-type": type, "content-length": b.data.length, "accept-ranges": "bytes" });
res.writeHead(200, { "content-type": type, "content-length": b.data.length });
return res.end(b.data);
}
/*