Repository: unjs/h3 (ts)

Issue:
Default content-type `text/html` used in 304 responses of static assets
### Environment

Node 18, Nuxt 3.8.2, Nitro 2.8.1

### Reproduction

none

### Describe the bug

Ran into this issue using Nuxt in production mode and a static `.svg` asset.

H3 uses the default content-type `text/html` for 304 responses. Apparently this happens in [handleHandlerResponse](https://github.com/unjs/h3/blob/53703dc860f1ff6fe7ce71d543deff1cfa810b11/src/app.ts#L255) - 304 responses have an empty string body, and the function [defaultContentType](https://github.com/unjs/h3/blob/53703dc860f1ff6fe7ce71d543deff1cfa810b11/src/utils/response.ts#L78) called subsequently only checks if the `content-type` header has been set already, but **not** if the response code is 304. Thus the default content type `text/html` is applied.

[RFC 7232 suggests](https://www.rfc-editor.org/rfc/rfc7232#section-4.1) that 304 responses should not contain a `content-type` header, so i think checking the response code too in `defaultContentType()` should be sufficient (although i'm not sure if this would be the right spot to change).

See also https://github.com/unjs/nitro/issues/1700

### Additional context

_No response_

### Logs

_No response_

Pull request description:
fix: avoid setting default `content-type` for responses with 304 status
- test: add failing test
- fix: avoid setting default `content-type` for responses with 304 status

<!---
☝️ PR title should follow conventional commits (https://conventionalcommits.org) 
-->

### 🔗 Linked issue

resolves #603

### ❓ Type of change

<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. -->

- [ ] 📖 Documentation (updates to the documentation, readme, or JSdoc annotations)
- [ ] 🐞 Bug fix (a non-breaking change that fixes an issue)
- [ ] 👌 Enhancement (improving an existing functionality like performance)
- [ ] ✨ New feature (a non-breaking change that adds functionality)
- [ ] 🧹 Chore (updates to the build process or auxiliary tools and libraries)
- [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

### 📚 Description

<!-- Describe your changes in detail -->
<!-- Why is this change required? What problem does it solve? -->
<!-- If it resolves an open issue, please link to the issue here. For example "Resolves #1337" -->

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->
<!-- If your change requires a documentation PR, please link it appropriately -->
<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] I have linked an issue or discussion.
- [ ] I have updated the documentation accordingly.


Patch:
diff --git a/src/utils/response.ts b/src/utils/response.ts
index d553638..4f2019f 100644
--- a/src/utils/response.ts
+++ b/src/utils/response.ts
@@ -76,7 +76,11 @@ export function getResponseStatusText(event: H3Event): string {
 }
 
 export function defaultContentType(event: H3Event, type?: string) {
-  if (type && !event.node.res.getHeader("content-type")) {
+  if (
+    type &&
+    event.node.res.statusCode !== 304 /* unjs/h3#603 */ &&
+    !event.node.res.getHeader("content-type")
+  ) {
     event.node.res.setHeader("content-type", type);
   }
 }


Test patch:
diff --git a/test/status.test.ts b/test/status.test.ts
index 7d62661..c95d50a 100644
--- a/test/status.test.ts
+++ b/test/status.test.ts
@@ -6,6 +6,7 @@ import {
   PlainHandler,
   eventHandler,
   setResponseStatus,
+  send,
 } from "../src";
 
 describe("setResponseStatus", () => {
@@ -109,5 +110,31 @@ describe("setResponseStatus", () => {
         headers: [],
       });
     });
+
+    it("does not sets content-type for 304", async () => {
+      app.use(
+        "/test",
+        eventHandler((event) => {
+          setResponseStatus(event, 304, "Not Modified");
+          return "";
+        }),
+      );
+
+      const res = await handler({
+        method: "GET",
+        path: "/test",
+        headers: [],
+        body: "",
+      });
+
+      console.log(res.headers);
+
+      expect(res).toMatchObject({
+        status: 304,
+        statusText: "Not Modified",
+        body: undefined,
+        headers: [],
+      });
+    });
   });
 });