Skip to main content

actix_web/types/
payload.rs

1//! Basic binary and string payload extractors.
2
3use std::{
4    borrow::Cow,
5    future::Future,
6    pin::Pin,
7    str,
8    task::{Context, Poll},
9};
10
11use actix_http::error::PayloadError;
12#[expect(deprecated, reason = "preserve the public ready future type")]
13use actix_utils::future::{ready, Either, Ready};
14use bytes::{Bytes, BytesMut};
15use encoding_rs::{Encoding, UTF_8};
16use futures_core::{ready, stream::Stream};
17use mime::Mime;
18
19use crate::{
20    body, dev, error::ErrorBadRequest, http::header, web, Error, FromRequest, HttpMessage,
21    HttpRequest,
22};
23
24/// Extract a request's raw payload stream.
25///
26/// See [`PayloadConfig`] for important notes when using this advanced extractor.
27///
28/// # Examples
29/// ```
30/// use std::future::Future;
31/// use futures_util::StreamExt as _;
32/// use actix_web::{post, web};
33///
34/// // `body: web::Payload` parameter extracts raw payload stream from request
35/// #[post("/")]
36/// async fn index(mut body: web::Payload) -> actix_web::Result<String> {
37///     // for demonstration only; in a normal case use the `Bytes` extractor
38///     // collect payload stream into a bytes object
39///     let mut bytes = web::BytesMut::new();
40///     while let Some(item) = body.next().await {
41///         bytes.extend_from_slice(&item?);
42///     }
43///
44///     Ok(format!("Request Body Bytes:\n{:?}", bytes))
45/// }
46/// ```
47pub struct Payload(dev::Payload);
48
49impl Payload {
50    /// Unwrap to inner Payload type.
51    #[inline]
52    pub fn into_inner(self) -> dev::Payload {
53        self.0
54    }
55
56    /// Buffers payload from request up to `limit` bytes.
57    ///
58    /// This method is preferred over [`Payload::to_bytes()`] since it will not lead to unexpected
59    /// memory exhaustion from massive payloads. Note that the other primitive extractors such as
60    /// [`Bytes`] and [`String`], as well as extractors built on top of them, already have this sort
61    /// of protection according to the configured (or default) [`PayloadConfig`].
62    ///
63    /// # Errors
64    ///
65    /// - The outer error type, [`BodyLimitExceeded`](body::BodyLimitExceeded), is returned when the
66    ///   payload is larger than `limit`.
67    /// - The inner error type is [the normal Actix Web error](crate::Error) and is only returned if
68    ///   the payload stream yields an error for some reason. Such cases are usually caused by
69    ///   unrecoverable connection issues.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use actix_web::{error, web::Payload, Responder};
75    ///
76    /// async fn limited_payload_handler(pl: Payload) -> actix_web::Result<impl Responder> {
77    ///     match pl.to_bytes_limited(5).await {
78    ///         Ok(res) => res,
79    ///         Err(err) => Err(error::ErrorPayloadTooLarge(err)),
80    ///     }
81    /// }
82    /// ```
83    pub async fn to_bytes_limited(
84        self,
85        limit: usize,
86    ) -> Result<crate::Result<Bytes>, body::BodyLimitExceeded> {
87        let stream = body::BodyStream::new(self.0);
88
89        match body::to_bytes_limited(stream, limit).await {
90            Ok(Ok(body)) => Ok(Ok(body)),
91            Ok(Err(err)) => Ok(Err(err.into())),
92            Err(err) => Err(err),
93        }
94    }
95
96    /// Buffers entire payload from request.
97    ///
98    /// Use of this method is discouraged unless you know for certain that requests will not be
99    /// large enough to exhaust memory. If this is not known, prefer [`Payload::to_bytes_limited()`]
100    /// or one of the higher level extractors like [`Bytes`] or [`String`] that implement size
101    /// limits according to the configured (or default) [`PayloadConfig`].
102    ///
103    /// # Errors
104    ///
105    /// An error is only returned if the payload stream yields an error for some reason. Such cases
106    /// are usually caused by unrecoverable connection issues.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use actix_web::{error, web::Payload, Responder};
112    ///
113    /// async fn payload_handler(pl: Payload) -> actix_web::Result<impl Responder> {
114    ///     pl.to_bytes().await
115    /// }
116    /// ```
117    pub async fn to_bytes(self) -> crate::Result<Bytes> {
118        let stream = body::BodyStream::new(self.0);
119        Ok(body::to_bytes(stream).await?)
120    }
121}
122
123impl Stream for Payload {
124    type Item = Result<Bytes, PayloadError>;
125
126    #[inline]
127    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
128        Pin::new(&mut self.0).poll_next(cx)
129    }
130}
131
132/// See [here](#Examples) for example of usage as an extractor.
133#[expect(deprecated, reason = "preserve the public ready future type")]
134impl FromRequest for Payload {
135    type Error = Error;
136    type Future = Ready<Result<Self, Self::Error>>;
137
138    #[inline]
139    fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
140        ready(Ok(Payload(payload.take())))
141    }
142}
143
144/// Extract binary data from a request's payload.
145///
146/// Collects request payload stream into a [Bytes] instance.
147///
148/// Use [`PayloadConfig`] to configure extraction process.
149///
150/// # Examples
151/// ```
152/// use actix_web::{post, web};
153///
154/// /// extract binary data from request
155/// #[post("/")]
156/// async fn index(body: web::Bytes) -> String {
157///     format!("Body {:?}!", body)
158/// }
159/// ```
160#[expect(deprecated, reason = "preserve the public ready future type")]
161impl FromRequest for Bytes {
162    type Error = Error;
163    type Future = Either<BytesExtractFut, Ready<Result<Bytes, Error>>>;
164
165    #[inline]
166    fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
167        // allow both Config and Data<Config>
168        let cfg = PayloadConfig::from_req(req);
169
170        if let Err(err) = cfg.check_mimetype(req) {
171            return Either::right(ready(Err(err)));
172        }
173
174        Either::left(BytesExtractFut {
175            body_fut: HttpMessageBody::new(req, payload).limit(cfg.limit),
176        })
177    }
178}
179
180/// Future for `Bytes` extractor.
181pub struct BytesExtractFut {
182    body_fut: HttpMessageBody,
183}
184
185impl Future for BytesExtractFut {
186    type Output = Result<Bytes, Error>;
187
188    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
189        Pin::new(&mut self.body_fut).poll(cx).map_err(Into::into)
190    }
191}
192
193/// Extract text information from a request's body.
194///
195/// Text extractor automatically decode body according to the request's charset.
196///
197/// Use [`PayloadConfig`] to configure extraction process.
198///
199/// # Examples
200/// ```
201/// use actix_web::{post, web, FromRequest};
202///
203/// // extract text data from request
204/// #[post("/")]
205/// async fn index(text: String) -> String {
206///     format!("Body {}!", text)
207/// }
208#[expect(deprecated, reason = "preserve the public ready future type")]
209impl FromRequest for String {
210    type Error = Error;
211    type Future = Either<StringExtractFut, Ready<Result<String, Error>>>;
212
213    #[inline]
214    fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
215        let cfg = PayloadConfig::from_req(req);
216
217        // check content-type
218        if let Err(err) = cfg.check_mimetype(req) {
219            return Either::right(ready(Err(err)));
220        }
221
222        // check charset
223        let encoding = match req.encoding() {
224            Ok(enc) => enc,
225            Err(err) => return Either::right(ready(Err(err.into()))),
226        };
227        let limit = cfg.limit;
228        let body_fut = HttpMessageBody::new(req, payload).limit(limit);
229
230        Either::left(StringExtractFut { body_fut, encoding })
231    }
232}
233
234/// Future for `String` extractor.
235pub struct StringExtractFut {
236    body_fut: HttpMessageBody,
237    encoding: &'static Encoding,
238}
239
240impl Future for StringExtractFut {
241    type Output = Result<String, Error>;
242
243    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
244        let encoding = self.encoding;
245
246        Pin::new(&mut self.body_fut).poll(cx).map(|out| {
247            let body = out?;
248            bytes_to_string(body, encoding)
249        })
250    }
251}
252
253fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result<String, Error> {
254    if encoding == UTF_8 {
255        Ok(str::from_utf8(body.as_ref())
256            .map_err(|_| ErrorBadRequest("Can not decode body"))?
257            .to_owned())
258    } else {
259        Ok(encoding
260            .decode_without_bom_handling_and_without_replacement(&body)
261            .map(Cow::into_owned)
262            .ok_or_else(|| ErrorBadRequest("Can not decode body"))?)
263    }
264}
265
266/// Configuration for request payloads.
267///
268/// Applies to the built-in [`Bytes`] and [`String`] extractors.
269/// Note that the [`Payload`] extractor does not automatically check
270/// conformance with this configuration to allow more flexibility when
271/// building extractors on top of [`Payload`].
272///
273/// By default, the payload size limit is 256kB and there is no mime type condition.
274///
275/// To use this, add an instance of it to your [`app`](crate::App), [`scope`](crate::Scope)
276/// or [`resource`](crate::Resource) through the associated `.app_data()` method.
277#[derive(Clone)]
278pub struct PayloadConfig {
279    limit: usize,
280    mimetype: Option<Mime>,
281}
282
283impl PayloadConfig {
284    /// Create new instance with a size limit (in bytes) and no mime type condition.
285    pub fn new(limit: usize) -> Self {
286        Self {
287            limit,
288            ..Default::default()
289        }
290    }
291
292    /// Set maximum accepted payload size in bytes. The default limit is 256KiB.
293    pub fn limit(mut self, limit: usize) -> Self {
294        self.limit = limit;
295        self
296    }
297
298    /// Set required mime type of the request. By default mime type is not enforced.
299    pub fn mimetype(mut self, mt: Mime) -> Self {
300        self.mimetype = Some(mt);
301        self
302    }
303
304    fn check_mimetype(&self, req: &HttpRequest) -> Result<(), Error> {
305        // check content-type
306        if let Some(ref mt) = self.mimetype {
307            match req.mime_type() {
308                Ok(Some(ref req_mt)) => {
309                    if mt != req_mt {
310                        return Err(ErrorBadRequest("Unexpected Content-Type"));
311                    }
312                }
313                Ok(None) => {
314                    return Err(ErrorBadRequest("Content-Type is expected"));
315                }
316                Err(err) => {
317                    return Err(err.into());
318                }
319            }
320        }
321
322        Ok(())
323    }
324
325    /// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
326    /// back to the default payload config if neither is found.
327    fn from_req(req: &HttpRequest) -> &Self {
328        req.app_data::<Self>()
329            .or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
330            .unwrap_or(&DEFAULT_CONFIG)
331    }
332}
333
334const DEFAULT_CONFIG_LIMIT: usize = 262_144; // 2^18 bytes (~256kB)
335
336/// Allow shared refs used as defaults.
337const DEFAULT_CONFIG: PayloadConfig = PayloadConfig {
338    limit: DEFAULT_CONFIG_LIMIT,
339    mimetype: None,
340};
341
342impl Default for PayloadConfig {
343    fn default() -> Self {
344        DEFAULT_CONFIG
345    }
346}
347
348/// Future that resolves to a complete HTTP body payload.
349///
350/// By default only 256kB payload is accepted before `PayloadError::Overflow` is returned.
351/// Use `MessageBody::limit()` method to change upper limit.
352pub struct HttpMessageBody {
353    limit: usize,
354    length: Option<usize>,
355    #[cfg(feature = "__compress")]
356    stream: dev::Decompress<dev::Payload>,
357    #[cfg(not(feature = "__compress"))]
358    stream: dev::Payload,
359    buf: BytesMut,
360    err: Option<PayloadError>,
361}
362
363impl HttpMessageBody {
364    /// Create `MessageBody` for request.
365    #[allow(clippy::borrow_interior_mutable_const)]
366    pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody {
367        let mut length = None;
368        let mut err = None;
369
370        if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) {
371            match l.to_str() {
372                Ok(s) => match s.parse::<usize>() {
373                    Ok(l) => {
374                        if l > DEFAULT_CONFIG_LIMIT {
375                            err = Some(PayloadError::Overflow);
376                        }
377                        length = Some(l)
378                    }
379                    Err(_) => err = Some(PayloadError::UnknownLength),
380                },
381                Err(_) => err = Some(PayloadError::UnknownLength),
382            }
383        }
384
385        let stream = {
386            cfg_if::cfg_if! {
387                if #[cfg(feature = "__compress")] {
388                    dev::Decompress::from_headers(payload.take(), req.headers())
389                        .limit(DEFAULT_CONFIG_LIMIT)
390                } else {
391                    payload.take()
392                }
393            }
394        };
395
396        HttpMessageBody {
397            stream,
398            limit: DEFAULT_CONFIG_LIMIT,
399            length,
400            buf: BytesMut::with_capacity(8192),
401            err,
402        }
403    }
404
405    /// Change max size of payload. By default max size is 256kB
406    pub fn limit(mut self, limit: usize) -> Self {
407        if let Some(l) = self.length {
408            self.err = if l > limit {
409                Some(PayloadError::Overflow)
410            } else {
411                None
412            };
413        }
414        self.limit = limit;
415
416        #[cfg(feature = "__compress")]
417        {
418            self.stream = self.stream.limit(limit);
419        }
420
421        self
422    }
423}
424
425impl Future for HttpMessageBody {
426    type Output = Result<Bytes, PayloadError>;
427
428    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
429        let this = self.get_mut();
430
431        if let Some(err) = this.err.take() {
432            return Poll::Ready(Err(err));
433        }
434
435        loop {
436            let res = ready!(Pin::new(&mut this.stream).poll_next(cx));
437            match res {
438                Some(chunk) => {
439                    let chunk = chunk?;
440                    if this.buf.len() + chunk.len() > this.limit {
441                        return Poll::Ready(Err(PayloadError::Overflow));
442                    } else {
443                        this.buf.extend_from_slice(&chunk);
444                    }
445                }
446                None => return Poll::Ready(Ok(this.buf.split().freeze())),
447            }
448        }
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use super::*;
455    use crate::{
456        http::StatusCode,
457        test::{call_service, init_service, read_body, TestRequest},
458        App, Responder,
459    };
460
461    #[actix_rt::test]
462    async fn payload_to_bytes() {
463        async fn payload_handler(pl: Payload) -> crate::Result<impl Responder> {
464            pl.to_bytes().await
465        }
466
467        async fn limited_payload_handler(pl: Payload) -> crate::Result<impl Responder> {
468            match pl.to_bytes_limited(5).await {
469                Ok(res) => res,
470                Err(_limited) => Err(ErrorBadRequest("too big")),
471            }
472        }
473
474        let srv = init_service(
475            App::new()
476                .route("/all", web::to(payload_handler))
477                .route("limited", web::to(limited_payload_handler)),
478        )
479        .await;
480
481        let req = TestRequest::with_uri("/all")
482            .set_payload("1234567890")
483            .to_request();
484        let res = call_service(&srv, req).await;
485        assert_eq!(res.status(), StatusCode::OK);
486        let body = read_body(res).await;
487        assert_eq!(body, "1234567890");
488
489        let req = TestRequest::with_uri("/limited")
490            .set_payload("1234567890")
491            .to_request();
492        let res = call_service(&srv, req).await;
493        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
494
495        let req = TestRequest::with_uri("/limited")
496            .set_payload("12345")
497            .to_request();
498        let res = call_service(&srv, req).await;
499        assert_eq!(res.status(), StatusCode::OK);
500        let body = read_body(res).await;
501        assert_eq!(body, "12345");
502    }
503
504    #[actix_rt::test]
505    async fn test_payload_config() {
506        let req = TestRequest::default().to_http_request();
507        let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON);
508        assert!(cfg.check_mimetype(&req).is_err());
509
510        let req = TestRequest::default()
511            .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
512            .to_http_request();
513        assert!(cfg.check_mimetype(&req).is_err());
514
515        let req = TestRequest::default()
516            .insert_header((header::CONTENT_TYPE, "application/json"))
517            .to_http_request();
518        assert!(cfg.check_mimetype(&req).is_ok());
519    }
520
521    // allow deprecated App::data
522    #[allow(deprecated)]
523    #[actix_rt::test]
524    async fn test_config_recall_locations() {
525        async fn bytes_handler(_: Bytes) -> impl Responder {
526            "payload is probably json bytes"
527        }
528
529        async fn string_handler(_: String) -> impl Responder {
530            "payload is probably json string"
531        }
532
533        let srv = init_service(
534            App::new()
535                .service(
536                    web::resource("/bytes-app-data")
537                        .app_data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
538                        .route(web::get().to(bytes_handler)),
539                )
540                .service(
541                    web::resource("/bytes-data")
542                        .data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
543                        .route(web::get().to(bytes_handler)),
544                )
545                .service(
546                    web::resource("/string-app-data")
547                        .app_data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
548                        .route(web::get().to(string_handler)),
549                )
550                .service(
551                    web::resource("/string-data")
552                        .data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
553                        .route(web::get().to(string_handler)),
554                ),
555        )
556        .await;
557
558        let req = TestRequest::with_uri("/bytes-app-data").to_request();
559        let resp = call_service(&srv, req).await;
560        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
561
562        let req = TestRequest::with_uri("/bytes-data").to_request();
563        let resp = call_service(&srv, req).await;
564        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
565
566        let req = TestRequest::with_uri("/string-app-data").to_request();
567        let resp = call_service(&srv, req).await;
568        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
569
570        let req = TestRequest::with_uri("/string-data").to_request();
571        let resp = call_service(&srv, req).await;
572        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
573
574        let req = TestRequest::with_uri("/bytes-app-data")
575            .insert_header(header::ContentType(mime::APPLICATION_JSON))
576            .to_request();
577        let resp = call_service(&srv, req).await;
578        assert_eq!(resp.status(), StatusCode::OK);
579
580        let req = TestRequest::with_uri("/bytes-data")
581            .insert_header(header::ContentType(mime::APPLICATION_JSON))
582            .to_request();
583        let resp = call_service(&srv, req).await;
584        assert_eq!(resp.status(), StatusCode::OK);
585
586        let req = TestRequest::with_uri("/string-app-data")
587            .insert_header(header::ContentType(mime::APPLICATION_JSON))
588            .to_request();
589        let resp = call_service(&srv, req).await;
590        assert_eq!(resp.status(), StatusCode::OK);
591
592        let req = TestRequest::with_uri("/string-data")
593            .insert_header(header::ContentType(mime::APPLICATION_JSON))
594            .to_request();
595        let resp = call_service(&srv, req).await;
596        assert_eq!(resp.status(), StatusCode::OK);
597    }
598
599    #[actix_rt::test]
600    async fn test_bytes() {
601        let (req, mut pl) = TestRequest::default()
602            .insert_header((header::CONTENT_LENGTH, "11"))
603            .set_payload(Bytes::from_static(b"hello=world"))
604            .to_http_parts();
605
606        let s = Bytes::from_request(&req, &mut pl).await.unwrap();
607        assert_eq!(s, Bytes::from_static(b"hello=world"));
608    }
609
610    #[actix_rt::test]
611    async fn test_string() {
612        let (req, mut pl) = TestRequest::default()
613            .insert_header((header::CONTENT_LENGTH, "11"))
614            .set_payload(Bytes::from_static(b"hello=world"))
615            .to_http_parts();
616
617        let s = String::from_request(&req, &mut pl).await.unwrap();
618        assert_eq!(s, "hello=world");
619    }
620
621    #[actix_rt::test]
622    async fn test_message_body() {
623        let (req, mut pl) = TestRequest::default()
624            .insert_header((header::CONTENT_LENGTH, "xxxx"))
625            .to_srv_request()
626            .into_parts();
627        let res = HttpMessageBody::new(&req, &mut pl).await;
628        match res.err().unwrap() {
629            PayloadError::UnknownLength => {}
630            _ => unreachable!("error"),
631        }
632
633        let (req, mut pl) = TestRequest::default()
634            .insert_header((header::CONTENT_LENGTH, "1000000"))
635            .to_srv_request()
636            .into_parts();
637        let res = HttpMessageBody::new(&req, &mut pl).await;
638        match res.err().unwrap() {
639            PayloadError::Overflow => {}
640            _ => unreachable!("error"),
641        }
642
643        let (req, mut pl) = TestRequest::default()
644            .set_payload(Bytes::from_static(b"test"))
645            .to_http_parts();
646        let res = HttpMessageBody::new(&req, &mut pl).await;
647        assert_eq!(res.ok().unwrap(), Bytes::from_static(b"test"));
648
649        let (req, mut pl) = TestRequest::default()
650            .set_payload(Bytes::from_static(b"11111111111111"))
651            .to_http_parts();
652        let res = HttpMessageBody::new(&req, &mut pl).limit(5).await;
653        match res.err().unwrap() {
654            PayloadError::Overflow => {}
655            _ => unreachable!("error"),
656        }
657    }
658}