Implement --max-http-header-size (#13577)

This commit is contained in:
Jarred Sumner
2024-08-29 00:38:47 -07:00
committed by GitHub
parent e48369ddab
commit 6faf657e32
7 changed files with 167 additions and 2 deletions

View File

@@ -39,6 +39,8 @@
#include "ProxyParser.h"
#include "QueryParser.h"
extern "C" size_t BUN_DEFAULT_MAX_HTTP_HEADER_SIZE;
namespace uWS
{
@@ -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) {

View File

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

View File

@@ -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{
@@ -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"))

View File

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

View File

@@ -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,
@@ -2261,7 +2264,12 @@ export default {
IncomingMessage,
request,
get,
maxHeaderSize: 16384,
get maxHeaderSize() {
return getMaxHTTPHeaderSize();
},
set maxHeaderSize(value) {
setMaxHTTPHeaderSize(value);
},
validateHeaderName,
validateHeaderValue,
setMaxIdleHTTPParsers(max) {

View File

@@ -0,0 +1,33 @@
import http from "node:http";
if (http.maxHeaderSize !== parseInt(process.env.BUN_HTTP_MAX_HEADER_SIZE, 10)) {
throw new Error("BUN_HTTP_MAX_HEADER_SIZE is not set to the correct value");
}
using server = Bun.serve({
port: 0,
fetch(req) {
return new Response(JSON.stringify(req.headers, null, 2));
},
});
await fetch(`${server.url}/`, {
headers: {
"Huge": Buffer.alloc(Math.max(http.maxHeaderSize, 256) - 256, "abc").toString(),
},
});
try {
await fetch(`${server.url}/`, {
headers: {
"Huge": Buffer.alloc(http.maxHeaderSize + 1024, "abc").toString(),
},
});
throw new Error("bad");
} catch (e) {
if (e.message.includes("bad")) {
process.exit(1);
}
process.exit(0);
}

View File

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