From 813313ecae07b92ce78e2ae9295af6569e397094 Mon Sep 17 00:00:00 2001 From: Eric Hurkman Date: Tue, 30 Jun 2026 14:07:17 -0400 Subject: [PATCH] fix: stream non-textual responses through untouched Non-textual responses (PDFs, fonts, and other file attachments) were being read with response.text(), which does a lossy UTF-8 decode/re-encode round-trip that corrupts the bytes. The browser then receives a broken file and can't render it. Detect the content-type and pass binary bodies straight through; only HTML/CSS/JS/JSON/XML get rewritten. Co-Authored-By: Claude Opus 4.8 --- src/proxy.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/proxy.ts b/src/proxy.ts index 7655aa0..cf15be1 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -111,6 +111,22 @@ async function reverseProxy(request: Request, siteConfig: NooxySiteConfigFull): return response; } + // Non-textual responses (PDFs, fonts, and other file attachments) must be + // streamed through untouched. Reading a binary body with response.text() does a + // lossy UTF-8 decode/re-encode round-trip that corrupts the bytes, so the browser + // receives a broken file and can't render it. Only HTML/CSS/JS/JSON/XML get + // rewritten below. + const contentType = response.headers.get('content-type') || ''; + const isTextual = /text\/html|text\/css|text\/plain|javascript|application\/json|\bxml\b/i.test(contentType); + if (!isTextual) { + const passthroughHeaders = modifyResponseHeaders(response.headers, hostname, siteConfig); + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: passthroughHeaders, + }); + } + // For 304 Not Modified responses, return with null body if (response.status === 304) { const modifiedResponseHeaders = modifyResponseHeaders(response.headers, hostname, siteConfig);