use crate::response::IntoResponse; use super::{rejection::*, FromRequest, RequestParts}; use async_trait::async_trait; use std::ops::Deref; /// Extractor that will reject requests with a body larger than some size. /// /// # Example /// /// ```rust,no_run /// use axum::{ /// extract::ContentLengthLimit, /// routing::post, /// Router, /// }; /// /// async fn handler(body: ContentLengthLimit) { /// // ... /// } /// /// let app = Router::new().route("/", post(handler)); /// # async { /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # }; /// ``` /// /// This requires the request to have a `Content-Length` header. #[derive(Debug, Clone)] pub struct ContentLengthLimit(pub T); #[async_trait] impl FromRequest for ContentLengthLimit where T: FromRequest, T::Rejection: IntoResponse, B: Send, { type Rejection = ContentLengthLimitRejection; async fn from_request(req: &mut RequestParts) -> Result { let content_length = req .headers() .ok_or(ContentLengthLimitRejection::HeadersAlreadyExtracted( HeadersAlreadyExtracted, ))? .get(http::header::CONTENT_LENGTH); let content_length = content_length.and_then(|value| value.to_str().ok()?.parse::().ok()); if let Some(length) = content_length { if length > N { return Err(ContentLengthLimitRejection::PayloadTooLarge( PayloadTooLarge, )); } } else { return Err(ContentLengthLimitRejection::LengthRequired(LengthRequired)); }; let value = T::from_request(req) .await .map_err(ContentLengthLimitRejection::Inner)?; Ok(Self(value)) } } impl Deref for ContentLengthLimit { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } #[cfg(test)] mod tests { use super::*; use crate::{routing::post, test_helpers::*, Router}; use bytes::Bytes; use http::StatusCode; use serde::Deserialize; #[tokio::test] async fn body_with_length_limit() { use std::iter::repeat; #[derive(Debug, Deserialize)] struct Input { foo: String, } const LIMIT: u64 = 8; let app = Router::new().route( "/", post(|_body: ContentLengthLimit| async {}), ); let client = TestClient::new(app); let res = client .post("/") .body(repeat(0_u8).take((LIMIT - 1) as usize).collect::>()) .send() .await; assert_eq!(res.status(), StatusCode::OK); let res = client .post("/") .body(repeat(0_u8).take(LIMIT as usize).collect::>()) .send() .await; assert_eq!(res.status(), StatusCode::OK); let res = client .post("/") .body(repeat(0_u8).take((LIMIT + 1) as usize).collect::>()) .send() .await; assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); let res = client .post("/") .body(reqwest::Body::wrap_stream(futures_util::stream::iter( vec![Ok::<_, std::io::Error>(bytes::Bytes::new())], ))) .send() .await; assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED); } }