@@ -12,6 +12,11 @@ use super::DecodedLength;
12
12
13
13
use self :: Kind :: { Chunked , Eof , Length } ;
14
14
15
+ /// Maximum amount of bytes allowed in chunked extensions.
16
+ ///
17
+ /// This limit is currentlty applied for the entire body, not per chunk.
18
+ const CHUNKED_EXTENSIONS_LIMIT : u64 = 1024 * 16 ;
19
+
15
20
/// Decoders to handle different Transfer-Encodings.
16
21
///
17
22
/// If a message body does not include a Transfer-Encoding, it *should*
@@ -26,7 +31,11 @@ enum Kind {
26
31
/// A Reader used when a Content-Length header is passed with a positive integer.
27
32
Length ( u64 ) ,
28
33
/// A Reader used when Transfer-Encoding is `chunked`.
29
- Chunked ( ChunkedState , u64 ) ,
34
+ Chunked {
35
+ state : ChunkedState ,
36
+ chunk_len : u64 ,
37
+ extensions_cnt : u64 ,
38
+ } ,
30
39
/// A Reader used for responses that don't indicate a length or chunked.
31
40
///
32
41
/// The bool tracks when EOF is seen on the transport.
@@ -74,7 +83,11 @@ impl Decoder {
74
83
75
84
pub ( crate ) fn chunked ( ) -> Decoder {
76
85
Decoder {
77
- kind : Kind :: Chunked ( ChunkedState :: new ( ) , 0 ) ,
86
+ kind : Kind :: Chunked {
87
+ state : ChunkedState :: new ( ) ,
88
+ chunk_len : 0 ,
89
+ extensions_cnt : 0 ,
90
+ } ,
78
91
}
79
92
}
80
93
@@ -97,7 +110,12 @@ impl Decoder {
97
110
pub ( crate ) fn is_eof ( & self ) -> bool {
98
111
matches ! (
99
112
self . kind,
100
- Length ( 0 ) | Chunked ( ChunkedState :: End , _) | Eof ( true )
113
+ Length ( 0 )
114
+ | Chunked {
115
+ state: ChunkedState :: End ,
116
+ ..
117
+ }
118
+ | Eof ( true )
101
119
)
102
120
}
103
121
@@ -128,11 +146,15 @@ impl Decoder {
128
146
Poll :: Ready ( Ok ( buf) )
129
147
}
130
148
}
131
- Chunked ( ref mut state, ref mut size) => {
149
+ Chunked {
150
+ ref mut state,
151
+ ref mut chunk_len,
152
+ ref mut extensions_cnt,
153
+ } => {
132
154
loop {
133
155
let mut buf = None ;
134
156
// advances the chunked state
135
- * state = ready ! ( state. step( cx, body, size , & mut buf) ) ?;
157
+ * state = ready ! ( state. step( cx, body, chunk_len , extensions_cnt , & mut buf) ) ?;
136
158
if * state == ChunkedState :: End {
137
159
trace ! ( "end of chunked" ) ;
138
160
return Poll :: Ready ( Ok ( Bytes :: new ( ) ) ) ;
@@ -203,14 +225,15 @@ impl ChunkedState {
203
225
cx : & mut Context < ' _ > ,
204
226
body : & mut R ,
205
227
size : & mut u64 ,
228
+ extensions_cnt : & mut u64 ,
206
229
buf : & mut Option < Bytes > ,
207
230
) -> Poll < Result < ChunkedState , io:: Error > > {
208
231
use self :: ChunkedState :: * ;
209
232
match * self {
210
233
Start => ChunkedState :: read_start ( cx, body, size) ,
211
234
Size => ChunkedState :: read_size ( cx, body, size) ,
212
235
SizeLws => ChunkedState :: read_size_lws ( cx, body) ,
213
- Extension => ChunkedState :: read_extension ( cx, body) ,
236
+ Extension => ChunkedState :: read_extension ( cx, body, extensions_cnt ) ,
214
237
SizeLf => ChunkedState :: read_size_lf ( cx, body, * size) ,
215
238
Body => ChunkedState :: read_body ( cx, body, size, buf) ,
216
239
BodyCr => ChunkedState :: read_body_cr ( cx, body) ,
@@ -307,6 +330,7 @@ impl ChunkedState {
307
330
fn read_extension < R : MemRead > (
308
331
cx : & mut Context < ' _ > ,
309
332
rdr : & mut R ,
333
+ extensions_cnt : & mut u64 ,
310
334
) -> Poll < Result < ChunkedState , io:: Error > > {
311
335
trace ! ( "read_extension" ) ;
312
336
// We don't care about extensions really at all. Just ignore them.
@@ -321,7 +345,17 @@ impl ChunkedState {
321
345
io:: ErrorKind :: InvalidData ,
322
346
"invalid chunk extension contains newline" ,
323
347
) ) ) ,
324
- _ => Poll :: Ready ( Ok ( ChunkedState :: Extension ) ) , // no supported extensions
348
+ _ => {
349
+ * extensions_cnt += 1 ;
350
+ if * extensions_cnt >= CHUNKED_EXTENSIONS_LIMIT {
351
+ Poll :: Ready ( Err ( io:: Error :: new (
352
+ io:: ErrorKind :: InvalidData ,
353
+ "chunk extensions over limit" ,
354
+ ) ) )
355
+ } else {
356
+ Poll :: Ready ( Ok ( ChunkedState :: Extension ) )
357
+ }
358
+ } // no supported extensions
325
359
}
326
360
}
327
361
fn read_size_lf < R : MemRead > (
@@ -492,7 +526,6 @@ mod tests {
492
526
}
493
527
}
494
528
495
- #[ cfg( feature = "nightly" ) ]
496
529
impl MemRead for Bytes {
497
530
fn read_mem ( & mut self , _: & mut Context < ' _ > , len : usize ) -> Poll < io:: Result < Bytes > > {
498
531
let n = std:: cmp:: min ( len, self . len ( ) ) ;
@@ -519,10 +552,12 @@ mod tests {
519
552
let mut state = ChunkedState :: new ( ) ;
520
553
let rdr = & mut s. as_bytes ( ) ;
521
554
let mut size = 0 ;
555
+ let mut ext_cnt = 0 ;
522
556
loop {
523
- let result =
524
- futures_util:: future:: poll_fn ( |cx| state. step ( cx, rdr, & mut size, & mut None ) )
525
- . await ;
557
+ let result = futures_util:: future:: poll_fn ( |cx| {
558
+ state. step ( cx, rdr, & mut size, & mut ext_cnt, & mut None )
559
+ } )
560
+ . await ;
526
561
let desc = format ! ( "read_size failed for {:?}" , s) ;
527
562
state = result. expect ( desc. as_str ( ) ) ;
528
563
if state == ChunkedState :: Body || state == ChunkedState :: EndCr {
@@ -536,10 +571,12 @@ mod tests {
536
571
let mut state = ChunkedState :: new ( ) ;
537
572
let rdr = & mut s. as_bytes ( ) ;
538
573
let mut size = 0 ;
574
+ let mut ext_cnt = 0 ;
539
575
loop {
540
- let result =
541
- futures_util:: future:: poll_fn ( |cx| state. step ( cx, rdr, & mut size, & mut None ) )
542
- . await ;
576
+ let result = futures_util:: future:: poll_fn ( |cx| {
577
+ state. step ( cx, rdr, & mut size, & mut ext_cnt, & mut None )
578
+ } )
579
+ . await ;
543
580
state = match result {
544
581
Ok ( s) => s,
545
582
Err ( e) => {
@@ -629,6 +666,33 @@ mod tests {
629
666
assert_eq ! ( "1234567890abcdef" , & result) ;
630
667
}
631
668
669
+ #[ tokio:: test]
670
+ async fn test_read_chunked_extensions_over_limit ( ) {
671
+ // construct a chunked body where each individual chunked extension
672
+ // is totally fine, but combined is over the limit.
673
+ let per_chunk = super :: CHUNKED_EXTENSIONS_LIMIT * 2 / 3 ;
674
+ let mut scratch = vec ! [ ] ;
675
+ for _ in 0 ..2 {
676
+ scratch. extend ( b"1;" ) ;
677
+ scratch. extend ( b"x" . repeat ( per_chunk as usize ) ) ;
678
+ scratch. extend ( b"\r \n A\r \n " ) ;
679
+ }
680
+ scratch. extend ( b"0\r \n \r \n " ) ;
681
+ let mut mock_buf = Bytes :: from ( scratch) ;
682
+
683
+ let mut decoder = Decoder :: chunked ( ) ;
684
+ let buf1 = decoder. decode_fut ( & mut mock_buf) . await . expect ( "decode1" ) ;
685
+ assert_eq ! ( & buf1[ ..] , b"A" ) ;
686
+
687
+ let err = decoder
688
+ . decode_fut ( & mut mock_buf)
689
+ . await
690
+ . expect_err ( "decode2" ) ;
691
+ assert_eq ! ( err. kind( ) , io:: ErrorKind :: InvalidData ) ;
692
+ assert_eq ! ( err. to_string( ) , "chunk extensions over limit" ) ;
693
+ }
694
+
695
+ #[ cfg( not( miri) ) ]
632
696
#[ tokio:: test]
633
697
async fn test_read_chunked_trailer_with_missing_lf ( ) {
634
698
let mut mock_buf = & b"10\r \n 1234567890abcdef\r \n 0\r \n bad\r \r \n " [ ..] ;
0 commit comments