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