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//! 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 }