Skip to content

Commit 30ff17d

Browse files
committed
Skip large header lines that don't fit into the header buffer.
#672
1 parent d7a3e2f commit 30ff17d

File tree

1 file changed

+52
-13
lines changed

1 file changed

+52
-13
lines changed

src/http/client.zig

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,13 +1564,17 @@ const Reader = struct {
15641564

15651565
header_done: bool,
15661566

1567+
// Whether or not the current header has to be skipped [because it's too long].
1568+
skip_current_header: bool,
1569+
15671570
fn init(state: *State, keepalive: *bool) Reader {
15681571
return .{
15691572
.pos = 0,
15701573
.response = .{},
15711574
.body_reader = null,
15721575
.header_done = false,
15731576
.keepalive = keepalive,
1577+
.skip_current_header = false,
15741578
.header_buf = state.header_buf,
15751579
.arena = state.arena.allocator(),
15761580
};
@@ -1610,6 +1614,17 @@ const Reader = struct {
16101614
var done = false;
16111615
var unprocessed = data;
16121616

1617+
if (self.skip_current_header) {
1618+
const index = std.mem.indexOfScalarPos(u8, data, 0, '\n') orelse {
1619+
// discard all of this data, since it belongs to a header we
1620+
// want to skip
1621+
return .{ .done = false, .data = null, .unprocessed = null };
1622+
};
1623+
self.pos = 0;
1624+
self.skip_current_header = false;
1625+
unprocessed = data[index + 1 ..];
1626+
}
1627+
16131628
// Data from a previous call to process that we weren't able to parse
16141629
const pos = self.pos;
16151630
const header_buf = self.header_buf;
@@ -1624,21 +1639,26 @@ const Reader = struct {
16241639
// data doesn't represent a complete header line. We need more data
16251640
const end = pos + data.len;
16261641
if (end > header_buf.len) {
1627-
return error.HeaderTooLarge;
1642+
self.prepareToSkipLongHeader();
1643+
} else {
1644+
self.pos = end;
1645+
@memcpy(self.header_buf[pos..end], data);
16281646
}
1629-
self.pos = end;
1630-
@memcpy(self.header_buf[pos..end], data);
16311647
return .{ .done = false, .data = null, .unprocessed = null };
16321648
}) + 1;
16331649

16341650
const end = pos + line_end;
16351651
if (end > header_buf.len) {
1636-
return error.HeaderTooLarge;
1652+
unprocessed = &.{};
1653+
self.prepareToSkipLongHeader();
1654+
// we can disable this immediately, since we've essentially
1655+
// finished skipping it this point.
1656+
self.skip_current_header = false;
1657+
} else {
1658+
@memcpy(header_buf[pos..end], data[0..line_end]);
1659+
done, unprocessed = try self.parseHeader(header_buf[0..end]);
16371660
}
16381661

1639-
@memcpy(header_buf[pos..end], data[0..line_end]);
1640-
done, unprocessed = try self.parseHeader(header_buf[0..end]);
1641-
16421662
// we gave parseHeader exactly 1 header line, there should be no leftovers
16431663
std.debug.assert(unprocessed.len == 0);
16441664

@@ -1661,10 +1681,11 @@ const Reader = struct {
16611681
const p = self.pos; // don't use pos, self.pos might have been altered
16621682
const end = p + unprocessed.len;
16631683
if (end > header_buf.len) {
1664-
return error.HeaderTooLarge;
1684+
self.prepareToSkipLongHeader();
1685+
} else {
1686+
@memcpy(header_buf[p..end], unprocessed);
1687+
self.pos = end;
16651688
}
1666-
@memcpy(header_buf[p..end], unprocessed);
1667-
self.pos = end;
16681689
return .{ .done = false, .data = null, .unprocessed = null };
16691690
}
16701691
}
@@ -1724,6 +1745,13 @@ const Reader = struct {
17241745
return .{ .done = false, .data = null, .unprocessed = null };
17251746
}
17261747

1748+
fn prepareToSkipLongHeader(self: *Reader) void {
1749+
self.skip_current_header = true;
1750+
const buf = self.header_buf;
1751+
const pos = std.mem.indexOfScalar(u8, buf, ':') orelse @min(buf.len, 20);
1752+
log.warn(.http_client, "skipping long header", .{ .name = buf[0..pos] });
1753+
}
1754+
17271755
// returns true when done
17281756
// returns any remaining unprocessed data
17291757
// When done == true, the remaining data must belong to the body
@@ -1770,6 +1798,15 @@ const Reader = struct {
17701798
};
17711799
const name_end = pos + sep;
17721800

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

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

24832520
{
2484-
// header too big
2485-
const data = "HTTP/1.1 200 OK\r\n" ++ ("a" ** 1500);
2486-
try testing.expectError(error.HeaderTooLarge, testReader(&state, &res, data));
2521+
// skips large headers
2522+
const data = "HTTP/1.1 200 OK\r\na: b\r\n" ++ ("a" ** 5000) ++ ": wow\r\nx:zz\r\n\r\n";
2523+
try testReader(&state, &res, data);
2524+
try testing.expectEqual(200, res.status);
2525+
try res.assertHeaders(&.{ "a", "b", "x", "zz" });
24872526
}
24882527
}
24892528
}

0 commit comments

Comments
 (0)