mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Add http_max_response_size
This adds a --http_max_response_size argument to the serve and fetch command which is enforced by the HTTP client. This defaults to null, no limit. As-is, the ScriptManager allocates a buffer based on Content-Length. Without setting this flag, a server could simply reply with Content-Length: 99999999999 9999999999 to cause an OOM. This new flag is checked both once we have the header if there's a content-length, and when reading the body. Also requested in https://github.com/lightpanda-io/browser/issues/415
This commit is contained in:
@@ -103,6 +103,13 @@ pub fn httpMaxRedirects(_: *const Config) u8 {
|
|||||||
return 10;
|
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 {
|
pub fn logLevel(self: *const Config) ?log.Level {
|
||||||
return switch (self.mode) {
|
return switch (self.mode) {
|
||||||
inline .serve, .fetch => |opts| opts.common.log_level,
|
inline .serve, .fetch => |opts| opts.common.log_level,
|
||||||
@@ -164,6 +171,7 @@ pub const Common = struct {
|
|||||||
http_max_host_open: ?u8 = null,
|
http_max_host_open: ?u8 = null,
|
||||||
http_timeout: ?u31 = null,
|
http_timeout: ?u31 = null,
|
||||||
http_connect_timeout: ?u31 = null,
|
http_connect_timeout: ?u31 = null,
|
||||||
|
http_max_response_size: ?usize = null,
|
||||||
tls_verify_host: bool = true,
|
tls_verify_host: bool = true,
|
||||||
log_level: ?log.Level = null,
|
log_level: ?log.Level = null,
|
||||||
log_format: ?log.Format = 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.
|
\\ to complete. 0 means it never times out.
|
||||||
\\ Defaults to 10000.
|
\\ 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.
|
\\--log_level The log level: debug, info, warn, error or fatal.
|
||||||
\\ Defaults to
|
\\ Defaults to
|
||||||
++ (if (builtin.mode == .Debug) " info." else "warn.") ++
|
++ (if (builtin.mode == .Debug) " info." else "warn.") ++
|
||||||
@@ -683,6 +696,19 @@ fn parseCommonArg(
|
|||||||
return true;
|
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)) {
|
if (std.mem.eql(u8, "--log_level", opt)) {
|
||||||
const str = args.next() orelse {
|
const str = args.next() orelse {
|
||||||
log.fatal(.app, "missing argument value", .{ .arg = "--log_level" });
|
log.fatal(.app, "missing argument value", .{ .arg = "--log_level" });
|
||||||
|
|||||||
@@ -366,17 +366,16 @@ fn makeTransfer(self: *Client, req: Request) !*Transfer {
|
|||||||
.req = req,
|
.req = req,
|
||||||
.ctx = req.ctx,
|
.ctx = req.ctx,
|
||||||
.client = self,
|
.client = self,
|
||||||
|
.max_response_size = self.config.httpMaxResponseSize(),
|
||||||
};
|
};
|
||||||
return transfer;
|
return transfer;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn requestFailed(transfer: *Transfer, err: anyerror, comptime execute_callback: bool) void {
|
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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,6 +786,7 @@ pub const Request = struct {
|
|||||||
resource_type: ResourceType,
|
resource_type: ResourceType,
|
||||||
credentials: ?[:0]const u8 = null,
|
credentials: ?[:0]const u8 = null,
|
||||||
notification: *Notification,
|
notification: *Notification,
|
||||||
|
max_response_size: ?usize = null,
|
||||||
|
|
||||||
// This is only relevant for intercepted requests. If a request is flagged
|
// 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
|
// 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.
|
// the headers, and the [encoded] body.
|
||||||
bytes_received: usize = 0,
|
bytes_received: usize = 0,
|
||||||
|
|
||||||
|
max_response_size: ?usize = null,
|
||||||
|
|
||||||
// We'll store the response header here
|
// We'll store the response header here
|
||||||
response_header: ?ResponseHeader = null,
|
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| {
|
const proceed = transfer.req.header_callback(transfer) catch |err| {
|
||||||
log.err(.http, "header_callback", .{ .err = err, .req = transfer });
|
log.err(.http, "header_callback", .{ .err = err, .req = transfer });
|
||||||
return err;
|
return err;
|
||||||
@@ -1276,6 +1286,13 @@ pub const Transfer = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transfer.bytes_received += chunk_len;
|
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];
|
const chunk = buffer[0..chunk_len];
|
||||||
transfer.req.data_callback(transfer, chunk) catch |err| {
|
transfer.req.data_callback(transfer, chunk) catch |err| {
|
||||||
log.err(.http, "data_callback", .{ .err = err, .req = transfer });
|
log.err(.http, "data_callback", .{ .err = err, .req = transfer });
|
||||||
|
|||||||
Reference in New Issue
Block a user