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 \n content" ;
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