@@ -3140,15 +3140,20 @@ where
31403140 if let Some ( end) = find_crlf ( & parse_buf, pos) {
31413141 break end;
31423142 }
3143- let n = reader. read ( & mut read_buf) . await . into_diagnostic ( ) ?;
3144- if n == 0 {
3145- return Err ( miette ! ( "Chunked body ended before chunk-size line" ) ) ;
3146- }
3147- if let Some ( guard) = generation_guard {
3148- guard. ensure_current ( ) ?;
3149- }
3150- writer. write_all ( & read_buf[ ..n] ) . await . into_diagnostic ( ) ?;
3151- parse_buf. extend_from_slice ( & read_buf[ ..n] ) ;
3143+ let target_len = parse_buf
3144+ . len ( )
3145+ . checked_add ( 1 )
3146+ . ok_or_else ( || miette ! ( "Chunked body size overflow" ) ) ?;
3147+ relay_chunked_until_len (
3148+ reader,
3149+ writer,
3150+ & mut read_buf,
3151+ & mut parse_buf,
3152+ target_len,
3153+ generation_guard,
3154+ "Chunked body ended before chunk-size line" ,
3155+ )
3156+ . await ?;
31523157 } ;
31533158
31543159 let size_line = std:: str:: from_utf8 ( & parse_buf[ pos..size_line_end] )
@@ -3172,15 +3177,20 @@ where
31723177 if let Some ( end) = find_crlf ( & parse_buf, pos) {
31733178 break end;
31743179 }
3175- let n = reader. read ( & mut read_buf) . await . into_diagnostic ( ) ?;
3176- if n == 0 {
3177- return Err ( miette ! ( "Chunked body ended before trailer terminator" ) ) ;
3178- }
3179- if let Some ( guard) = generation_guard {
3180- guard. ensure_current ( ) ?;
3181- }
3182- writer. write_all ( & read_buf[ ..n] ) . await . into_diagnostic ( ) ?;
3183- parse_buf. extend_from_slice ( & read_buf[ ..n] ) ;
3180+ let target_len = parse_buf
3181+ . len ( )
3182+ . checked_add ( 1 )
3183+ . ok_or_else ( || miette ! ( "Chunked trailer size overflow" ) ) ?;
3184+ relay_chunked_until_len (
3185+ reader,
3186+ writer,
3187+ & mut read_buf,
3188+ & mut parse_buf,
3189+ target_len,
3190+ generation_guard,
3191+ "Chunked body ended before trailer terminator" ,
3192+ )
3193+ . await ?;
31843194 } ;
31853195
31863196 let trailer_line = & parse_buf[ pos..trailer_end] ;
@@ -3207,17 +3217,16 @@ where
32073217 . checked_add ( 2 )
32083218 . ok_or_else ( || miette ! ( "Chunk size overflow" ) ) ?;
32093219
3210- while parse_buf. len ( ) < chunk_with_crlf_end {
3211- let n = reader. read ( & mut read_buf) . await . into_diagnostic ( ) ?;
3212- if n == 0 {
3213- return Err ( miette ! ( "Chunked body ended mid-chunk" ) ) ;
3214- }
3215- if let Some ( guard) = generation_guard {
3216- guard. ensure_current ( ) ?;
3217- }
3218- writer. write_all ( & read_buf[ ..n] ) . await . into_diagnostic ( ) ?;
3219- parse_buf. extend_from_slice ( & read_buf[ ..n] ) ;
3220- }
3220+ relay_chunked_until_len (
3221+ reader,
3222+ writer,
3223+ & mut read_buf,
3224+ & mut parse_buf,
3225+ chunk_with_crlf_end,
3226+ generation_guard,
3227+ "Chunked body ended mid-chunk" ,
3228+ )
3229+ . await ?;
32213230 if & parse_buf[ chunk_end..chunk_with_crlf_end] != b"\r \n " {
32223231 return Err ( miette ! ( "Chunk missing terminating CRLF" ) ) ;
32233232 }
@@ -3233,6 +3242,43 @@ where
32333242 }
32343243}
32353244
3245+ /// Read and forward only the bytes needed to reach `target_len`.
3246+ ///
3247+ /// Limiting each socket read to the current chunked framing boundary prevents
3248+ /// a pipelined request from being consumed and written upstream as part of the
3249+ /// authorized request body.
3250+ async fn relay_chunked_until_len < R , W > (
3251+ reader : & mut R ,
3252+ writer : & mut W ,
3253+ read_buf : & mut [ u8 ; RELAY_BUF_SIZE ] ,
3254+ parse_buf : & mut Vec < u8 > ,
3255+ target_len : usize ,
3256+ generation_guard : Option < & PolicyGenerationGuard > ,
3257+ eof_message : & ' static str ,
3258+ ) -> Result < ( ) >
3259+ where
3260+ R : AsyncRead + Unpin ,
3261+ W : AsyncWrite + Unpin ,
3262+ {
3263+ while parse_buf. len ( ) < target_len {
3264+ let remaining = target_len - parse_buf. len ( ) ;
3265+ let to_read = remaining. min ( read_buf. len ( ) ) ;
3266+ let n = reader
3267+ . read ( & mut read_buf[ ..to_read] )
3268+ . await
3269+ . into_diagnostic ( ) ?;
3270+ if n == 0 {
3271+ return Err ( miette ! ( eof_message) ) ;
3272+ }
3273+ if let Some ( guard) = generation_guard {
3274+ guard. ensure_current ( ) ?;
3275+ }
3276+ writer. write_all ( & read_buf[ ..n] ) . await . into_diagnostic ( ) ?;
3277+ parse_buf. extend_from_slice ( & read_buf[ ..n] ) ;
3278+ }
3279+ Ok ( ( ) )
3280+ }
3281+
32363282fn find_crlf ( buf : & [ u8 ] , start : usize ) -> Option < usize > {
32373283 buf. get ( start..) ?
32383284 . windows ( 2 )
@@ -4553,6 +4599,67 @@ mod tests {
45534599 }
45544600 }
45554601
4602+ #[ tokio:: test]
4603+ async fn relay_chunked_leaves_pipelined_request_for_next_policy_decision ( ) {
4604+ let chunked_body = b"0\r \n \r \n " ;
4605+ let pipelined_request =
4606+ b"DELETE /blocked HTTP/1.1\r \n Host: example.com\r \n Content-Length: 0\r \n \r \n " ;
4607+ let mut wire = chunked_body. to_vec ( ) ;
4608+ wire. extend_from_slice ( pipelined_request) ;
4609+
4610+ let ( mut relay_reader, mut client_writer) = tokio:: io:: duplex ( 4096 ) ;
4611+ client_writer. write_all ( & wire) . await . unwrap ( ) ;
4612+ let ( mut relay_writer, mut upstream_reader) = tokio:: io:: duplex ( 4096 ) ;
4613+
4614+ relay_chunked ( & mut relay_reader, & mut relay_writer, & [ ] , None )
4615+ . await
4616+ . expect ( "chunked body should relay" ) ;
4617+
4618+ let mut remaining = vec ! [ 0 ; pipelined_request. len( ) ] ;
4619+ relay_reader. read_exact ( & mut remaining) . await . unwrap ( ) ;
4620+ assert_eq ! ( remaining, pipelined_request) ;
4621+
4622+ drop ( relay_writer) ;
4623+ let mut forwarded = Vec :: new ( ) ;
4624+ upstream_reader. read_to_end ( & mut forwarded) . await . unwrap ( ) ;
4625+ assert_eq ! ( forwarded, chunked_body) ;
4626+ }
4627+
4628+ #[ tokio:: test]
4629+ async fn relay_chunked_with_forwarded_prefix_and_trailers_preserves_pipeline_boundary ( ) {
4630+ let already_forwarded = b"3\r \n a" ;
4631+ let body_remainder = b"bc\r \n 0\r \n X-Checksum: abc123\r \n \r \n " ;
4632+ let pipelined_request =
4633+ b"DELETE /blocked HTTP/1.1\r \n Host: example.com\r \n Content-Length: 0\r \n \r \n " ;
4634+ let mut later_read = body_remainder. to_vec ( ) ;
4635+ later_read. extend_from_slice ( pipelined_request) ;
4636+
4637+ let ( mut relay_reader, mut client_writer) = tokio:: io:: duplex ( 4096 ) ;
4638+ client_writer. write_all ( & later_read) . await . unwrap ( ) ;
4639+ let ( mut relay_writer, mut upstream_reader) = tokio:: io:: duplex ( 4096 ) ;
4640+ relay_writer. write_all ( already_forwarded) . await . unwrap ( ) ;
4641+
4642+ relay_chunked (
4643+ & mut relay_reader,
4644+ & mut relay_writer,
4645+ already_forwarded,
4646+ None ,
4647+ )
4648+ . await
4649+ . expect ( "chunked body with trailers should relay" ) ;
4650+
4651+ let mut remaining = vec ! [ 0 ; pipelined_request. len( ) ] ;
4652+ relay_reader. read_exact ( & mut remaining) . await . unwrap ( ) ;
4653+ assert_eq ! ( remaining, pipelined_request) ;
4654+
4655+ drop ( relay_writer) ;
4656+ let mut forwarded = Vec :: new ( ) ;
4657+ upstream_reader. read_to_end ( & mut forwarded) . await . unwrap ( ) ;
4658+ let mut expected = already_forwarded. to_vec ( ) ;
4659+ expected. extend_from_slice ( body_remainder) ;
4660+ assert_eq ! ( forwarded, expected) ;
4661+ }
4662+
45564663 #[ test]
45574664 fn parse_no_body ( ) {
45584665 let headers = "GET /api HTTP/1.1\r \n Host: example.com\r \n \r \n " ;
0 commit comments