### What does this PR do?

Fixes #24817

### How did you verify your code works?
Test

---------

Co-authored-by: taylor.fish <contact@taylor.fish>
This commit is contained in:
Jarred Sumner
2025-11-19 21:17:51 -08:00
committed by GitHub
parent b554626662
commit 9189fc4fa1
4 changed files with 79 additions and 3 deletions

View File

@@ -29,9 +29,11 @@ pub const InitFromBytesOptions = struct {
/// Ownership of `blob` is transferred to this function.
pub fn initFromAnyBlob(blob: *const AnyBlob, options: InitFromBytesOptions) *StaticRoute {
var headers = bun.handleOom(Headers.from(options.headers, bun.default_allocator, .{ .body = blob }));
if (options.mime_type) |mime_type| {
if (headers.getContentType() == null) {
if (headers.getContentType() == null) {
if (options.mime_type) |mime_type| {
bun.handleOom(headers.append("Content-Type", mime_type.value));
} else if (blob.hasContentTypeFromUser()) {
bun.handleOom(headers.append("Content-Type", blob.contentType()));
}
}
@@ -92,6 +94,7 @@ pub fn fromJS(globalThis: *jsc.JSGlobalObject, argument: jsc.JSValue) bun.JSErro
// The user may want to pass in the same Response object multiple endpoints
// Let's let them do that.
const bodyValue = response.getBodyValue();
const was_string = bodyValue.wasString();
bodyValue.toBlobIfPossible();
const blob: AnyBlob = brk: {
@@ -151,6 +154,10 @@ pub fn fromJS(globalThis: *jsc.JSGlobalObject, argument: jsc.JSValue) bun.JSErro
.allocator = bun.default_allocator,
};
if (was_string and headers.getContentType() == null) {
bun.handleOom(headers.append("Content-Type", bun.http.MimeType.Table.@"text/plain; charset=utf-8".slice()));
}
// Generate ETag if not already present
if (headers.get("etag") == null) {
if (blob.slice().len > 0) {

View File

@@ -272,6 +272,14 @@ pub const Value = union(Tag) {
};
}
pub fn wasString(this: *const Value) bool {
return switch (this.*) {
.InternalBlob => |*blob| blob.was_string,
.WTFStringImpl => true,
else => false,
};
}
pub const heap_breakdown_label = "BodyValue";
pub const ValueError = union(enum) {
AbortReason: jsc.CommonAbortReason,

View File

@@ -246,7 +246,6 @@ pub const html = MimeType.initComptime("text/html;charset=utf-8", .html);
pub const json = MimeType.initComptime("application/json;charset=utf-8", .json);
pub const transpiled_json = javascript;
pub const text = MimeType.initComptime("text/plain;charset=utf-8", .html);
pub const text_plain = MimeType.initComptime("text/plain;charset=utf-8", .text);
pub const wasm = MimeType.initComptime(
"application/wasm",
.wasm,

View File

@@ -0,0 +1,62 @@
// https://github.com/oven-sh/bun/issues/24817
// Unicode not working with static route
import { expect, test } from "bun:test";
test("static routes should handle unicode correctly", async () => {
using server = Bun.serve({
port: 0,
routes: {
"/dynamic": () => new Response("▲"),
"/static": new Response("▲"),
"/unicode-string": new Response("こんにちは世界"),
"/emoji": new Response("🎉🚀✨"),
},
});
const baseUrl = server.url.href;
// Test basic unicode character
{
const staticResp = await fetch(`${baseUrl}/static`);
const staticText = await staticResp.text();
expect(staticText).toBe("▲");
expect(staticResp.headers.get("content-type")).toBe("text/plain; charset=utf-8");
}
// Test Japanese characters
{
const resp = await fetch(`${baseUrl}/unicode-string`);
const text = await resp.text();
expect(text).toBe("こんにちは世界");
expect(resp.headers.get("content-type")).toBe("text/plain; charset=utf-8");
}
// Test emoji
{
const resp = await fetch(`${baseUrl}/emoji`);
const text = await resp.text();
expect(text).toBe("🎉🚀✨");
expect(resp.headers.get("content-type")).toBe("text/plain; charset=utf-8");
}
});
test("static routes with explicit content-type should not override", async () => {
using server = Bun.serve({
port: 0,
routes: {
"/custom": new Response("▲", { headers: { "content-type": "text/html" } }),
},
});
const baseUrl = server.url.href;
const resp = await fetch(`${baseUrl}/custom`);
const text = await resp.text();
expect(text).toBe("▲");
// Should respect the explicit content-type
expect(resp.headers.get("content-type")).toBe("text/html");
});