1use std::{
4 future::Future,
5 io::{self, Write as _},
6 pin::Pin,
7 task::{Context, Poll},
8};
9
10use bytes::Bytes;
11#[cfg(feature = "compress-gzip")]
12use flate2::write::{GzDecoder, ZlibDecoder};
13use futures_core::{ready, Stream};
14use pin_project_lite::pin_project;
15use tokio::task::{spawn_blocking, JoinHandle};
16#[cfg(feature = "compress-zstd")]
17use zstd::stream::write::Decoder as ZstdDecoder;
18
19use crate::{
20 encoding::Writer,
21 error::PayloadError,
22 header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
23};
24
25const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049;
26
27pin_project! {
28 pub struct Decoder<S> {
37 decoder: Option<ContentDecoder>,
38 #[pin]
39 stream: S,
40 eof: bool,
41 fut: Option<JoinHandle<io::Result<(Option<Bytes>, ContentDecoder)>>>,
42 }
43}
44
45impl<S> Decoder<S>
46where
47 S: Stream<Item = Result<Bytes, PayloadError>>,
48{
49 #[inline]
54 pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
55 #[cfg(feature = "compress-brotli")]
56 const DEFAULT_DECODER_CAPACITY: usize = 8 * 1024; let decoder = match encoding {
59 #[cfg(feature = "compress-brotli")]
60 ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new(
61 brotli::DecompressorWriter::new(Writer::new(), DEFAULT_DECODER_CAPACITY),
62 ))),
63
64 #[cfg(feature = "compress-gzip")]
65 ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(ZlibDecoder::new(
66 Writer::new(),
67 )))),
68
69 #[cfg(feature = "compress-gzip")]
70 ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
71 Writer::new(),
72 )))),
73
74 #[cfg(feature = "compress-zstd")]
75 ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
76 ZstdDecoder::new(Writer::new()).expect(
77 "Failed to create zstd decoder. This is a bug. \
78 Please report it to the actix-web repository.",
79 ),
80 ))),
81
82 _ => None,
83 };
84
85 Decoder {
86 decoder,
87 stream,
88 fut: None,
89 eof: false,
90 }
91 }
92
93 pub fn limit(mut self, limit: usize) -> Self {
101 if let Some(ref mut decoder) = self.decoder {
102 decoder.set_limit(limit);
103 }
104
105 self
106 }
107
108 #[inline]
117 pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder<S> {
118 let encoding = headers
120 .get(&CONTENT_ENCODING)
121 .and_then(|val| val.to_str().ok())
122 .and_then(|x| x.parse().ok())
123 .unwrap_or(ContentEncoding::Identity);
124
125 Self::new(stream, encoding)
126 }
127}
128
129impl<S> Stream for Decoder<S>
130where
131 S: Stream<Item = Result<Bytes, PayloadError>>,
132{
133 type Item = Result<Bytes, PayloadError>;
134
135 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
136 let mut this = self.project();
137
138 loop {
139 if let Some(ref mut fut) = this.fut {
141 let result = ready!(Pin::new(fut).poll(cx))
142 .map_err(|_| {
143 PayloadError::Io(io::Error::other(
144 "Blocking task was cancelled unexpectedly",
145 ))
146 })
147 .and_then(|result| result.map_err(from_decoder_error));
148
149 this.fut.take();
151
152 let (chunk, decoder) = match result {
153 Ok(result) => result,
154 Err(err) => {
155 *this.eof = true;
156 return Poll::Ready(Some(Err(err)));
157 }
158 };
159
160 *this.decoder = Some(decoder);
161
162 if let Some(chunk) = chunk {
163 return Poll::Ready(Some(Ok(chunk)));
164 }
165 }
166
167 if *this.eof {
168 return Poll::Ready(None);
169 }
170
171 match ready!(this.stream.as_mut().poll_next(cx)) {
172 Some(Err(err)) => return Poll::Ready(Some(Err(err))),
173
174 Some(Ok(chunk)) => {
175 if let Some(mut decoder) = this.decoder.take() {
176 if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE {
177 let chunk = match decoder.feed_data(chunk) {
178 Ok(chunk) => chunk,
179 Err(err) => {
180 *this.eof = true;
181 return Poll::Ready(Some(Err(from_decoder_error(err))));
182 }
183 };
184
185 *this.decoder = Some(decoder);
186
187 if let Some(chunk) = chunk {
188 return Poll::Ready(Some(Ok(chunk)));
189 }
190 } else {
191 *this.fut = Some(spawn_blocking(move || {
192 let chunk = decoder.feed_data(chunk)?;
193 Ok((chunk, decoder))
194 }));
195 }
196
197 continue;
198 } else {
199 return Poll::Ready(Some(Ok(chunk)));
200 }
201 }
202
203 None => {
204 *this.eof = true;
205
206 return if let Some(mut decoder) = this.decoder.take() {
207 match decoder.feed_eof() {
208 Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
209 Ok(None) => Poll::Ready(None),
210 Err(err) => Poll::Ready(Some(Err(from_decoder_error(err)))),
211 }
212 } else {
213 Poll::Ready(None)
214 };
215 }
216 }
217 }
218 }
219}
220
221fn from_decoder_error(err: io::Error) -> PayloadError {
222 match err.kind() {
223 io::ErrorKind::FileTooLarge => PayloadError::Overflow,
224 _ => err.into(),
225 }
226}
227
228enum ContentDecoder {
229 #[cfg(feature = "compress-gzip")]
230 Deflate(Box<ZlibDecoder<Writer>>),
231
232 #[cfg(feature = "compress-gzip")]
233 Gzip(Box<GzDecoder<Writer>>),
234
235 #[cfg(feature = "compress-brotli")]
236 Brotli(Box<brotli::DecompressorWriter<Writer>>),
237
238 #[cfg(feature = "compress-zstd")]
242 Zstd(Box<ZstdDecoder<'static, Writer>>),
243}
244
245impl ContentDecoder {
246 fn set_limit(&mut self, limit: usize) {
247 match self {
248 #[cfg(feature = "compress-brotli")]
249 ContentDecoder::Brotli(ref mut decoder) => decoder.get_mut().set_limit(limit),
250
251 #[cfg(feature = "compress-gzip")]
252 ContentDecoder::Gzip(ref mut decoder) => decoder.get_mut().set_limit(limit),
253
254 #[cfg(feature = "compress-gzip")]
255 ContentDecoder::Deflate(ref mut decoder) => decoder.get_mut().set_limit(limit),
256
257 #[cfg(feature = "compress-zstd")]
258 ContentDecoder::Zstd(ref mut decoder) => decoder.get_mut().set_limit(limit),
259 }
260 }
261
262 fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
263 match self {
264 #[cfg(feature = "compress-brotli")]
265 ContentDecoder::Brotli(ref mut decoder) => match decoder.flush() {
266 Ok(()) => {
267 let b = decoder.get_mut().take();
268
269 if !b.is_empty() {
270 Ok(Some(b))
271 } else {
272 Ok(None)
273 }
274 }
275 Err(err) => Err(err),
276 },
277
278 #[cfg(feature = "compress-gzip")]
279 ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() {
280 Ok(_) => {
281 let b = decoder.get_mut().take();
282
283 if !b.is_empty() {
284 Ok(Some(b))
285 } else {
286 Ok(None)
287 }
288 }
289 Err(err) => Err(err),
290 },
291
292 #[cfg(feature = "compress-gzip")]
293 ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() {
294 Ok(_) => {
295 let b = decoder.get_mut().take();
296 if !b.is_empty() {
297 Ok(Some(b))
298 } else {
299 Ok(None)
300 }
301 }
302 Err(err) => Err(err),
303 },
304
305 #[cfg(feature = "compress-zstd")]
306 ContentDecoder::Zstd(ref mut decoder) => match decoder.flush() {
307 Ok(_) => {
308 let b = decoder.get_mut().take();
309 if !b.is_empty() {
310 Ok(Some(b))
311 } else {
312 Ok(None)
313 }
314 }
315 Err(err) => Err(err),
316 },
317 }
318 }
319
320 fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
321 match self {
322 #[cfg(feature = "compress-brotli")]
323 ContentDecoder::Brotli(ref mut decoder) => match decoder.write_all(&data) {
324 Ok(_) => {
325 decoder.flush()?;
326 let b = decoder.get_mut().take();
327
328 if !b.is_empty() {
329 Ok(Some(b))
330 } else {
331 Ok(None)
332 }
333 }
334 Err(err) => Err(err),
335 },
336
337 #[cfg(feature = "compress-gzip")]
338 ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
339 Ok(_) => {
340 decoder.flush()?;
341 let b = decoder.get_mut().take();
342
343 if !b.is_empty() {
344 Ok(Some(b))
345 } else {
346 Ok(None)
347 }
348 }
349 Err(err) => Err(err),
350 },
351
352 #[cfg(feature = "compress-gzip")]
353 ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
354 Ok(_) => {
355 decoder.flush()?;
356
357 let b = decoder.get_mut().take();
358 if !b.is_empty() {
359 Ok(Some(b))
360 } else {
361 Ok(None)
362 }
363 }
364 Err(err) => Err(err),
365 },
366
367 #[cfg(feature = "compress-zstd")]
368 ContentDecoder::Zstd(ref mut decoder) => match decoder.write_all(&data) {
369 Ok(_) => {
370 decoder.flush()?;
371
372 let b = decoder.get_mut().take();
373 if !b.is_empty() {
374 Ok(Some(b))
375 } else {
376 Ok(None)
377 }
378 }
379 Err(err) => Err(err),
380 },
381 }
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use futures_util::{stream, StreamExt as _};
388 use rand::RngExt as _;
389
390 use super::*;
391
392 const ENCODINGS: &[ContentEncoding] = &[
393 #[cfg(feature = "compress-brotli")]
394 ContentEncoding::Brotli,
395 #[cfg(feature = "compress-gzip")]
396 ContentEncoding::Gzip,
397 #[cfg(feature = "compress-gzip")]
398 ContentEncoding::Deflate,
399 #[cfg(feature = "compress-zstd")]
400 ContentEncoding::Zstd,
401 ];
402
403 fn encode(data: &[u8], encoding: ContentEncoding) -> Bytes {
404 match encoding {
405 #[cfg(feature = "compress-brotli")]
406 ContentEncoding::Brotli => {
407 let mut encoder = brotli::CompressorWriter::new(Vec::new(), 4096, 3, 22);
408 encoder.write_all(data).unwrap();
409 encoder.into_inner().into()
410 }
411
412 #[cfg(feature = "compress-gzip")]
413 ContentEncoding::Gzip => {
414 let mut encoder =
415 flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
416 encoder.write_all(data).unwrap();
417 encoder.finish().unwrap().into()
418 }
419
420 #[cfg(feature = "compress-gzip")]
421 ContentEncoding::Deflate => {
422 let mut encoder =
423 flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::fast());
424 encoder.write_all(data).unwrap();
425 encoder.finish().unwrap().into()
426 }
427
428 #[cfg(feature = "compress-zstd")]
429 ContentEncoding::Zstd => zstd::stream::encode_all(data, 3).unwrap().into(),
430
431 ContentEncoding::Identity => Bytes::copy_from_slice(data),
432 }
433 }
434
435 #[actix_rt::test]
436 async fn decoded_output_is_bounded() {
437 let data = vec![b'a'; 1024 * 1024];
438
439 for &encoding in ENCODINGS {
440 let compressed = encode(&data, encoding);
441 let stream = stream::iter([Ok(compressed)]);
442 let mut decoder = Decoder::new(stream, encoding).limit(1024);
443
444 assert!(
445 matches!(decoder.next().await, Some(Err(PayloadError::Overflow))),
446 "{encoding:?} should report overflow"
447 );
448 assert!(
449 decoder.next().await.is_none(),
450 "{encoding:?} should end after overflow"
451 );
452 }
453 }
454
455 #[actix_rt::test]
456 async fn blocking_decode_reports_overflow() {
457 let data_that_compresses_poorly = rand::rng()
458 .sample_iter(rand::distr::StandardUniform)
459 .take(65_536)
460 .collect::<Vec<u8>>();
461
462 for &encoding in ENCODINGS {
463 let compressed = encode(&data_that_compresses_poorly, encoding);
464
465 assert!(
466 compressed.len() >= MAX_CHUNK_SIZE_DECODE_IN_PLACE,
467 "{encoding:?} should use the blocking decode path"
468 );
469
470 let stream = stream::iter([Ok(compressed)]);
471 let mut decoder = Decoder::new(stream, encoding).limit(1024);
472
473 assert!(
474 matches!(decoder.next().await, Some(Err(PayloadError::Overflow))),
475 "{encoding:?} should report overflow"
476 );
477 assert!(
478 decoder.next().await.is_none(),
479 "{encoding:?} should end after overflow"
480 );
481 }
482 }
483
484 #[actix_rt::test]
485 async fn decoded_output_at_limit_is_complete() {
486 let data = vec![b'a'; 1024];
487
488 for &encoding in ENCODINGS {
489 let compressed = encode(&data, encoding);
490 let stream = stream::iter(
491 compressed
492 .chunks(8)
493 .map(|chunk| Ok(Bytes::copy_from_slice(chunk)))
494 .collect::<Vec<_>>(),
495 );
496
497 let mut decoder = Decoder::new(stream, encoding).limit(data.len());
498 let mut output = Vec::new();
499
500 while let Some(chunk) = decoder.next().await {
501 output.extend_from_slice(&chunk.unwrap());
502 }
503
504 assert_eq!(
505 output, data,
506 "{encoding:?} should decode the complete body at the limit"
507 );
508 }
509 }
510
511 #[actix_rt::test]
512 async fn decoded_limit_applies_across_chunks() {
513 let data = vec![b'a'; 1024 * 1024];
514
515 for &encoding in ENCODINGS {
516 let compressed = encode(&data, encoding);
517 let chunks = compressed
518 .chunks(16)
519 .map(|chunk| Ok(Bytes::copy_from_slice(chunk)))
520 .collect::<Vec<_>>();
521
522 let mut decoder = Decoder::new(stream::iter(chunks), encoding).limit(200_000);
523 let mut size = 0;
524
525 loop {
526 match decoder.next().await {
527 Some(Ok(chunk)) => {
528 size += chunk.len();
529 assert!(
530 size <= 200_000,
531 "{encoding:?} should keep total decoded output within the limit"
532 );
533 }
534 Some(Err(PayloadError::Overflow)) => break,
535 result => panic!("{encoding:?} should report overflow, got {result:?}"),
536 }
537 }
538
539 assert!(
540 decoder.next().await.is_none(),
541 "{encoding:?} should end after overflow"
542 );
543 }
544 }
545
546 #[actix_rt::test]
547 async fn zero_decoded_limit_rejects_nonempty_body() {
548 for &encoding in ENCODINGS {
549 let compressed = encode(b"a", encoding);
550 let mut decoder = Decoder::new(stream::iter([Ok(compressed)]), encoding).limit(0);
551 assert!(
552 matches!(decoder.next().await, Some(Err(PayloadError::Overflow))),
553 "{encoding:?} should report overflow"
554 );
555 }
556 }
557
558 #[test]
559 fn io_errors_are_not_classified_as_overflow() {
560 assert!(matches!(
561 from_decoder_error(io::Error::other("codec error")),
562 PayloadError::Incomplete(Some(_))
563 ));
564 }
565}