Skip to content

Commit 3806091

Browse files
committed
std.http.Client: fix handling of \r\n before next chunk size
1 parent 24b4e64 commit 3806091

1 file changed

Lines changed: 99 additions & 28 deletions

File tree

lib/std/http/Client.zig

Lines changed: 99 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
//! This API is a barely-touched, barely-functional http client, just the
2-
//! absolute minimum thing I needed in order to test `std.crypto.tls`. Bear
3-
//! with me and I promise the API will become useful and streamlined.
4-
//!
51
//! TODO: send connection: keep-alive and LRU cache a configurable number of
62
//! open connections to skip DNS and TLS handshake for subsequent requests.
73

@@ -178,6 +174,8 @@ pub const Request = struct {
178174
seen_rnr,
179175
finished,
180176
/// Begin transfer-encoding: chunked parsing states.
177+
chunk_size_prefix_r,
178+
chunk_size_prefix_n,
181179
chunk_size,
182180
chunk_r,
183181
chunk_data,
@@ -382,6 +380,8 @@ pub const Request = struct {
382380
continue :state;
383381
},
384382
},
383+
.chunk_size_prefix_r => unreachable,
384+
.chunk_size_prefix_n => unreachable,
385385
.chunk_size => unreachable,
386386
.chunk_r => unreachable,
387387
.chunk_data => unreachable,
@@ -449,18 +449,6 @@ pub const Request = struct {
449449
try expectEqual(@as(u10, 999), parseInt3("999".*));
450450
}
451451

452-
inline fn int16(array: *const [2]u8) u16 {
453-
return @bitCast(u16, array.*);
454-
}
455-
456-
inline fn int32(array: *const [4]u8) u32 {
457-
return @bitCast(u32, array.*);
458-
}
459-
460-
inline fn int64(array: *const [8]u8) u64 {
461-
return @bitCast(u64, array.*);
462-
}
463-
464452
test "find headers end basic" {
465453
var buffer: [1]u8 = undefined;
466454
var r = Response.initStatic(&buffer);
@@ -480,6 +468,29 @@ pub const Request = struct {
480468
"\r\ncontent";
481469
try testing.expectEqual(@as(usize, 131), r.findHeadersEnd(example));
482470
}
471+
472+
test "find headers end bug" {
473+
var buffer: [1]u8 = undefined;
474+
var r = Response.initStatic(&buffer);
475+
const trail = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
476+
const example =
477+
"HTTP/1.1 200 OK\r\n" ++
478+
"Access-Control-Allow-Origin: https://render.githubusercontent.com\r\n" ++
479+
"content-disposition: attachment; filename=zig-0.10.0.tar.gz\r\n" ++
480+
"Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox\r\n" ++
481+
"Content-Type: application/x-gzip\r\n" ++
482+
"ETag: \"bfae0af6b01c7c0d89eb667cb5f0e65265968aeebda2689177e6b26acd3155ca\"\r\n" ++
483+
"Strict-Transport-Security: max-age=31536000\r\n" ++
484+
"Vary: Authorization,Accept-Encoding,Origin\r\n" ++
485+
"X-Content-Type-Options: nosniff\r\n" ++
486+
"X-Frame-Options: deny\r\n" ++
487+
"X-XSS-Protection: 1; mode=block\r\n" ++
488+
"Date: Fri, 06 Jan 2023 22:26:22 GMT\r\n" ++
489+
"Transfer-Encoding: chunked\r\n" ++
490+
"X-GitHub-Request-Id: 89C6:17E9:A7C9E:124B51:63B8A00E\r\n" ++
491+
"connection: close\r\n\r\n" ++ trail;
492+
try testing.expectEqual(@as(usize, example.len - trail.len), r.findHeadersEnd(example));
493+
}
483494
};
484495

