From f45900d7e6d1dfb2bb289b3e293c7d47efcb09a4 Mon Sep 17 00:00:00 2001 From: Filip Stevanovic <62512535+filipstev@users.noreply.github.com> Date: Fri, 26 Sep 2025 12:54:41 +0200 Subject: [PATCH] fix(fetch): print request body for application/x-www-form-urlencoded in curl logs (#22849) ### What does this PR do? fixes an issue where fetch requests with `Content-Type: application/x-www-form-urlencoded` would not include the request body in curl logs when `BUN_CONFIG_VERBOSE_FETCH=curl` is enabled previously, only JSON and text-based content types were recognized as safe-to-print in the curl formatter. This change updates the allow-list to also handle `application/x-www-form-urlencoded`, ensuring bodies for common form submissions are shown in logs ### How did you verify your code works? - added `Content-Type: application/x-www-form-urlencoded` to a fetch request and confirmed that `BUN_CONFIG_VERBOSE_FETCH=curl` now outputs a `--data-raw` section with the encoded body - verified the fix against the reproduction script provided in issue #12042 - created and ran a regression test - checked that existing content types (JSON, text, etc.) continue to print correctly fixes #12042 --- src/deps/picohttp.zig | 11 ++++++- test/regression/issue/12042.test.ts | 47 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 test/regression/issue/12042.test.ts diff --git a/src/deps/picohttp.zig b/src/deps/picohttp.zig index b674897fb9..a0507c6ffa 100644 --- a/src/deps/picohttp.zig +++ b/src/deps/picohttp.zig @@ -97,6 +97,15 @@ pub const Request = struct { ignore_insecure: bool = false, body: []const u8 = "", + fn isPrintableBody(content_type: []const u8) bool { + if (content_type.len == 0) return false; + + return bun.strings.hasPrefixComptime(content_type, "text/") or + bun.strings.hasPrefixComptime(content_type, "application/json") or + bun.strings.containsComptime(content_type, "json") or + bun.strings.hasPrefixComptime(content_type, "application/x-www-form-urlencoded"); + } + pub fn format(self: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void { const request = self.request; if (Output.enable_ansi_colors_stderr) { @@ -132,7 +141,7 @@ pub const Request = struct { } } - if (self.body.len > 0 and (content_type.len > 0 and bun.strings.hasPrefixComptime(content_type, "application/json") or bun.strings.hasPrefixComptime(content_type, "text/") or bun.strings.containsComptime(content_type, "json"))) { + if (self.body.len > 0 and isPrintableBody(content_type)) { _ = try writer.writeAll(" --data-raw "); try bun.js_printer.writeJSONString(self.body, @TypeOf(writer), writer, .utf8); } diff --git a/test/regression/issue/12042.test.ts b/test/regression/issue/12042.test.ts new file mode 100644 index 0000000000..f882aa78fe --- /dev/null +++ b/test/regression/issue/12042.test.ts @@ -0,0 +1,47 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness"; + +test("#12042 curl verbose fetch logs form-urlencoded body", async () => { + using dir = tempDir("issue-12042", { + "form.ts": ` +const server = Bun.serve({ + port: 0, + fetch() { + return new Response(JSON.stringify({ ok: true }), { + headers: { "Content-Type": "application/json" }, + }); + }, +}); + +const params = new URLSearchParams(); +params.set("grant_type", "client_credentials"); +params.set("client_id", "abc"); +params.set("client_secret", "xyz"); + +await fetch(String(server.url), { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: params, +}); + +await server.stop(); + `, + }); + + const dirPath = String(dir); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "form.ts"], + env: { ...bunEnv, BUN_CONFIG_VERBOSE_FETCH: "curl" }, + cwd: dirPath, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr] = await Promise.all([proc.stdout.text(), proc.stderr.text()]); + + const output = stdout + stderr; + const normalized = normalizeBunSnapshot(output, dirPath); + + expect(normalized).toContain('--data-raw "grant_type=client_credentials&client_id=abc&client_secret=xyz'); +});