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                } else {
390                    payload.take()
391                }
392            }
393        };
394
395        HttpMessageBody {
396            stream,
397            limit: DEFAULT_CONFIG_LIMIT,
398            length,
399            buf: BytesMut::with_capacity(8192),
400            err,
401        }
402    }
403
404    /// Change max size of payload. By default max size is 256kB
405    pub fn limit(mut self, limit: usize) -> Self {
406        if let Some(l) = self.length {
407            self.err = if l > limit {
408                Some(PayloadError::Overflow)
409            } else {
410                None
411            };
412        }
413        self.limit = limit;
414        self
415    }
416}
417
418impl Future for HttpMessageBody {
419    type Output = Result<Bytes, PayloadError>;
420
421    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
422        let this = self.get_mut();
423
424        if let Some(err) = this.err.take() {
425            return Poll::Ready(Err(err));
426        }
427
428        loop {
429            let res = ready!(Pin::new(&mut this.stream).poll_next(cx));
430            match res {
431                Some(chunk) => {
432                    let chunk = chunk?;
433                    if this.buf.len() + chunk.len() > this.limit {
434                        return Poll::Ready(Err(PayloadError::Overflow));
435                    } else {
436                        this.buf.extend_from_slice(&chunk);
437                    }
438                }
439                None => return Poll::Ready(Ok(this.buf.split().freeze())),
440            }
441        }
442    }
443}
444
445#[cfg(test)]
446mod tests {
447    use super::*;
448    use crate::{
449        http::StatusCode,
450        test::{call_service, init_service, read_body, TestRequest},
451        App, Responder,
452    };
453
454    #[actix_rt::test]
455    async fn payload_to_bytes() {
456        async fn payload_handler(pl: Payload) -> crate::Result<impl Responder> {
457            pl.to_bytes().await
458        }
459
460        async fn limited_payload_handler(pl: Payload) -> crate::Result<impl Responder> {
461            match pl.to_bytes_limited(5).await {
462                Ok(res) => res,
463                Err(_limited) => Err(ErrorBadRequest("too big")),
464            }
465        }
466
467        let srv = init_service(
468            App::new()
469                .route("/all", web::to(payload_handler))
470                .route("limited", web::to(limited_payload_handler)),
471        )
472        .await;
473
474        let req = TestRequest::with_uri("/all")
475            .set_payload("1234567890")
476            .to_request();
477        let res = call_service(&srv, req).await;
478        assert_eq!(res.status(), StatusCode::OK);
479        let body = read_body(res).await;
480        assert_eq!(body, "1234567890");
481
482        let req = TestRequest::with_uri("/limited")
483            .set_payload("1234567890")
484            .to_request();
485        let res = call_service(&srv, req).await;
486        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
487
488        let req = TestRequest::with_uri("/limited")
489            .set_payload("12345")
490            .to_request();
491        let res = call_service(&srv, req).await;
492        assert_eq!(res.status(), StatusCode::OK);
493        let body = read_body(res).await;
494        assert_eq!(body, "12345");
495    }
496
497    #[actix_rt::test]
498    async fn test_payload_config() {
499        let req = TestRequest::default().to_http_request();
500        let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON);
501        assert!(cfg.check_mimetype(&req).is_err());
502
503        let req = TestRequest::default()
504            .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
505            .to_http_request();
506        assert!(cfg.check_mimetype(&req).is_err());
507
508        let req = TestRequest::default()
509            .insert_header((header::CONTENT_TYPE, "application/json"))
510            .to_http_request();
511        assert!(cfg.check_mimetype(&req).is_ok());
512    }
513
514    // allow deprecated App::data
515    #[allow(deprecated)]
516    #[actix_rt::test]
517    async fn test_config_recall_locations() {
518        async fn bytes_handler(_: Bytes) -> impl Responder {
519            "payload is probably json bytes"
520        }
521
522        async fn string_handler(_: String) -> impl Responder {
523            "payload is probably json string"
524        }
525
526        let srv = init_service(
527            App::new()
528                .service(
529                    web::resource("/bytes-app-data")
530                        .app_data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
531                        .route(web::get().to(bytes_handler)),
532                )
533                .service(
534                    web::resource("/bytes-data")
535                        .data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
536                        .route(web::get().to(bytes_handler)),
537                )
538                .service(
539                    web::resource("/string-app-data")
540                        .app_data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
541                        .route(web::get().to(string_handler)),
542                )
543                .service(
544                    web::resource("/string-data")
545                        .data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
546                        .route(web::get().to(string_handler)),
547                ),
548        )
549        .await;
550
551        let req = TestRequest::with_uri("/bytes-app-data").to_request();
552        let resp = call_service(&srv, req).await;
553        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
554
555        let req = TestRequest::with_uri("/bytes-data").to_request();
556        let resp = call_service(&srv, req).await;
557        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
558
559        let req = TestRequest::with_uri("/string-app-data").to_request();
560        let resp = call_service(&srv, req).await;
561        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
562
563        let req = TestRequest::with_uri("/string-data").to_request();
564        let resp = call_service(&srv, req).await;
565        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
566
567        let req = TestRequest::with_uri("/bytes-app-data")
568            .insert_header(header::ContentType(mime::APPLICATION_JSON))
569            .to_request();
570        let resp = call_service(&srv, req).await;
571        assert_eq!(resp.status(), StatusCode::OK);
572
573        let req = TestRequest::with_uri("/bytes-data")
574            .insert_header(header::ContentType(mime::APPLICATION_JSON))
575            .to_request();
576        let resp = call_service(&srv, req).await;
577        assert_eq!(resp.status(), StatusCode::OK);
578
579        let req = TestRequest::with_uri("/string-app-data")
580            .insert_header(header::ContentType(mime::APPLICATION_JSON))
581            .to_request();
582        let resp = call_service(&srv, req).await;
583        assert_eq!(resp.status(), StatusCode::OK);
584
585        let req = TestRequest::with_uri("/string-data")
586            .insert_header(header::ContentType(mime::APPLICATION_JSON))
587            .to_request();
588        let resp = call_service(&srv, req).await;
589        assert_eq!(resp.status(), StatusCode::OK);
590    }
591
592    #[actix_rt::test]
593    async fn test_bytes() {
594        let (req, mut pl) = TestRequest::default()
595            .insert_header((header::CONTENT_LENGTH, "11"))
596            .set_payload(Bytes::from_static(b"hello=world"))
597            .to_http_parts();
598
599        let s = Bytes::from_request(&req, &mut pl).await.unwrap();
600        assert_eq!(s, Bytes::from_static(b"hello=world"));
601    }
602
603    #[actix_rt::test]
604    async fn test_string() {
605        let (req, mut pl) = TestRequest::default()
606            .insert_header((header::CONTENT_LENGTH, "11"))
607            .set_payload(Bytes::from_static(b"hello=world"))
608            .to_http_parts();
609
610        let s = String::from_request(&req, &mut pl).await.unwrap();
611        assert_eq!(s, "hello=world");
612    }
613
614    #[actix_rt::test]
615    async fn test_message_body() {
616        let (req, mut pl) = TestRequest::default()
617            .insert_header((header::CONTENT_LENGTH, "xxxx"))
618            .to_srv_request()
619            .into_parts();
620        let res = HttpMessageBody::new(&req, &mut pl).await;
621        match res.err().unwrap() {
622            PayloadError::UnknownLength => {}
623            _ => unreachable!("error"),
624        }
625
626        let (req, mut pl) = TestRequest::default()
627            .insert_header((header::CONTENT_LENGTH, "1000000"))
628            .to_srv_request()
629            .into_parts();
630        let res = HttpMessageBody::new(&req, &mut pl).await;
631        match res.err().unwrap() {
632            PayloadError::Overflow => {}
633            _ => unreachable!("error"),
634        }
635
636        let (req, mut pl) = TestRequest::default()
637            .set_payload(Bytes::from_static(b"test"))
638            .to_http_parts();
639        let res = HttpMessageBody::new(&req, &mut pl).await;
640        assert_eq!(res.ok().unwrap(), Bytes::from_static(b"test"));
641
642        let (req, mut pl) = TestRequest::default()
643            .set_payload(Bytes::from_static(b"11111111111111"))
644            .to_http_parts();
645        let res = HttpMessageBody::new(&req, &mut pl).limit(5).await;
646        match res.err().unwrap() {
647            PayloadError::Overflow => {}
648            _ => unreachable!("error"),
649        }
650    }
651}