| ... | ... | @@ -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 | 1 | //! TODO: send connection: keep-alive and LRU cache a configurable number of |
| 6 | 2 | //! open connections to skip DNS and TLS handshake for subsequent requests. |
| 7 | 3 | |
| ... | ... | @@ -178,6 +174,8 @@ pub const Request = struct { |
| 178 | 174 | seen_rnr, |
| 179 | 175 | finished, |
| 180 | 176 | /// Begin transfer-encoding: chunked parsing states. |
| 177 | chunk_size_prefix_r, |
| 178 | chunk_size_prefix_n, |
| 181 | 179 | chunk_size, |
| 182 | 180 | chunk_r, |
| 183 | 181 | chunk_data, |
| ... | ... | @@ -382,6 +380,8 @@ pub const Request = struct { |
| 382 | 380 | continue :state; |
| 383 | 381 | }, |
| 384 | 382 | }, |
| 383 | .chunk_size_prefix_r => unreachable, |
| 384 | .chunk_size_prefix_n => unreachable, |
| 385 | 385 | .chunk_size => unreachable, |
| 386 | 386 | .chunk_r => unreachable, |
| 387 | 387 | .chunk_data => unreachable, |
| ... | ... | @@ -449,18 +449,6 @@ pub const Request = struct { |
| 449 | 449 | try expectEqual(@as(u10, 999), parseInt3("999".*)); |
| 450 | 450 | } |
| 451 | 451 | |
| 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 | 452 | test "find headers end basic" { |
| 465 | 453 | var buffer: [1]u8 = undefined; |
| 466 | 454 | var r = Response.initStatic(&buffer); |
| ... | ... | @@ -480,6 +468,29 @@ pub const Request = struct { |
| 480 | 468 | "\r\ncontent"; |
| 481 | 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 | }; |
| 484 | 495 | |
| 485 | 496 | pub const Headers = struct { |
| ... | ... | @@ -536,8 +547,7 @@ pub const Request = struct { |
| 536 | 547 | /// This one can return 0 without meaning EOF. |
| 537 | 548 | /// TODO change to readvAdvanced |
| 538 | 549 | 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)]; |
| 541 | 551 | var out_index: usize = 0; |
| 542 | 552 | while (true) { |
| 543 | 553 | switch (req.response.state) { |
| ... | ... | @@ -571,7 +581,8 @@ pub const Request = struct { |
| 571 | 581 | req.deinit(); |
| 572 | 582 | req.* = new_req; |
| 573 | 583 | assert(out_index == 0); |
| 574 | | return readAdvanced(req, buffer); |
| 584 | in = buffer[0..try req.connection.read(buffer)]; |
| 585 | continue; |
| 575 | 586 | } |
| 576 | 587 | |
| 577 | 588 | if (req.response.headers.transfer_encoding) |transfer_encoding| { |
| ... | ... | @@ -598,8 +609,50 @@ pub const Request = struct { |
| 598 | 609 | return 0; |
| 599 | 610 | }, |
| 600 | 611 | .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 | }, |
| 603 | 656 | }, |
| 604 | 657 | .chunk_size, .chunk_r => { |
| 605 | 658 | const i = req.response.findChunkedLen(in); |
| ... | ... | @@ -619,20 +672,38 @@ pub const Request = struct { |
| 619 | 672 | }, |
| 620 | 673 | .chunk_data => { |
| 621 | 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 | 675 | 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 | } |
| 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 | } |
| 635 | 694 | |
| 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 | 707 | test { |
| 637 | 708 | _ = Response; |
| 638 | 709 | } |