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