Skip to main content

ntex/web/types/
form.rs

1//! Form extractor
2use std::{borrow::Cow, fmt, future::Future, ops, pin::Pin, task::Context, task::Poll};
3
4use encoding_rs::{Encoding, UTF_8};
5use serde::{Serialize, de::DeserializeOwned};
6
7#[cfg(feature = "compress")]
8use crate::http::encoding::Decoder;
9use crate::http::header::{CONTENT_LENGTH, CONTENT_TYPE};
10use crate::http::{HttpMessage, Payload, Response, StatusCode};
11use crate::util::BoxFuture;
12use crate::web::error::{UrlencodedError, WebResponseError};
13use crate::web::{FromRequest, HttpRequest, Responder, State};
14
15/// Form data helper (`application/x-www-form-urlencoded`)
16///
17/// Can be used to extract url-encoded data from the request body,
18/// or send url-encoded data as the response.
19///
20/// ## Extract
21///
22/// To extract typed information from request's body, the type `T` must
23/// implement the `Deserialize` trait from *serde*.
24///
25/// [`FormConfig`] allows to configure extraction
26/// process.
27///
28/// ### Example
29/// ```rust
30/// use ntex::web;
31///
32/// #[derive(serde::Deserialize)]
33/// struct FormData {
34///     username: String,
35/// }
36///
37/// /// Extract form data using serde.
38/// ///
39/// /// If the content type is not *x-www-form-urlencoded* or the body cannot be
40/// /// deserialized to a `FormData` struct, the extractor fails with
41/// /// `UrlencodedError` and the handler is not called.
42/// async fn index(form: web::types::Form<FormData>) -> String {
43///     format!("Welcome {}!", form.username)
44/// }
45/// # fn main() {}
46/// ```
47///
48/// ## Respond
49///
50/// The `Form` type also allows you to respond with well-formed url-encoded data:
51/// simply return a value of type `Form<T>` where T is the type to be url-encoded.
52/// The type  must implement `serde::Serialize`;
53///
54/// ### Example
55/// ```rust
56/// use ntex::web;
57///
58/// #[derive(serde::Serialize)]
59/// struct SomeForm {
60///     name: String,
61///     age: u8
62/// }
63///
64/// // Will return a 200 response with header
65/// // `Content-Type: application/x-www-form-urlencoded`
66/// // and body "name=ntex&age=123"
67/// async fn index() -> web::types::Form<SomeForm> {
68///     web::types::Form(SomeForm {
69///         name: "ntex".into(),
70///         age: 123
71///     })
72/// }
73/// # fn main() {}
74/// ```
75#[derive(PartialEq, Eq, PartialOrd, Ord)]
76pub struct Form<T>(pub T);
77
78impl<T> Form<T> {
79    /// Deconstruct to an inner value
80    pub fn into_inner(self) -> T {
81        self.0
82    }
83}
84
85impl<T> ops::Deref for Form<T> {
86    type Target = T;
87
88    fn deref(&self) -> &T {
89        &self.0
90    }
91}
92
93impl<T> ops::DerefMut for Form<T> {
94    fn deref_mut(&mut self) -> &mut T {
95        &mut self.0
96    }
97}
98
99impl<St, T> FromRequest<St> for Form<T>
100where
101    St: State,
102    T: DeserializeOwned + 'static,
103{
104    type Error = UrlencodedError;
105
106    async fn from_request(
107        _: &St,
108        req: &HttpRequest,
109        payload: &mut Payload,
110    ) -> Result<Self, Self::Error> {
111        let limit = req.app_state::<FormConfig>().map_or(16384, |c| c.limit);
112
113        match UrlEncoded::new(req, payload).limit(limit).await {
114            Err(e) => Err(e),
115            Ok(item) => Ok(Form(item)),
116        }
117    }
118}
119
120impl<T: fmt::Debug> fmt::Debug for Form<T> {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        f.debug_tuple("Form").field(&self.0).finish()
123    }
124}
125
126impl<T: fmt::Display> fmt::Display for Form<T> {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        self.0.fmt(f)
129    }
130}
131
132impl<St, T: Serialize> Responder<St> for Form<T>
133where
134    St: State,
135    serde_urlencoded::ser::Error: WebResponseError<St, St::Error>,
136{
137    async fn respond_to(self, st: &St, _: &HttpRequest) -> Response {
138        let body = match serde_urlencoded::to_string(&self.0) {
139            Ok(body) => body,
140            Err(e) => return e.error_response(st),
141        };
142
143        Response::builder(StatusCode::OK)
144            .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
145            .body(body)
146    }
147}
148
149/// Form extractor configuration
150///
151/// ```rust
152/// use std::convert::Infallible;
153/// use ntex::web::{self, App, WebError, FromRequest, WebAppConfig};
154///
155/// #[derive(serde::Deserialize)]
156/// struct FormData {
157///     username: String,
158/// }
159///
160/// /// Extract form data using serde.
161/// /// Custom configuration is used for this handler, max payload size is 4k
162/// async fn index(form: web::types::Form<FormData>) -> Result<String, Infallible> {
163///     Ok(format!("Welcome {}!", form.username))
164/// }
165///
166/// fn main() {
167///     let cfg = WebAppConfig::new()
168///         // change `Form` extractor configuration
169///         .set_state(web::types::FormConfig::default().limit(4097));
170///
171///     let app = App::default()
172///         .with_config(cfg)
173///         .service(web::resource("/index.html").route(web::get().to(index)));
174/// }
175/// ```
176#[derive(Clone, Debug)]
177pub struct FormConfig {
178    limit: usize,
179}
180
181impl FormConfig {
182    #[must_use]
183    /// Change max size of payload.
184    ///
185    /// By default max size is 16Kb
186    pub fn limit(mut self, limit: usize) -> Self {
187        self.limit = limit;
188        self
189    }
190}
191
192impl Default for FormConfig {
193    fn default() -> Self {
194        FormConfig { limit: 16384 }
195    }
196}
197
198/// Future that resolves to a parsed urlencoded values.
199///
200/// Parse `application/x-www-form-urlencoded` encoded request's body.
201/// Return `UrlEncoded` future. Form can be deserialized to any type that
202/// implements `Deserialize` trait from *serde*.
203///
204/// Returns error:
205///
206/// * content type is not `application/x-www-form-urlencoded`
207/// * content-length is greater than the limit (32k by default; the `Form`
208///   extractor sets it from [`FormConfig`], 16k by default)
209///
210struct UrlEncoded<U> {
211    #[cfg(feature = "compress")]
212    stream: Option<Decoder<Payload>>,
213    #[cfg(not(feature = "compress"))]
214    stream: Option<Payload>,
215    limit: usize,
216    length: Option<usize>,
217    encoding: &'static Encoding,
218    err: Option<UrlencodedError>,
219    fut: Option<BoxFuture<'static, Result<U, UrlencodedError>>>,
220}
221
222impl<U> UrlEncoded<U> {
223    /// Create a new future to URL encode a request
224    fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded<U> {
225        // check content type
226        if !req
227            .content_type()
228            .eq_ignore_ascii_case("application/x-www-form-urlencoded")
229        {
230            return Self::err(UrlencodedError::ContentType);
231        }
232        let Ok(encoding) = req.encoding() else {
233            return Self::err(UrlencodedError::ContentType);
234        };
235
236        let mut len = None;
237        if let Some(l) = req.headers().get(&CONTENT_LENGTH) {
238            if let Ok(s) = l.to_str() {
239                if let Ok(l) = s.parse::<usize>() {
240                    len = Some(l);
241                } else {
242                    return Self::err(UrlencodedError::UnknownLength);
243                }
244            } else {
245                return Self::err(UrlencodedError::UnknownLength);
246            }
247        }
248
249        #[cfg(feature = "compress")]
250        let payload = Decoder::from_headers(payload.take(), req.headers());
251        #[cfg(not(feature = "compress"))]
252        let payload = payload.take();
253
254        UrlEncoded {
255            encoding,
256            stream: Some(payload),
257            limit: 32_768,
258            length: len,
259            fut: None,
260            err: None,
261        }
262    }
263
264    fn err(e: UrlencodedError) -> Self {
265        UrlEncoded {
266            stream: None,
267            limit: 32_768,
268            fut: None,
269            err: Some(e),
270            length: None,
271            encoding: UTF_8,
272        }
273    }
274
275    /// Change max size of payload. By default max size is 32Kb
276    fn limit(mut self, limit: usize) -> Self {
277        self.limit = limit;
278        self
279    }
280}
281
282impl<U> Future for UrlEncoded<U>
283where
284    U: DeserializeOwned + 'static,
285{
286    type Output = Result<U, UrlencodedError>;
287
288    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
289        if let Some(ref mut fut) = self.fut {
290            return Pin::new(fut).poll(cx);
291        }
292
293        if let Some(err) = self.err.take() {
294            return Poll::Ready(Err(err));
295        }
296
297        // payload size
298        let (limit, length) = (self.limit, self.length);
299        if let Some(len) = length
300            && len > limit
301        {
302            return Poll::Ready(Err(UrlencodedError::Overflow { size: len, limit }));
303        }
304
305        // future
306        let encoding = self.encoding;
307        let mut stream = self.stream.take().unwrap();
308
309        self.fut = Some(Box::pin(async move {
310            let body = super::read_body(&mut stream, limit, length, |size| {
311                UrlencodedError::Overflow { size, limit }
312            })
313            .await?;
314
315            if encoding == UTF_8 {
316                serde_urlencoded::from_bytes::<U>(&body).map_err(|_| UrlencodedError::Parse)
317            } else {
318                let body = encoding
319                    .decode_without_bom_handling_and_without_replacement(&body)
320                    .map(Cow::into_owned)
321                    .ok_or(UrlencodedError::Parse)?;
322                serde_urlencoded::from_str::<U>(&body).map_err(|_| UrlencodedError::Parse)
323            }
324        }));
325        self.poll(cx)
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use serde::{Deserialize, Serialize};
332
333    use super::*;
334    use crate::http::header::HeaderValue;
335    use crate::util::Bytes;
336    use crate::web::test::{TestRequest, from_request, respond_to};
337
338    #[derive(Deserialize, Serialize, Debug, PartialEq, thiserror::Error)]
339    #[error("Info({hello})")]
340    struct Info {
341        hello: String,
342        counter: i64,
343    }
344
345    fn eq(err: &UrlencodedError, other: &UrlencodedError) -> bool {
346        if let UrlencodedError::Overflow { .. } = err
347            && let UrlencodedError::Overflow { .. } = other
348        {
349            return true;
350        } else if let UrlencodedError::UnknownLength = err
351            && let UrlencodedError::UnknownLength = other
352        {
353            return true;
354        } else if let UrlencodedError::ContentType = err
355            && let UrlencodedError::ContentType = other
356        {
357            return true;
358        }
359        false
360    }
361
362    #[test]
363    fn test_basic() {
364        let mut f = Form(Info {
365            hello: "world".into(),
366            counter: 123,
367        });
368        assert_eq!(f.hello, "world");
369        f.hello = "test".to_string();
370        assert_eq!(f.hello, "test");
371        assert!(format!("{f:?}").contains("Form"));
372        assert!(format!("{f}").contains("test"));
373    }
374
375    #[crate::rt_test]
376    async fn test_form() {
377        let (req, mut pl, ()) =
378            TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
379                .header(CONTENT_LENGTH, "11")
380                .payload(Bytes::from_static(b"hello=world&counter=123"))
381                .to_http_parts();
382
383        let Form(s) = from_request::<_, Form<Info>>(&(), &req, &mut pl)
384            .await
385            .unwrap();
386        assert_eq!(
387            s,
388            Info {
389                hello: "world".into(),
390                counter: 123
391            }
392        );
393
394        let (req, mut pl, ()) =
395            TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
396                .header(CONTENT_LENGTH, "xx")
397                .payload(Bytes::from_static(b"hello=world&counter=123"))
398                .to_http_parts();
399        let res = from_request::<_, Form<Info>>(&(), &req, &mut pl).await;
400        assert!(eq(&res.err().unwrap(), &UrlencodedError::UnknownLength));
401    }
402
403    #[crate::rt_test]
404    async fn test_urlencoded_error() {
405        let (req, mut pl, ()) =
406            TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
407                .header(CONTENT_LENGTH, "xxxx")
408                .to_http_parts();
409        let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
410        assert!(eq(&info.err().unwrap(), &UrlencodedError::UnknownLength));
411
412        let (req, mut pl, ()) =
413            TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
414                .header(CONTENT_LENGTH, "1000000")
415                .to_http_parts();
416        let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
417        assert!(eq(
418            &info.err().unwrap(),
419            &UrlencodedError::Overflow { size: 0, limit: 0 }
420        ));
421
422        let (req, mut pl, ()) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
423            .header(CONTENT_LENGTH, "10")
424            .to_http_parts();
425        let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
426        assert!(eq(&info.err().unwrap(), &UrlencodedError::ContentType));
427    }
428
429    #[crate::rt_test]
430    async fn test_urlencoded() {
431        let (req, mut pl, ()) =
432            TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
433                .header(CONTENT_LENGTH, "11")
434                .payload(Bytes::from_static(b"hello=world&counter=123"))
435                .to_http_parts();
436
437        let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
438        assert_eq!(
439            info,
440            Info {
441                hello: "world".to_owned(),
442                counter: 123
443            }
444        );
445
446        let (req, mut pl, ()) =
447            TestRequest::with_header(CONTENT_TYPE, "Application/X-WWW-Form-URLEncoded")
448                .header(CONTENT_LENGTH, "11")
449                .payload(Bytes::from_static(b"hello=world&counter=123"))
450                .to_http_parts();
451        assert!(UrlEncoded::<Info>::new(&req, &mut pl).await.is_ok());
452
453        let (req, mut pl, ()) = TestRequest::with_header(
454            CONTENT_TYPE,
455            "application/x-www-form-urlencoded; charset=utf-8",
456        )
457        .header(CONTENT_LENGTH, "11")
458        .payload(Bytes::from_static(b"hello=world&counter=123"))
459        .to_http_parts();
460
461        let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
462        assert_eq!(
463            info,
464            Info {
465                hello: "world".to_owned(),
466                counter: 123
467            }
468        );
469
470        let (req, mut pl, ()) = TestRequest::with_header(
471            CONTENT_TYPE,
472            "application/x-www-form-urlencoded; charset=cp1251",
473        )
474        .header(CONTENT_LENGTH, "11")
475        .payload(Bytes::from_static(b"hello=world&counter=123"))
476        .to_http_parts();
477
478        let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
479        assert_eq!(
480            info,
481            Info {
482                hello: "world".to_owned(),
483                counter: 123
484            }
485        );
486    }
487
488    #[crate::rt_test]
489    async fn test_responder() {
490        let req = TestRequest::default().to_http_request();
491
492        let form = Form(Info {
493            hello: "world".to_string(),
494            counter: 123,
495        });
496        let resp = respond_to(form, &req).await;
497        assert_eq!(resp.status(), StatusCode::OK);
498        assert_eq!(
499            resp.headers().get(CONTENT_TYPE).unwrap(),
500            HeaderValue::from_static("application/x-www-form-urlencoded")
501        );
502
503        assert_eq!(resp.get_body_ref(), b"hello=world&counter=123");
504    }
505
506    #[crate::rt_test]
507    async fn test_urlencoded_errors2() {
508        let (req, mut pl, ()) = TestRequest::with_header(
509            CONTENT_TYPE,
510            "application/x-www-form-urlencoded; charset=unknown",
511        )
512        .to_http_parts();
513        let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
514        assert!(eq(&info.err().unwrap(), &UrlencodedError::ContentType));
515
516        let (req, mut pl, ()) =
517            TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
518                .header(CONTENT_LENGTH, HeaderValue::from_bytes(b"1\xff").unwrap())
519                .to_http_parts();
520        let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
521        assert!(eq(&info.err().unwrap(), &UrlencodedError::UnknownLength));
522
523        // no content-length, payload is larger than limit
524        let (req, mut pl, ()) =
525            TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
526                .payload(Bytes::from_static(b"hello=world&counter=123"))
527                .to_http_parts();
528        let info = UrlEncoded::<Info>::new(&req, &mut pl).limit(5).await;
529        assert!(eq(
530            &info.err().unwrap(),
531            &UrlencodedError::Overflow { size: 0, limit: 0 }
532        ));
533    }
534
535    #[crate::rt_test]
536    async fn test_responder_error() {
537        let req = TestRequest::default().to_http_request();
538        let resp = respond_to(Form(1), &req).await;
539        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
540    }
541}