Merge pull request #1499 from lightpanda-io/http_max_response_size

Add http_max_response_size
This commit is contained in:
Karl Seguin
2026-02-09 16:02:27 +08:00
committed by GitHub
2 changed files with 48 additions and 5 deletions

View File

@@ -103,6 +103,13 @@ pub fn httpMaxRedirects(_: *const Config) u8 {
return 10;
}
pub fn httpMaxResponseSize(self: *const Config) ?usize {
return switch (self.mode) {
inline .serve, .fetch => |opts| opts.common.http_max_response_size,
else => unreachable,
};
}
pub fn logLevel(self: *const Config) ?log.Level {
return switch (self.mode) {
inline .serve, .fetch => |opts| opts.common.log_level,
@@ -164,6 +171,7 @@ pub const Common = struct {
http_max_host_open: ?u8 = null,
http_timeout: ?u31 = null,
http_connect_timeout: ?u31 = null,
http_max_response_size: ?usize = null,
tls_verify_host: bool = true,
log_level: ?log.Level = null,
log_format: ?log.Format = null,
@@ -249,6 +257,11 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void {
\\ to complete. 0 means it never times out.
\\ Defaults to 10000.
\\
\\--http_max_response_size
\\ Limits the acceptable response size for any request
\\ (e.g. XHR, fetch, script loading, ...).
\\ Defaults to no limit.
\\
\\--log_level The log level: debug, info, warn, error or fatal.
\\ Defaults to
++ (if (builtin.mode == .Debug) " info." else "warn.") ++
@@ -683,6 +696,19 @@ fn parseCommonArg(
return true;
}
if (std.mem.eql(u8, "--http_max_response_size", opt)) {
const str = args.next() orelse {
log.fatal(.app, "missing argument value", .{ .arg = "--http_max_response_size" });
return error.InvalidArgument;
};
common.http_max_response_size = std.fmt.parseInt(usize, str, 10) catch |err| {
log.fatal(.app, "invalid argument value", .{ .arg = "--http_max_response_size", .err = err });
return error.InvalidArgument;
};
return true;
}
if (std.mem.eql(u8, "--log_level", opt)) {
const str = args.next() orelse {
log.fatal(.app, "missing argument value", .{ .arg = "--log_level" });

View File

@@ -366,17 +366,16 @@ fn makeTransfer(self: *Client, req: Request) !*Transfer {
.req = req,
.ctx = req.ctx,
.client = self,
.max_response_size = self.config.httpMaxResponseSize(),
};
return transfer;
}
fn requestFailed(transfer: *Transfer, err: anyerror, comptime execute_callback: bool) void {
// this shouldn't happen, we'll crash in debug mode. But in release, we'll
// just noop this state.
if (comptime IS_DEBUG) {
std.debug.assert(transfer._notified_fail == false);
}
if (transfer._notified_fail) {
// we can force a failed request within a callback, which will eventually
// result in this being called again in the more general loop. We do this
// because we can raise a more specific error inside a callback in some cases
return;
}
@@ -787,6 +786,7 @@ pub const Request = struct {
resource_type: ResourceType,
credentials: ?[:0]const u8 = null,
notification: *Notification,
max_response_size: ?usize = null,
// This is only relevant for intercepted requests. If a request is flagged
// as blocking AND is intercepted, then it'll be up to us to wait until
@@ -877,6 +877,8 @@ pub const Transfer = struct {
// the headers, and the [encoded] body.
bytes_received: usize = 0,
max_response_size: ?usize = null,
// We'll store the response header here
response_header: ?ResponseHeader = null,
@@ -1125,6 +1127,14 @@ pub const Transfer = struct {
}
}
if (transfer.max_response_size) |max_size| {
if (transfer.getContentLength()) |cl| {
if (cl > max_size) {
return error.ResponseTooLarge;
}
}
}
const proceed = transfer.req.header_callback(transfer) catch |err| {
log.err(.http, "header_callback", .{ .err = err, .req = transfer });
return err;
@@ -1276,6 +1286,13 @@ pub const Transfer = struct {
}
transfer.bytes_received += chunk_len;
if (transfer.max_response_size) |max_size| {
if (transfer.bytes_received > max_size) {
requestFailed(transfer, error.ResponseTooLarge, true);
return -1;
}
}
const chunk = buffer[0..chunk_len];
transfer.req.data_callback(transfer, chunk) catch |err| {
log.err(.http, "data_callback", .{ .err = err, .req = transfer });