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
This commit is contained in:
Filip Stevanovic
2025-09-26 12:54:41 +02:00
committed by GitHub
parent 00490199f1
commit f45900d7e6
2 changed files with 57 additions and 1 deletions

View File

@@ -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);
}

View File

@@ -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');
});