Skip to content

Commit

Permalink
Implement --max-http-header-size (#13577)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jarred-Sumner authored Aug 29, 2024
1 parent e48369d commit 6faf657
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 2 deletions.
4 changes: 3 additions & 1 deletion packages/bun-uws/src/HttpParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
#include "ProxyParser.h"
#include "QueryParser.h"

extern "C" size_t BUN_DEFAULT_MAX_HTTP_HEADER_SIZE;

namespace uWS
{

Expand Down Expand Up @@ -207,7 +209,7 @@ namespace uWS
/* This guy really has only 30 bits since we reserve two highest bits to chunked encoding parsing state */
uint64_t remainingStreamingBytes = 0;

const size_t MAX_FALLBACK_SIZE = 1024 * 8;
const size_t MAX_FALLBACK_SIZE = BUN_DEFAULT_MAX_HTTP_HEADER_SIZE;

/* Returns UINT_MAX on error. Maximum 999999999 is allowed. */
static uint64_t toUnsignedInteger(std::string_view str) {
Expand Down
21 changes: 21 additions & 0 deletions src/bun.js/node/node_http_binding.zig
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,24 @@ pub fn getBunServerAllClosedPromise(globalThis: *JSC.JSGlobalObject, callframe:

return globalThis.throwInvalidArgumentTypeValue("server", "bun.Server", value);
}

pub fn getMaxHTTPHeaderSize(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
_ = globalThis; // autofix
_ = callframe; // autofix
return JSC.JSValue.jsNumber(bun.http.max_http_header_size);
}

pub fn setMaxHTTPHeaderSize(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(1).slice();
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("setMaxHTTPHeaderSize", 1, arguments.len);
return .zero;
}
const value = arguments[0];
const num = value.coerceToInt64(globalThis);
if (num <= 0) {
return globalThis.throwInvalidArgumentTypeValue("maxHeaderSize", "non-negative integer", value);
}
bun.http.max_http_header_size = @intCast(num);
return JSC.JSValue.jsNumber(bun.http.max_http_header_size);
}
13 changes: 13 additions & 0 deletions src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ pub const Arguments = struct {
clap.parseParam("-u, --origin <STR>") catch unreachable,
clap.parseParam("--conditions <STR>... Pass custom conditions to resolve") catch unreachable,
clap.parseParam("--fetch-preconnect <STR>... Preconnect to a URL while code is loading") catch unreachable,
clap.parseParam("--max-http-header-size <INT> Set the maximum size of HTTP headers in bytes. Default is 16KiB") catch unreachable,
};

const auto_or_run_params = [_]ParamType{
Expand Down Expand Up @@ -612,6 +613,18 @@ pub const Arguments = struct {
}
}

if (args.option("--max-http-header-size")) |size_str| {
const size = std.fmt.parseInt(usize, size_str, 10) catch {
Output.errGeneric("Invalid value for --max-http-header-size: \"{s}\". Must be a positive integer\n", .{size_str});
Global.exit(1);
};
if (size == 0) {
bun.http.max_http_header_size = 1024 * 1024 * 1024;
} else {
bun.http.max_http_header_size = size;
}
}

ctx.debug.offline_mode_setting = if (args.flag("--prefer-offline"))
Bunfig.OfflineMode.offline
else if (args.flag("--prefer-latest"))
Expand Down
5 changes: 5 additions & 0 deletions src/http.zig
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ var async_http_id: std.atomic.Value(u32) = std.atomic.Value(u32).init(0);
const MAX_REDIRECT_URL_LENGTH = 128 * 1024;
var custom_ssl_context_map = std.AutoArrayHashMap(*SSLConfig, *NewHTTPContext(true)).init(bun.default_allocator);

pub var max_http_header_size: usize = 16 * 1024;
comptime {
@export(max_http_header_size, .{ .name = "BUN_DEFAULT_MAX_HTTP_HEADER_SIZE" });
}

const print_every = 0;
var print_every_i: usize = 0;

Expand Down
10 changes: 9 additions & 1 deletion src/js/node/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2250,6 +2250,9 @@ function emitAbortNextTick(self) {
self.emit("abort");
}

const setMaxHTTPHeaderSize = $newZigFunction("node_http_binding.zig", "setMaxHTTPHeaderSize", 1);
const getMaxHTTPHeaderSize = $newZigFunction("node_http_binding.zig", "getMaxHTTPHeaderSize", 0);

var globalAgent = new Agent();
export default {
Agent,
Expand All @@ -2261,7 +2264,12 @@ export default {
IncomingMessage,
request,
get,
maxHeaderSize: 16384,
get maxHeaderSize() {
return getMaxHTTPHeaderSize();
},
set maxHeaderSize(value) {
setMaxHTTPHeaderSize(value);
},
validateHeaderName,
validateHeaderValue,
setMaxIdleHTTPParsers(max) {
Expand Down
33 changes: 33 additions & 0 deletions test/js/node/http/max-header-size-fixture.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 83 additions & 0 deletions test/js/node/http/node-http-maxHeaderSize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import http from "node:http";
import path from "path";
import { test, expect } from "bun:test";
import { bunEnv } from "harness";

test("maxHeaderSize", async () => {
const originalMaxHeaderSize = http.maxHeaderSize;
expect(http.maxHeaderSize).toBe(16 * 1024);
// @ts-expect-error its a liar
http.maxHeaderSize = 1024;
expect(http.maxHeaderSize).toBe(1024);
{
using server = Bun.serve({
port: 0,

fetch(req) {
return new Response(JSON.stringify(req.headers, null, 2));
},
});

expect(
async () =>
await fetch(`${server.url}/`, {
headers: {
"Huge": Buffer.alloc(8 * 1024, "abc").toString(),
},
}),
).toThrow();
expect(
async () =>
await fetch(`${server.url}/`, {
headers: {
"Huge": Buffer.alloc(512, "abc").toString(),
},
}),
).not.toThrow();
}
http.maxHeaderSize = 16 * 1024;
{
using server = Bun.serve({
port: 0,

fetch(req) {
return new Response(JSON.stringify(req.headers, null, 2));
},
});

expect(
async () =>
await fetch(`${server.url}/`, {
headers: {
"Huge": Buffer.alloc(15 * 1024, "abc").toString(),
},
}),
).not.toThrow();
expect(
async () =>
await fetch(`${server.url}/`, {
headers: {
"Huge": Buffer.alloc(17 * 1024, "abc").toString(),
},
}),
).toThrow();
}

http.maxHeaderSize = originalMaxHeaderSize;
});

test("--max-http-header-size=1024", async () => {
const size = 1024;
bunEnv.BUN_HTTP_MAX_HEADER_SIZE = size;
expect(["--max-http-header-size=" + size, path.join(import.meta.dir, "max-header-size-fixture.ts")]).toRun();
});

test("--max-http-header-size=NaN", async () => {
expect(["--max-http-header-size=" + "NaN", path.join(import.meta.dir, "max-header-size-fixture.ts")]).not.toRun();
});

test("--max-http-header-size=16*1024", async () => {
const size = 16 * 1024;
bunEnv.BUN_HTTP_MAX_HEADER_SIZE = size;
expect(["--max-http-header-size=" + size, path.join(import.meta.dir, "max-header-size-fixture.ts")]).toRun();
});

0 comments on commit 6faf657

Please sign in to comment.