authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-06 17:53:06-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-06 17:53:06-07:00
log3806091a106dca684fdcce2cb5c5dfb4941e825f
tree5e1292e264161cf3f7911c2ea9201e02528a7ff8
parent24b4e643f486ce803f758a6eab0c587c44b9cfa4

std.http.Client: fix handling of \r\n before next chunk size


1 files changed, 99 insertions(+), 28 deletions(-)

lib/std/http/Client.zig+99-28
...@@ -1,7 +1,3 @@...@@ -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//!
5//! TODO: send connection: keep-alive and LRU cache a configurable number of1//! TODO: send connection: keep-alive and LRU cache a configurable number of
6//! open connections to skip DNS and TLS handshake for subsequent requests.2//! open connections to skip DNS and TLS handshake for subsequent requests.
73
...@@ -178,6 +174,8 @@ pub const Request = struct {...@@ -178,6 +174,8 @@ pub const Request = struct {
178 seen_rnr,174 seen_rnr,
179 finished,175 finished,
180 /// Begin transfer-encoding: chunked parsing states.176 /// Begin transfer-encoding: chunked parsing states.
177 chunk_size_prefix_r,
178 chunk_size_prefix_n,
181 chunk_size,179 chunk_size,
182 chunk_r,180 chunk_r,
183 chunk_data,181 chunk_data,
...@@ -382,6 +380,8 @@ pub const Request = struct {...@@ -382,6 +380,8 @@ pub const Request = struct {
382 continue :state;380 continue :state;
383 },381 },
384 },382 },
383 .chunk_size_prefix_r => unreachable,
384 .chunk_size_prefix_n => unreachable,
385 .chunk_size => unreachable,385 .chunk_size => unreachable,
386 .chunk_r => unreachable,386 .chunk_r => unreachable,
387 .chunk_data => unreachable,387 .chunk_data => unreachable,
...@@ -449,18 +449,6 @@ pub const Request = struct {...@@ -449,18 +449,6 @@ pub const Request = struct {
449 try expectEqual(@as(u10, 999), parseInt3("999".*));449 try expectEqual(@as(u10, 999), parseInt3("999".*));
450 }450 }
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
464 test "find headers end basic" {452 test "find headers end basic" {
465 var buffer: [1]u8 = undefined;453 var buffer: [1]u8 = undefined;
466 var r = Response.initStatic(&buffer);454 var r = Response.initStatic(&buffer);
...@@ -480,6 +468,29 @@ pub const Request = struct {...@@ -480,6 +468,29 @@ pub const Request = struct {
480 "\r\ncontent";468 "\r\ncontent";
481 try testing.expectEqual(@as(usize, 131), r.findHeadersEnd(example));469 try testing.expectEqual(@as(usize, 131), r.findHeadersEnd(example));
482 }470 }
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 }
483 };494 };
484495
485 pub const Headers = struct {496 pub const Headers = struct {
...@@ -536,8 +547,7 @@ pub const Request = struct {...@@ -536,8 +547,7 @@ pub const Request = struct {
536 /// This one can return 0 without meaning EOF.547 /// This one can return 0 without meaning EOF.
537 /// TODO change to readvAdvanced548 /// TODO change to readvAdvanced
538 pub fn readAdvanced(req: *Request, buffer: []u8) !usize {549 pub fn readAdvanced(req: *Request, buffer: []u8) !usize {
539 const amt = try req.connection.read(buffer);550 var in = buffer[0..try req.connection.read(buffer)];
540 var in = buffer[0..amt];
541 var out_index: usize = 0;551 var out_index: usize = 0;
542 while (true) {552 while (true) {
543 switch (req.response.state) {553 switch (req.response.state) {
...@@ -571,7 +581,8 @@ pub const Request = struct {...@@ -571,7 +581,8 @@ pub const Request = struct {
571 req.deinit();581 req.deinit();
572 req.* = new_req;582 req.* = new_req;
573 assert(out_index == 0);583 assert(out_index == 0);
574 return readAdvanced(req, buffer);584 in = buffer[0..try req.connection.read(buffer)];
585 continue;
575 }586 }
576587
577 if (req.response.headers.transfer_encoding) |transfer_encoding| {588 if (req.response.headers.transfer_encoding) |transfer_encoding| {
...@@ -598,8 +609,50 @@ pub const Request = struct {...@@ -598,8 +609,50 @@ pub const Request = struct {
598 return 0;609 return 0;
599 },610 },
600 .finished => {611 .finished => {
601 mem.copy(u8, buffer[out_index..], in);612 if (in.ptr == buffer.ptr) {
602 return out_index + in.len;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 },
603 },656 },
604 .chunk_size, .chunk_r => {657 .chunk_size, .chunk_r => {
605 const i = req.response.findChunkedLen(in);658 const i = req.response.findChunkedLen(in);
...@@ -619,20 +672,38 @@ pub const Request = struct {...@@ -619,20 +672,38 @@ pub const Request = struct {
619 },672 },
620 .chunk_data => {673 .chunk_data => {
621 const sub_amt = @min(req.response.next_chunk_length, in.len);674 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;
624 req.response.next_chunk_length -= sub_amt;675 req.response.next_chunk_length -= sub_amt;
625 if (req.response.next_chunk_length == 0) {676 if (req.response.next_chunk_length > 0) {
626 req.response.state = .chunk_size;677 if (in.ptr == buffer.ptr) {
627 in = in[sub_amt..];678 return sub_amt;
628 continue;679 } else {
680 mem.copy(u8, buffer[out_index..], in[0..sub_amt]);
681 out_index += sub_amt;
682 return out_index;
683 }
629 }684 }
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;
631 },690 },
632 }691 }
633 }692 }
634 }693 }
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
636 test {707 test {
637 _ = Response;708 _ = Response;
638 }709 }