1use crate::types::Http1Error;
21
22const MAX_TRAILER_BUF_SIZE: usize = 8192;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum ChunkedState {
28 SizeLine,
30 ChunkData,
32 ChunkDataEnd,
34 Trailer,
36 Done,
38}
39
40#[derive(Debug, Clone)]
42pub struct ChunkedDecoder {
43 pub state: ChunkedState,
45 pub remaining: u64,
47 pub current_chunk_size: u64,
49 pub total_decoded: u64,
51 pub max_total: u64,
53 pub saw_last: bool,
55 size_buf: [u8; 64],
57 size_len: usize,
58 trailer_buf: Vec<u8>,
61}
62
63impl ChunkedDecoder {
64 #[inline]
66 pub fn new(max_total: u64) -> Self {
67 Self {
68 state: ChunkedState::SizeLine,
69 remaining: 0,
70 current_chunk_size: 0,
71 total_decoded: 0,
72 max_total,
73 saw_last: false,
74 size_buf: [0u8; 64],
75 size_len: 0,
76 trailer_buf: Vec::new(),
77 }
78 }
79
80 #[inline]
82 pub fn reset(&mut self) {
83 self.state = ChunkedState::SizeLine;
84 self.remaining = 0;
85 self.current_chunk_size = 0;
86 self.total_decoded = 0;
87 self.saw_last = false;
88 self.size_len = 0;
89 self.trailer_buf.clear();
90 }
91
92 #[inline]
94 pub fn is_done(&self) -> bool {
95 matches!(self.state, ChunkedState::Done)
96 }
97
98 pub fn feed(&mut self, input: &[u8]) -> Result<(Vec<u8>, usize), Http1Error> {
107 let mut output = Vec::with_capacity(input.len());
108
109 if self.size_len > 0 {
111 let buffered = self.size_len;
112 let combined_len = buffered.checked_add(input.len()).ok_or_else(|| {
114 Http1Error::ChunkedError("buffered input length overflow".into())
115 })?;
116 let mut combined = Vec::with_capacity(combined_len);
117 combined.extend_from_slice(&self.size_buf[..buffered]);
118 combined.extend_from_slice(input);
119 self.size_len = 0;
120 let consumed = self.feed_combined(&combined, &mut output)?;
121 let own_consumed = consumed.saturating_sub(buffered);
123 return Ok((output, own_consumed));
124 }
125
126 if !self.trailer_buf.is_empty() {
128 let buffered = self.trailer_buf.len();
129 let combined_len = buffered.checked_add(input.len()).ok_or_else(|| {
130 Http1Error::ChunkedError("buffered trailer input length overflow".into())
131 })?;
132 let mut combined = Vec::with_capacity(combined_len);
133 combined.extend_from_slice(&self.trailer_buf);
134 combined.extend_from_slice(input);
135 self.trailer_buf.clear();
136 let consumed = self.feed_combined(&combined, &mut output)?;
137 let own_consumed = consumed.saturating_sub(buffered);
139 return Ok((output, own_consumed));
140 }
141
142 let consumed = self.feed_combined(input, &mut output)?;
143 Ok((output, consumed))
144 }
145
146 fn feed_combined(
148 &mut self,
149 input: &[u8],
150 output: &mut Vec<u8>,
151 ) -> Result<usize, Http1Error> {
152 let mut consumed = 0;
153
154 while consumed < input.len() && !matches!(self.state, ChunkedState::Done) {
155 match self.state {
156 ChunkedState::SizeLine => {
157 let rest = &input[consumed..];
158 if let Some(crlf) = find_crlf(rest) {
159 let line = &rest[..crlf];
160 self.parse_size_line(line)?;
161 consumed += crlf + 2;
162 } else {
163 let available = rest.len().min(self.size_buf.len() - self.size_len);
164 if available > 0 {
165 self.size_buf[self.size_len..self.size_len + available]
166 .copy_from_slice(&rest[..available]);
167 self.size_len += available;
168 consumed += available;
169 }
170 if self.size_len >= self.size_buf.len() {
171 return Err(Http1Error::ChunkedError(
172 "chunk-size line too long".into(),
173 ));
174 }
175 break;
176 }
177 }
178 ChunkedState::ChunkData => {
179 let take = (input.len() - consumed) as u64;
180 let take = take.min(self.remaining);
181 if take == 0 {
182 break;
184 }
185 output.extend_from_slice(
186 &input[consumed..consumed + take as usize],
187 );
188 consumed += take as usize;
189 self.remaining -= take;
190 self.total_decoded = self.total_decoded.checked_add(take).ok_or(Http1Error::BodyTooLarge)?;
192 if self.total_decoded > self.max_total {
193 return Err(Http1Error::BodyTooLarge);
194 }
195 if self.remaining == 0 {
196 self.state = ChunkedState::ChunkDataEnd;
197 }
198 }
199 ChunkedState::ChunkDataEnd => {
200 let rest = &input[consumed..];
201 if rest.len() < 2 {
202 break;
203 }
204 if rest[0] == b'\r' && rest[1] == b'\n' {
205 consumed += 2;
206 self.state = ChunkedState::SizeLine;
207 } else {
208 return Err(Http1Error::ChunkedError(
209 "missing CRLF after chunk-data".into(),
210 ));
211 }
212 }
213 ChunkedState::Trailer => {
214 let rest = &input[consumed..];
215 if rest.is_empty() {
217 break;
218 }
219 if let Some(crlf) = find_crlf(rest) {
221 consumed += crlf + 2;
222 if crlf == 0 {
223 self.state = ChunkedState::Done;
224 }
225 } else {
226 self.trailer_buf.extend_from_slice(rest);
230 if self.trailer_buf.len() > MAX_TRAILER_BUF_SIZE {
232 return Err(Http1Error::ChunkedError(
233 "trailer buffer exceeds maximum size".into(),
234 ));
235 }
236 consumed += rest.len();
237 break;
238 }
239 }
240 ChunkedState::Done => {
242 return Err(Http1Error::ChunkedError(
243 "chunked decoder in terminal state".into(),
244 ));
245 }
246 }
247 }
248
249 Ok(consumed)
253 }
254
255 fn parse_size_line(&mut self, line: &[u8]) -> Result<(), Http1Error> {
261 let parsed = crate::parser::parse_chunk_size(line)?;
262 let val = u64::try_from(parsed).map_err(|_| Http1Error::BodyTooLarge)?;
264
265 self.current_chunk_size = val;
266 if val == 0 {
267 self.saw_last = true;
268 self.state = ChunkedState::Trailer;
271 } else {
272 if val > self.max_total {
273 return Err(Http1Error::BodyTooLarge);
274 }
275 if self
277 .total_decoded
278 .checked_add(val)
279 .map(|sum| sum > self.max_total)
280 .unwrap_or(true)
281 {
282 return Err(Http1Error::BodyTooLarge);
283 }
284 self.remaining = val;
285 self.state = ChunkedState::ChunkData;
286 }
287 Ok(())
288 }
289}
290
291#[inline]
293fn find_crlf(buf: &[u8]) -> Option<usize> {
294 let mut i = 0;
295 while i + 1 < buf.len() {
296 if buf[i] == b'\r' && buf[i + 1] == b'\n' {
297 return Some(i);
298 }
299 i += 1;
300 }
301 None
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[test]
309 fn test_simple_chunked() {
310 let mut dec = ChunkedDecoder::new(1024);
311 let input = b"5\r\nhello\r\n0\r\n\r\n";
312 let (out, consumed) = dec.feed(input).unwrap();
313 assert_eq!(out, b"hello");
314 assert_eq!(consumed, input.len());
315 assert!(dec.is_done());
316 }
317
318 #[test]
319 fn test_multiple_chunks() {
320 let mut dec = ChunkedDecoder::new(1024);
321 let input = b"5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n";
322 let (out, consumed) = dec.feed(input).unwrap();
323 assert_eq!(out, b"hello world");
324 assert_eq!(consumed, input.len());
325 assert!(dec.is_done());
326 }
327
328 #[test]
329 fn test_incremental() {
330 let mut dec = ChunkedDecoder::new(1024);
331 let input = b"5\r\nhello\r\n0\r\n\r\n";
332 let (out1, c1) = dec.feed(&input[..6]).unwrap();
333 assert_eq!(c1, 6, "first feed should consume 6 bytes");
334 let (out2, _c2) = dec.feed(&input[c1..]).unwrap();
335 let mut all = out1;
336 all.extend_from_slice(&out2);
337 assert_eq!(all, b"hello");
338 assert!(dec.is_done());
339 }
340
341 #[test]
342 fn test_chunk_extension() {
343 let mut dec = ChunkedDecoder::new(1024);
344 let input = b"5;foo=bar\r\nhello\r\n0\r\n\r\n";
345 let (out, _) = dec.feed(input).unwrap();
346 assert_eq!(out, b"hello");
347 assert!(dec.is_done());
348 }
349
350 #[test]
351 fn test_invalid_hex() {
352 let mut dec = ChunkedDecoder::new(1024);
353 let input = b"ZZ\r\nx\r\n0\r\n\r\n";
354 let r = dec.feed(input);
355 assert!(r.is_err());
356 }
357
358 #[test]
359 fn test_too_large() {
360 let mut dec = ChunkedDecoder::new(2);
361 let input = b"5\r\nhello\r\n0\r\n\r\n";
362 let r = dec.feed(input);
363 assert!(r.is_err());
364 }
365
366 #[test]
367 fn test_missing_crlf() {
368 let mut dec = ChunkedDecoder::new(1024);
369 let input = b"5\r\nhello\rBAD";
370 let r = dec.feed(input);
371 assert!(r.is_err());
372 }
373
374 #[test]
375 fn test_empty_chunk_zero_size() {
376 let mut dec = ChunkedDecoder::new(1024);
377 let input = b"0\r\n\r\n";
378 let (out, consumed) = dec.feed(input).unwrap();
379 assert!(out.is_empty());
380 assert_eq!(consumed, input.len());
381 assert!(dec.is_done());
382 assert!(dec.saw_last);
383 }
384
385 #[test]
386 fn test_single_byte_chunk() {
387 let mut dec = ChunkedDecoder::new(1024);
388 let input = b"1\r\nA\r\n0\r\n\r\n";
389 let (out, _) = dec.feed(input).unwrap();
390 assert_eq!(out, b"A");
391 assert!(dec.is_done());
392 }
393
394 #[test]
395 fn test_large_hex_chunk_size() {
396 let mut dec = ChunkedDecoder::new(1024 * 1024);
397 let size = 0xFF;
398 let mut input = Vec::new();
399 input.extend_from_slice(format!("{:X}\r\n", size).as_bytes());
400 input.extend_from_slice(&vec![b'x'; size]);
401 input.extend_from_slice(b"\r\n0\r\n\r\n");
402 let (out, _) = dec.feed(&input).unwrap();
403 assert_eq!(out.len(), size);
404 assert!(dec.is_done());
405 }
406
407 #[test]
408 fn test_chunk_size_with_trailing_spaces() {
409 let mut dec = ChunkedDecoder::new(1024);
410 let input = b"5 \r\nhello\r\n0\r\n\r\n";
411 let (out, _) = dec.feed(input).unwrap();
412 assert_eq!(out, b"hello");
413 assert!(dec.is_done());
414 }
415
416 #[test]
417 fn test_chunk_size_with_tabs() {
418 let mut dec = ChunkedDecoder::new(1024);
419 let input = b"5\t\r\nhello\r\n0\r\n\r\n";
420 let (out, _) = dec.feed(input).unwrap();
421 assert_eq!(out, b"hello");
422 assert!(dec.is_done());
423 }
424
425 #[test]
426 fn test_empty_chunk_size_line() {
427 let mut dec = ChunkedDecoder::new(1024);
428 let input = b"\r\nhello\r\n0\r\n\r\n";
429 let r = dec.feed(input);
430 assert!(r.is_err());
431 }
432
433 #[test]
434 fn test_chunk_size_line_too_long() {
435 let mut dec = ChunkedDecoder::new(1024);
436 let mut long_line = vec![b'A'; 100];
437 long_line.extend_from_slice(b"\r\n");
438 let r = dec.feed(&long_line);
439 assert!(r.is_err());
440 }
441
442 #[test]
443 fn test_trailer_state_received() {
444 let mut dec = ChunkedDecoder::new(1024);
445 let input = b"5\r\nhello\r\n0\r\n\r\n";
446 let (out, _) = dec.feed(input).unwrap();
447 assert_eq!(out, b"hello");
448 assert!(dec.is_done());
449 }
450
451 #[test]
452 fn test_trailer_empty_immediate_done() {
453 let mut dec = ChunkedDecoder::new(1024);
454 let input = b"5\r\nhello\r\n0\r\n";
455 dec.feed(input).unwrap();
456 let _ = dec.feed(b"\r\n");
457 assert!(dec.is_done());
458 }
459
460 #[test]
461 fn test_reset_decoder() {
462 let mut dec = ChunkedDecoder::new(1024);
463 let input = b"5\r\nhello\r\n0\r\n\r\n";
464 dec.feed(input).unwrap();
465 assert!(dec.is_done());
466 dec.reset();
467 assert_eq!(dec.state, ChunkedState::SizeLine);
468 assert_eq!(dec.total_decoded, 0);
469 assert!(!dec.saw_last);
470 assert_eq!(dec.current_chunk_size, 0);
471 }
472
473 #[test]
474 fn test_chunked_state_variants() {
475 let states = [
476 ChunkedState::SizeLine,
477 ChunkedState::ChunkData,
478 ChunkedState::ChunkDataEnd,
479 ChunkedState::Trailer,
480 ChunkedState::Done,
481 ];
482 for (i, s) in states.iter().enumerate() {
483 assert_eq!(*s, states[i]);
484 }
485 assert_ne!(ChunkedState::SizeLine, ChunkedState::Done);
486 }
487
488 #[test]
489 fn test_decoder_clone() {
490 let dec = ChunkedDecoder::new(1024);
491 let dec2 = dec.clone();
492 assert_eq!(dec.state, dec2.state);
493 assert_eq!(dec.max_total, dec2.max_total);
494 }
495
496 #[test]
497 fn test_total_decoded_tracking() {
498 let mut dec = ChunkedDecoder::new(1024);
499 let input = b"5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n";
500 let (_, _) = dec.feed(input).unwrap();
501 assert_eq!(dec.total_decoded, 11);
502 }
503
504 #[test]
505 fn test_uppercase_hex() {
506 let mut dec = ChunkedDecoder::new(1024);
507 let input = b"A\r\n0123456789\r\n0\r\n\r\n";
508 let (out, _) = dec.feed(input).unwrap();
509 assert_eq!(out.len(), 10);
510 assert!(dec.is_done());
511 }
512
513 #[test]
514 fn test_lowercase_hex() {
515 let mut dec = ChunkedDecoder::new(1024);
516 let input = b"a\r\n0123456789\r\n0\r\n\r\n";
517 let (out, _) = dec.feed(input).unwrap();
518 assert_eq!(out.len(), 10);
519 assert!(dec.is_done());
520 }
521
522 #[test]
523 fn test_negative_chunk_size_rejected() {
524 let mut dec = ChunkedDecoder::new(1024);
525 let input = b"-5\r\nhello\r\n0\r\n\r\n";
526 let r = dec.feed(input);
527 assert!(r.is_err());
528 }
529
530 #[test]
531 fn test_body_too_large_accumulated() {
532 let mut dec = ChunkedDecoder::new(10);
533 let input = b"6\r\nhello \r\n6\r\nworld!\r\n0\r\n\r\n";
534 let r = dec.feed(input);
535 assert!(r.is_err());
536 }
537
538 #[test]
539 fn test_total_decoded_overflow_fail_closed() {
540 let mut dec = ChunkedDecoder::new(u64::MAX);
543 let input = b"5\r\nhello\r\nFFFFFFFFFFFFFFFC\r\nx\r\n0\r\n\r\n";
545 let r = dec.feed(input);
546 assert!(
547 matches!(r, Err(Http1Error::BodyTooLarge)),
548 "total_decoded 溢出必须返回 BodyTooLarge,实际 {r:?}"
549 );
550 }
551
552 #[test]
553 fn test_incremental_small_chunks() {
554 let mut dec = ChunkedDecoder::new(1024);
555 let input = b"5\r\nhello\r\n0\r\n\r\n";
556 let mut output = Vec::new();
557 let mut pos = 0;
558 while pos < input.len() {
559 let end = (pos + 2).min(input.len());
560 let (out, consumed) = dec.feed(&input[pos..end]).unwrap();
561 output.extend_from_slice(&out);
562 pos += consumed;
563 if consumed == 0 {
564 pos += 1;
565 }
566 }
567 assert_eq!(output, b"hello");
568 assert!(dec.is_done());
569 }
570}