Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 52 additions & 13 deletions src/http/client.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1564,13 +1564,17 @@ const Reader = struct {

header_done: bool,

// Whether or not the current header has to be skipped [because it's too long].
skip_current_header: bool,

fn init(state: *State, keepalive: *bool) Reader {
return .{
.pos = 0,
.response = .{},
.body_reader = null,
.header_done = false,
.keepalive = keepalive,
.skip_current_header = false,
.header_buf = state.header_buf,
.arena = state.arena.allocator(),
};
Expand Down Expand Up @@ -1610,6 +1614,17 @@ const Reader = struct {
var done = false;
var unprocessed = data;

if (self.skip_current_header) {
const index = std.mem.indexOfScalarPos(u8, data, 0, '\n') orelse {
// discard all of this data, since it belongs to a header we
// want to skip
return .{ .done = false, .data = null, .unprocessed = null };
};
self.pos = 0;
self.skip_current_header = false;
unprocessed = data[index + 1 ..];
}

// Data from a previous call to process that we weren't able to parse
const pos = self.pos;
const header_buf = self.header_buf;
Expand All @@ -1624,21 +1639,26 @@ const Reader = struct {
// data doesn't represent a complete header line. We need more data
const end = pos + data.len;
if (end > header_buf.len) {
return error.HeaderTooLarge;
self.prepareToSkipLongHeader();
} else {
self.pos = end;
@memcpy(self.header_buf[pos..end], data);
}
self.pos = end;
@memcpy(self.header_buf[pos..end], data);
return .{ .done = false, .data = null, .unprocessed = null };
}) + 1;

const end = pos + line_end;
if (end > header_buf.len) {
return error.HeaderTooLarge;
unprocessed = &.{};
self.prepareToSkipLongHeader();
// we can disable this immediately, since we've essentially
// finished skipping it this point.
self.skip_current_header = false;
} else {
@memcpy(header_buf[pos..end], data[0..line_end]);
done, unprocessed = try self.parseHeader(header_buf[0..end]);
}

@memcpy(header_buf[pos..end], data[0..line_end]);
done, unprocessed = try self.parseHeader(header_buf[0..end]);

// we gave parseHeader exactly 1 header line, there should be no leftovers
std.debug.assert(unprocessed.len == 0);

Expand All @@ -1661,10 +1681,11 @@ const Reader = struct {
const p = self.pos; // don't use pos, self.pos might have been altered
const end = p + unprocessed.len;
if (end > header_buf.len) {
return error.HeaderTooLarge;
self.prepareToSkipLongHeader();
} else {
@memcpy(header_buf[p..end], unprocessed);
self.pos = end;
}
@memcpy(header_buf[p..end], unprocessed);
self.pos = end;
return .{ .done = false, .data = null, .unprocessed = null };
}
}
Expand Down Expand Up @@ -1724,6 +1745,13 @@ const Reader = struct {
return .{ .done = false, .data = null, .unprocessed = null };
}

fn prepareToSkipLongHeader(self: *Reader) void {
self.skip_current_header = true;
const buf = self.header_buf;
const pos = std.mem.indexOfScalar(u8, buf, ':') orelse @min(buf.len, 20);
log.warn(.http_client, "skipping long header", .{ .name = buf[0..pos] });
}

// returns true when done
// returns any remaining unprocessed data
// When done == true, the remaining data must belong to the body
Expand Down Expand Up @@ -1770,6 +1798,15 @@ const Reader = struct {
};
const name_end = pos + sep;

if (value_end - pos > MAX_HEADER_LINE_LEN) {
// at this point, we could return this header, but then it would
// be inconsistent with long headers that are split up and need
// to be buffered.
log.warn(.http_client, "skipping long header", .{ .name = data[pos..name_end] });
pos = value_end + 1;
continue;
}

const value_start = name_end + 1;

if (value_end == value_start or data[value_end - 1] != '\r') {
Expand Down Expand Up @@ -2481,9 +2518,11 @@ test "HttpClient Reader: fuzz" {
}

{
// header too big
const data = "HTTP/1.1 200 OK\r\n" ++ ("a" ** 1500);
try testing.expectError(error.HeaderTooLarge, testReader(&state, &res, data));
// skips large headers
const data = "HTTP/1.1 200 OK\r\na: b\r\n" ++ ("a" ** 5000) ++ ": wow\r\nx:zz\r\n\r\n";
try testReader(&state, &res, data);
try testing.expectEqual(200, res.status);
try res.assertHeaders(&.{ "a", "b", "x", "zz" });
}
}
}
Expand Down