1use 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
24pub struct Payload(dev::Payload);
48
49impl Payload {
50 #[inline]
52 pub fn into_inner(self) -> dev::Payload {
53 self.0
54 }
55
56 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 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#[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#[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 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
180pub 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#[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 if let Err(err) = cfg.check_mimetype(req) {
219 return Either::right(ready(Err(err)));
220 }
221
222 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
234pub 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#[derive(Clone)]
278pub struct PayloadConfig {
279 limit: usize,
280 mimetype: Option<Mime>,
281}
282
283impl PayloadConfig {
284 pub fn new(limit: usize) -> Self {
286 Self {
287 limit,
288 ..Default::default()
289 }
290 }
291
292 pub fn limit(mut self, limit: usize) -> Self {
294 self.limit = limit;
295 self
296 }
297
298 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 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 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; const 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
348pub 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 #[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 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)]
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}