• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use async_trait::async_trait;
2 use axum_core::extract::FromRequest;
3 use bytes::{Bytes, BytesMut};
4 use http::{Method, Request};
5 
6 use super::{
7     has_content_type,
8     rejection::{InvalidFormContentType, RawFormRejection},
9 };
10 
11 use crate::{body::HttpBody, BoxError};
12 
13 /// Extractor that extracts raw form requests.
14 ///
15 /// For `GET` requests it will extract the raw query. For other methods it extracts the raw
16 /// `application/x-www-form-urlencoded` encoded request body.
17 ///
18 /// # Example
19 ///
20 /// ```rust,no_run
21 /// use axum::{
22 ///     extract::RawForm,
23 ///     routing::get,
24 ///     Router
25 /// };
26 ///
27 /// async fn handler(RawForm(form): RawForm) {}
28 ///
29 /// let app = Router::new().route("/", get(handler));
30 /// # async {
31 /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
32 /// # };
33 /// ```
34 #[derive(Debug)]
35 pub struct RawForm(pub Bytes);
36 
37 #[async_trait]
38 impl<S, B> FromRequest<S, B> for RawForm
39 where
40     B: HttpBody + Send + 'static,
41     B::Data: Send,
42     B::Error: Into<BoxError>,
43     S: Send + Sync,
44 {
45     type Rejection = RawFormRejection;
46 
from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection>47     async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
48         if req.method() == Method::GET {
49             let mut bytes = BytesMut::new();
50 
51             if let Some(query) = req.uri().query() {
52                 bytes.extend(query.as_bytes());
53             }
54 
55             Ok(Self(bytes.freeze()))
56         } else {
57             if !has_content_type(req.headers(), &mime::APPLICATION_WWW_FORM_URLENCODED) {
58                 return Err(InvalidFormContentType.into());
59             }
60 
61             Ok(Self(Bytes::from_request(req, state).await?))
62         }
63     }
64 }
65 
66 #[cfg(test)]
67 mod tests {
68     use http::{header::CONTENT_TYPE, Request};
69 
70     use super::{InvalidFormContentType, RawForm, RawFormRejection};
71 
72     use crate::{
73         body::{Bytes, Empty, Full},
74         extract::FromRequest,
75     };
76 
check_query(uri: &str, value: &[u8])77     async fn check_query(uri: &str, value: &[u8]) {
78         let req = Request::builder()
79             .uri(uri)
80             .body(Empty::<Bytes>::new())
81             .unwrap();
82 
83         assert_eq!(RawForm::from_request(req, &()).await.unwrap().0, value);
84     }
85 
check_body(body: &'static [u8])86     async fn check_body(body: &'static [u8]) {
87         let req = Request::post("http://example.com/test")
88             .header(CONTENT_TYPE, mime::APPLICATION_WWW_FORM_URLENCODED.as_ref())
89             .body(Full::new(Bytes::from(body)))
90             .unwrap();
91 
92         assert_eq!(RawForm::from_request(req, &()).await.unwrap().0, body);
93     }
94 
95     #[crate::test]
test_from_query()96     async fn test_from_query() {
97         check_query("http://example.com/test", b"").await;
98 
99         check_query("http://example.com/test?page=0&size=10", b"page=0&size=10").await;
100     }
101 
102     #[crate::test]
test_from_body()103     async fn test_from_body() {
104         check_body(b"").await;
105 
106         check_body(b"username=user&password=secure%20password").await;
107     }
108 
109     #[crate::test]
test_incorrect_content_type()110     async fn test_incorrect_content_type() {
111         let req = Request::post("http://example.com/test")
112             .body(Full::<Bytes>::from(Bytes::from("page=0&size=10")))
113             .unwrap();
114 
115         assert!(matches!(
116             RawForm::from_request(req, &()).await.unwrap_err(),
117             RawFormRejection::InvalidFormContentType(InvalidFormContentType)
118         ))
119     }
120 }
121