485496
pub const Headers = struct {
@@ -536,8 +547,7 @@ pub const Request = struct {
536547
/// This one can return 0 without meaning EOF.
537548
/// TODO change to readvAdvanced
538549
pub fn readAdvanced(req: *Request, buffer: []u8) !usize {
539-
const amt = try req.connection.read(buffer);
540-
var in = buffer[0..amt];
550+
var in = buffer[0..try req.connection.read(buffer)];
541551
var out_index: usize = 0;
542552
while (true) {
543553
switch (req.response.state) {
@@ -571,7 +581,8 @@ pub const Request = struct {
571581
req.deinit();
572582
req.* = new_req;
573583
assert(out_index == 0);
574-
return readAdvanced(req, buffer);
584+
in = buffer[0..try req.connection.read(buffer)];
585+
continue;
575586
}
576587

577588
if (req.response.headers.transfer_encoding) |transfer_encoding| {
@@ -598,8 +609,50 @@ pub const Request = struct {
598609
return 0;
599610
},
600611
.finished => {
601-
mem.copy(u8, buffer[out_index..], in);
602-
return out_index + in.len;
612+
if (in.ptr == buffer.ptr) {
613+
return in.len;
614+
} else {
615+
mem.copy(u8, buffer[out_index..], in);
616+
return out_index + in.len;
617+
}
618+
},
619+
.chunk_size_prefix_r => switch (in.len) {
620+
0 => return out_index,
621+
1 => switch (in[0]) {
622+
'\r' => {
623+
req.response.state = .chunk_size_prefix_n;
624+
return out_index;
625+
},
626+
else => {
627+
req.response.state = .invalid;
628+
return error.HttpHeadersInvalid;
629+
},
630+
},
631+
else => switch (int16(in[0..2])) {
632+
int16("\r\n") => {
633+
in = in[2..];
634+
req.response.state = .chunk_size;
635+
continue;
636+
},
637+
else => {
638+
req.response.state = .invalid;
639+
return error.HttpHeadersInvalid;
640+
},
641+
},
642+
},
643+
.chunk_size_prefix_n => switch (in.len) {
644+
0 => return out_index,
645+
else => switch (in[0]) {
646+
'\n' => {
647+
in = in[1..];
648+
req.response.state = .chunk_size;
649+
continue;
650+
},
651+
else => {
652+
req.response.state = .invalid;
653+
return error.HttpHeadersInvalid;
654+
},
655+
},
603656
},
604657
.chunk_size, .chunk_r => {
605658
const i = req.response.findChunkedLen(in);
@@ -619,20 +672,38 @@ pub const Request = struct {
619672
},
620673
.chunk_data => {
621674
const sub_amt = @min(req.response.next_chunk_length, in.len);
622-
mem.copy(u8, buffer[out_index..], in[0..sub_amt]);
623-
out_index += sub_amt;
624675
req.response.next_chunk_length -= sub_amt;
625-
if (req.response.next_chunk_length == 0) {
626-
req.response.state = .chunk_size;
627-
in = in[sub_amt..];
628-
continue;
676+
if (req.response.next_chunk_length > 0) {
677+
if (in.ptr == buffer.ptr) {
678+
return sub_amt;
679+
} else {
680+
mem.copy(u8, buffer[out_index..], in[0..sub_amt]);
681+
out_index += sub_amt;
682+
return out_index;
683+
}
629684
}
630-
return out_index;
685+
mem.copy(u8, buffer[out_index..], in[0..sub_amt]);
686+
out_index += sub_amt;
687+
req.response.state = .chunk_size_prefix_r;
688+
in = in[sub_amt..];
689+
continue;
631690
},
632691
}
633692
}
634693
}
635694

695+
inline fn int16(array: *const [2]u8) u16 {
696+
return @bitCast(u16, array.*);
697+
}
698+
699+
inline fn int32(array: *const [4]u8) u32 {
700+
return @bitCast(u32, array.*);
701+
}
702+
703+
inline fn int64(array: *const [8]u8) u64 {
704+
return @bitCast(u64, array.*);
705+
}
706+
636707
test {
637708
_ = Response;
638709
}

0 commit comments

Comments
 (0)