From be8a5d5680aa10955885a510c2e32fa73e6a686b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=9A=D0=BE=D0=B7?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=D1=81=D0=BA=D0=B8=D0=B9?= Date: Sat, 26 Sep 2026 20:07:39 +0000 Subject: [PATCH 1/2] fix: detect `+xml` content types as text `detectResponseType` matched XML media types against a fixed set of strings, so types using the structured `+xml` suffix fell through to `blob`: `application/rss+xml`, `application/atom+xml`, `application/xhtml+xml` and `image/svg+xml`. The `+json` suffix is already handled the same way by `JSON_RE`, so this mirrors it with an equivalent pattern for XML. --- src/utils.ts | 7 ++++++- test/index.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 3bc0dbf5..0414a32e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -50,6 +50,7 @@ const textTypes = new Set([ ]); const JSON_RE = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i; +const XML_RE = /^(?:application|image)\/(?:[\w!#$%&*.^`~-]*\+)?xml(;.+)?$/i; // This provides reasonable defaults for the correct parser based on Content-Type header. export function detectResponseType(_contentType = ""): ResponseType { @@ -75,7 +76,11 @@ export function detectResponseType(_contentType = ""): ResponseType { return "stream"; } - if (textTypes.has(contentType) || contentType.startsWith("text/")) { + if ( + textTypes.has(contentType) || + XML_RE.test(contentType) || + contentType.startsWith("text/") + ) { return "text"; } diff --git a/test/index.test.ts b/test/index.test.ts index 5ac20b07..cad9529b 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -47,6 +47,13 @@ describe("ofetch", () => { event.res.headers.set("Content-Type", "application/octet-stream"); return new Blob(["binary"]); }) + .all("/content-type", (event) => { + event.res.headers.set( + "Content-Type", + event.url.searchParams.get("type") || "text/plain" + ); + return ""; + }) .all( "/403", () => new HTTPError({ status: 403, statusMessage: "Forbidden" }) @@ -110,6 +117,21 @@ describe("ofetch", () => { expect(await $fetch(getURL("binary"))).to.be.instanceOf(Blob); }); + it("returns text for xml content-types", async () => { + for (const type of [ + "application/xml", + "application/rss+xml", + "application/atom+xml", + "application/xhtml+xml", + "image/svg+xml", + "application/rss+xml; charset=utf-8", + ]) { + expect( + await $fetch(getURL(`content-type?type=${encodeURIComponent(type)}`)) + ).to.equal(""); + } + }); + it("baseURL", async () => { expect(await $fetch("/x?foo=123", { baseURL: getURL("url") })).to.equal( "/url/x?foo=123" From c7861eb4186c18293d6da7b4eeb7c446e37391c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=9A=D0=BE=D0=B7?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=D1=81=D0=BA=D0=B8=D0=B9?= Date: Wed, 21 Oct 2026 18:01:55 +0000 Subject: [PATCH 2/2] fix: match the `+xml` suffix across all top-level types Scoping the pattern to `application` and `image` still left registered `+xml` types under other top-level types detected as `blob`: `model/step+xml`, `model/vnd.collada+xml`, `model/vnd.moml+xml`, `model/vnd.opengex+xml` and `message/imdn+xml`. --- src/utils.ts | 2 +- test/index.test.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 0414a32e..0fbed223 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -50,7 +50,7 @@ const textTypes = new Set([ ]); const JSON_RE = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i; -const XML_RE = /^(?:application|image)\/(?:[\w!#$%&*.^`~-]*\+)?xml(;.+)?$/i; +const XML_RE = /^[\w!#$%&*.^`~-]+\/(?:[\w!#$%&*.^`~-]*\+)?xml(;.+)?$/i; // This provides reasonable defaults for the correct parser based on Content-Type header. export function detectResponseType(_contentType = ""): ResponseType { diff --git a/test/index.test.ts b/test/index.test.ts index cad9529b..955d8965 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -124,6 +124,8 @@ describe("ofetch", () => { "application/atom+xml", "application/xhtml+xml", "image/svg+xml", + "model/step+xml", + "message/imdn+xml", "application/rss+xml; charset=utf-8", ]) { expect(