1use std::{
4 fmt,
5 marker::PhantomData,
6 pin::Pin,
7 task::{Context, Poll, ready},
8};
9
10use actix_web::{
11 Error, FromRequest, HttpMessage, HttpRequest, ResponseError,
12 dev::Payload,
13 error::PayloadError,
14 http::{StatusCode, header},
15 web,
16};
17use derive_more::{Display, Error};
18use futures_util::Stream as _;
19use serde::de::DeserializeOwned;
20use tracing::debug;
21
22pub const DEFAULT_URL_ENCODED_FORM_LIMIT: usize = 2_097_152;
24
25#[doc(alias = "html_form", alias = "html form", alias = "form")]
61#[derive(Debug)]
62pub struct UrlEncodedForm<T, const LIMIT: usize = DEFAULT_URL_ENCODED_FORM_LIMIT>(pub T);
64
65mod waiting_on_derive_more_to_start_using_syn_2_due_to_proc_macro_panic {
66 use super::*;
67
68 impl<T, const LIMIT: usize> std::ops::Deref for UrlEncodedForm<T, LIMIT> {
69 type Target = T;
70
71 fn deref(&self) -> &Self::Target {
72 &self.0
73 }
74 }
75
76 impl<T, const LIMIT: usize> std::ops::DerefMut for UrlEncodedForm<T, LIMIT> {
77 fn deref_mut(&mut self) -> &mut Self::Target {
78 &mut self.0
79 }
80 }
81
82 impl<T: std::fmt::Display, const LIMIT: usize> std::fmt::Display for UrlEncodedForm<T, LIMIT> {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 std::fmt::Display::fmt(&self.0, f)
85 }
86 }
87}
88
89impl<T, const LIMIT: usize> UrlEncodedForm<T, LIMIT> {
90 pub fn into_inner(self) -> T {
92 self.0
93 }
94}
95
96impl<T: DeserializeOwned, const LIMIT: usize> FromRequest for UrlEncodedForm<T, LIMIT> {
98 type Error = Error;
99 type Future = UrlEncodedFormExtractFut<T, LIMIT>;
100
101 #[inline]
102 fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
103 UrlEncodedFormExtractFut {
104 req: Some(req.clone()),
105 fut: UrlEncodedFormBody::new(req, payload),
106 }
107 }
108}
109
110#[allow(missing_debug_implementations)]
111pub struct UrlEncodedFormExtractFut<T, const LIMIT: usize> {
112 req: Option<HttpRequest>,
113 fut: UrlEncodedFormBody<T, LIMIT>,
114}
115
116impl<T: DeserializeOwned, const LIMIT: usize> Future for UrlEncodedFormExtractFut<T, LIMIT> {
117 type Output = Result<UrlEncodedForm<T, LIMIT>, Error>;
118
119 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
120 let this = self.get_mut();
121
122 let res = ready!(Pin::new(&mut this.fut).poll(cx));
123
124 let res = match res {
125 Err(err) => {
126 let req = this.req.take().unwrap();
127 debug!(
128 "Failed to deserialize UrlEncodedForm<{}> from payload in handler: {}",
129 core::any::type_name::<T>(),
130 req.match_name().unwrap_or_else(|| req.path())
131 );
132
133 Err(err.into())
134 }
135 Ok(data) => Ok(UrlEncodedForm(data)),
136 };
137
138 Poll::Ready(res)
139 }
140}
141
142pub enum UrlEncodedFormBody<T, const LIMIT: usize> {
151 Error(Option<UrlEncodedFormError>),
152 Body {
153 #[allow(dead_code)]
155 length: Option<usize>,
156 payload: Payload,
157 buf: web::BytesMut,
158 _res: PhantomData<fn() -> T>,
159 },
160}
161
162impl<T: DeserializeOwned, const LIMIT: usize> UrlEncodedFormBody<T, LIMIT> {
163 pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self {
165 let can_parse_form = if let Ok(Some(mime)) = req.mime_type() {
167 mime == mime::APPLICATION_WWW_FORM_URLENCODED
168 } else {
169 false
170 };
171
172 if !can_parse_form {
173 return UrlEncodedFormBody::Error(Some(UrlEncodedFormError::ContentType));
174 }
175
176 let length = req
177 .headers()
178 .get(&header::CONTENT_LENGTH)
179 .and_then(|l| l.to_str().ok())
180 .and_then(|s| s.parse::<usize>().ok());
181
182 let payload = payload.take();
187
188 if let Some(len) = length
189 && len > LIMIT
190 {
191 return UrlEncodedFormBody::Error(Some(UrlEncodedFormError::Overflow {
192 size: len,
193 limit: LIMIT,
194 }));
195 }
196
197 UrlEncodedFormBody::Body {
198 length,
199 payload,
200 buf: web::BytesMut::with_capacity(8192),
201 _res: PhantomData,
202 }
203 }
204}
205
206impl<T: DeserializeOwned, const LIMIT: usize> Future for UrlEncodedFormBody<T, LIMIT> {
207 type Output = Result<T, UrlEncodedFormError>;
208
209 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
210 let this = self.get_mut();
211
212 match this {
213 UrlEncodedFormBody::Body { buf, payload, .. } => loop {
214 let res = ready!(Pin::new(&mut *payload).poll_next(cx));
215
216 match res {
217 Some(chunk) => {
218 let chunk =
219 chunk.map_err(|err| UrlEncodedFormError::Payload { source: err })?;
220
221 let buf_len = buf.len() + chunk.len();
222 if buf_len > LIMIT {
223 return Poll::Ready(Err(UrlEncodedFormError::Overflow {
224 size: buf_len,
225 limit: LIMIT,
226 }));
227 } else {
228 buf.extend_from_slice(&chunk);
229 }
230 }
231
232 None => {
233 let de = serde_html_form::Deserializer::from_bytes(buf);
234
235 let form = serde_path_to_error::deserialize(de).map_err(|err| {
236 UrlEncodedFormError::Deserialize {
237 source: UrlEncodedFormDeserializeError {
238 path: err.path().clone(),
239 source: err.into_inner(),
240 },
241 }
242 })?;
243
244 return Poll::Ready(Ok(form));
245 }
246 }
247 },
248
249 UrlEncodedFormBody::Error(err) => Poll::Ready(Err(err.take().unwrap())),
250 }
251 }
252}
253
254#[derive(Debug, Display, Error)]
256#[non_exhaustive]
257pub enum UrlEncodedFormError {
258 #[display(
260 "URL encoded payload is larger ({} bytes) than allowed (limit: {} bytes).",
261 size,
262 limit
263 )]
264 Overflow { size: usize, limit: usize },
265
266 #[display("Content type error.")]
268 ContentType,
269
270 #[display("Deserialization error")]
272 Deserialize {
273 source: UrlEncodedFormDeserializeError,
275 },
276
277 #[display("Error that occur during reading payload")]
279 Payload { source: PayloadError },
280}
281
282impl ResponseError for UrlEncodedFormError {
283 fn status_code(&self) -> StatusCode {
284 match self {
285 Self::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE,
286 Self::ContentType => StatusCode::UNSUPPORTED_MEDIA_TYPE,
287 Self::Payload { source: err } => err.status_code(),
288 Self::Deserialize { .. } => StatusCode::UNPROCESSABLE_ENTITY,
289 }
290 }
291}
292
293#[derive(Debug, Error)]
295pub struct UrlEncodedFormDeserializeError {
296 path: serde_path_to_error::Path,
298
299 source: serde_html_form::de::Error,
301}
302
303impl UrlEncodedFormDeserializeError {
304 pub fn path(&self) -> impl fmt::Display + '_ {
306 &self.path
307 }
308
309 pub fn source(&self) -> &serde_html_form::de::Error {
311 &self.source
312 }
313}
314
315impl fmt::Display for UrlEncodedFormDeserializeError {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 f.write_str("URL-encoded form deserialization failed")?;
318
319 if self.path.iter().len() > 0 {
320 write!(f, " at path: {}", self.path)?;
321 }
322
323 Ok(())
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use actix_web::{http::header, test::TestRequest, web::Bytes};
330 use serde::{Deserialize, Serialize};
331
332 use super::*;
333
334 #[derive(Serialize, Deserialize, PartialEq, Debug)]
335 struct MyObject {
336 name: String,
337 }
338
339 fn err_eq(err: UrlEncodedFormError, other: UrlEncodedFormError) -> bool {
340 match err {
341 UrlEncodedFormError::Overflow { .. } => {
342 matches!(other, UrlEncodedFormError::Overflow { .. })
343 }
344
345 UrlEncodedFormError::ContentType => matches!(other, UrlEncodedFormError::ContentType),
346
347 _ => false,
348 }
349 }
350
351 #[actix_web::test]
352 async fn test_extract() {
353 let (req, mut pl) = TestRequest::default()
354 .insert_header(header::ContentType::form_url_encoded())
355 .insert_header((
356 header::CONTENT_LENGTH,
357 header::HeaderValue::from_static("9"),
358 ))
359 .set_payload(Bytes::from_static(b"name=test"))
360 .to_http_parts();
361
362 let s =
363 UrlEncodedForm::<MyObject, DEFAULT_URL_ENCODED_FORM_LIMIT>::from_request(&req, &mut pl)
364 .await
365 .unwrap();
366 assert_eq!(s.name, "test");
367 assert_eq!(
368 s.into_inner(),
369 MyObject {
370 name: "test".to_string()
371 }
372 );
373
374 let (req, mut pl) = TestRequest::default()
375 .insert_header(header::ContentType::form_url_encoded())
376 .insert_header((
377 header::CONTENT_LENGTH,
378 header::HeaderValue::from_static("9"),
379 ))
380 .set_payload(Bytes::from_static(b"name=test"))
381 .to_http_parts();
382
383 let s = UrlEncodedForm::<MyObject, 8>::from_request(&req, &mut pl).await;
384 let err = format!("{}", s.unwrap_err());
385 assert_eq!(
386 err,
387 "URL encoded payload is larger (9 bytes) than allowed (limit: 8 bytes).",
388 );
389
390 let (req, mut pl) = TestRequest::default()
391 .insert_header(header::ContentType::form_url_encoded())
392 .insert_header((
393 header::CONTENT_LENGTH,
394 header::HeaderValue::from_static("9"),
395 ))
396 .set_payload(Bytes::from_static(b"name=test"))
397 .to_http_parts();
398 let s = UrlEncodedForm::<MyObject, 8>::from_request(&req, &mut pl).await;
399 let err = format!("{}", s.unwrap_err());
400 assert!(
401 err.contains("payload is larger") && err.contains("than allowed"),
402 "unexpected error string: {err:?}"
403 );
404 }
405
406 #[actix_web::test]
407 async fn test_form_body() {
408 let (req, mut pl) = TestRequest::default().to_http_parts();
409 let form =
410 UrlEncodedFormBody::<MyObject, DEFAULT_URL_ENCODED_FORM_LIMIT>::new(&req, &mut pl)
411 .await;
412 assert!(err_eq(form.unwrap_err(), UrlEncodedFormError::ContentType));
413
414 let (req, mut pl) = TestRequest::default()
415 .insert_header((
416 header::CONTENT_TYPE,
417 header::HeaderValue::from_static("application/text"),
418 ))
419 .to_http_parts();
420 let form =
421 UrlEncodedFormBody::<MyObject, DEFAULT_URL_ENCODED_FORM_LIMIT>::new(&req, &mut pl)
422 .await;
423 assert!(err_eq(form.unwrap_err(), UrlEncodedFormError::ContentType));
424
425 let (req, mut pl) = TestRequest::default()
426 .insert_header(header::ContentType::form_url_encoded())
427 .insert_header((
428 header::CONTENT_LENGTH,
429 header::HeaderValue::from_static("10000"),
430 ))
431 .to_http_parts();
432
433 let form = UrlEncodedFormBody::<MyObject, 100>::new(&req, &mut pl).await;
434 assert!(err_eq(
435 form.unwrap_err(),
436 UrlEncodedFormError::Overflow {
437 size: 10000,
438 limit: 100
439 }
440 ));
441
442 let (req, mut pl) = TestRequest::default()
443 .insert_header(header::ContentType::form_url_encoded())
444 .set_payload(Bytes::from_static(&[0u8; 1000]))
445 .to_http_parts();
446
447 let form = UrlEncodedFormBody::<MyObject, 100>::new(&req, &mut pl).await;
448
449 assert!(err_eq(
450 form.unwrap_err(),
451 UrlEncodedFormError::Overflow {
452 size: 1000,
453 limit: 100
454 }
455 ));
456
457 let (req, mut pl) = TestRequest::default()
458 .insert_header(header::ContentType::form_url_encoded())
459 .insert_header((
460 header::CONTENT_LENGTH,
461 header::HeaderValue::from_static("9"),
462 ))
463 .set_payload(Bytes::from_static(b"name=test"))
464 .to_http_parts();
465
466 let form =
467 UrlEncodedFormBody::<MyObject, DEFAULT_URL_ENCODED_FORM_LIMIT>::new(&req, &mut pl)
468 .await;
469 assert_eq!(
470 form.ok().unwrap(),
471 MyObject {
472 name: "test".to_owned()
473 }
474 );
475 }
476
477 #[actix_web::test]
478 async fn test_with_form_and_bad_content_type() {
479 let (req, mut pl) = TestRequest::default()
480 .insert_header((
481 header::CONTENT_TYPE,
482 header::HeaderValue::from_static("text/plain"),
483 ))
484 .insert_header((
485 header::CONTENT_LENGTH,
486 header::HeaderValue::from_static("9"),
487 ))
488 .set_payload(Bytes::from_static(b"name=test"))
489 .to_http_parts();
490
491 let s = UrlEncodedForm::<MyObject, 4096>::from_request(&req, &mut pl).await;
492 assert!(s.is_err())
493 }
494
495 #[actix_web::test]
496 async fn test_with_config_in_data_wrapper() {
497 let (req, mut pl) = TestRequest::default()
498 .insert_header(header::ContentType::form_url_encoded())
499 .insert_header((header::CONTENT_LENGTH, 9))
500 .set_payload(Bytes::from_static(b"name=test"))
501 .to_http_parts();
502
503 let s = UrlEncodedForm::<MyObject, 8>::from_request(&req, &mut pl).await;
504 assert!(s.is_err());
505
506 let err_str = s.unwrap_err().to_string();
507 assert_eq!(
508 err_str,
509 "URL encoded payload is larger (9 bytes) than allowed (limit: 8 bytes).",
510 );
511 }
512}