1use std::{
4 future::Future,
5 marker::PhantomData,
6 pin::Pin,
7 task::{ready, Context, Poll},
8};
9
10use actix_web::{
11 dev::Payload, error::UrlencodedError, http::header, web, Error, FromRequest, HttpMessage,
12 HttpRequest,
13};
14use futures_core::Stream as _;
15use serde::de::DeserializeOwned;
16use tracing::debug;
17
18pub const DEFAULT_URL_ENCODED_FORM_LIMIT: usize = 2_097_152;
20
21#[doc(alias = "html_form", alias = "html form", alias = "form")]
57#[derive(Debug)]
58pub struct UrlEncodedForm<T, const LIMIT: usize = DEFAULT_URL_ENCODED_FORM_LIMIT>(pub T);
60
61mod waiting_on_derive_more_to_start_using_syn_2_due_to_proc_macro_panic {
62 use super::*;
63
64 impl<T, const LIMIT: usize> std::ops::Deref for UrlEncodedForm<T, LIMIT> {
65 type Target = T;
66
67 fn deref(&self) -> &Self::Target {
68 &self.0
69 }
70 }
71
72 impl<T, const LIMIT: usize> std::ops::DerefMut for UrlEncodedForm<T, LIMIT> {
73 fn deref_mut(&mut self) -> &mut Self::Target {
74 &mut self.0
75 }
76 }
77
78 impl<T: std::fmt::Display, const LIMIT: usize> std::fmt::Display for UrlEncodedForm<T, LIMIT> {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 std::fmt::Display::fmt(&self.0, f)
81 }
82 }
83}
84
85impl<T, const LIMIT: usize> UrlEncodedForm<T, LIMIT> {
86 pub fn into_inner(self) -> T {
88 self.0
89 }
90}
91
92impl<T: DeserializeOwned, const LIMIT: usize> FromRequest for UrlEncodedForm<T, LIMIT> {
94 type Error = Error;
95 type Future = UrlEncodedFormExtractFut<T, LIMIT>;
96
97 #[inline]
98 fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
99 UrlEncodedFormExtractFut {
100 req: Some(req.clone()),
101 fut: UrlEncodedFormBody::new(req, payload),
102 }
103 }
104}
105
106pub struct UrlEncodedFormExtractFut<T, const LIMIT: usize> {
107 req: Option<HttpRequest>,
108 fut: UrlEncodedFormBody<T, LIMIT>,
109}
110
111impl<T: DeserializeOwned, const LIMIT: usize> Future for UrlEncodedFormExtractFut<T, LIMIT> {
112 type Output = Result<UrlEncodedForm<T, LIMIT>, Error>;
113
114 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
115 let this = self.get_mut();
116
117 let res = ready!(Pin::new(&mut this.fut).poll(cx));
118
119 let res = match res {
120 Err(err) => {
121 let req = this.req.take().unwrap();
122 debug!(
123 "Failed to deserialize UrlEncodedForm<{}> from payload in handler: {}",
124 core::any::type_name::<T>(),
125 req.match_name().unwrap_or_else(|| req.path())
126 );
127
128 Err(err.into())
129 }
130 Ok(data) => Ok(UrlEncodedForm(data)),
131 };
132
133 Poll::Ready(res)
134 }
135}
136
137pub enum UrlEncodedFormBody<T, const LIMIT: usize> {
146 Error(Option<UrlencodedError>),
147 Body {
148 length: Option<usize>,
150 payload: Payload,
151 buf: web::BytesMut,
152 _res: PhantomData<T>,
153 },
154}
155
156impl<T, const LIMIT: usize> Unpin for UrlEncodedFormBody<T, LIMIT> {}
157
158impl<T: DeserializeOwned, const LIMIT: usize> UrlEncodedFormBody<T, LIMIT> {
159 pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self {
161 let can_parse_form = if let Ok(Some(mime)) = req.mime_type() {
163 mime == mime::APPLICATION_WWW_FORM_URLENCODED
164 } else {
165 false
166 };
167
168 if !can_parse_form {
169 return UrlEncodedFormBody::Error(Some(UrlencodedError::ContentType));
170 }
171
172 let length = req
173 .headers()
174 .get(&header::CONTENT_LENGTH)
175 .and_then(|l| l.to_str().ok())
176 .and_then(|s| s.parse::<usize>().ok());
177
178 let payload = payload.take();
183
184 if let Some(len) = length {
185 if len > LIMIT {
186 return UrlEncodedFormBody::Error(Some(UrlencodedError::Overflow {
187 size: len,
188 limit: LIMIT,
189 }));
190 }
191 }
192
193 UrlEncodedFormBody::Body {
194 length,
195 payload,
196 buf: web::BytesMut::with_capacity(8192),
197 _res: PhantomData,
198 }
199 }
200}
201
202impl<T: DeserializeOwned, const LIMIT: usize> Future for UrlEncodedFormBody<T, LIMIT> {
203 type Output = Result<T, UrlencodedError>;
204
205 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
206 let this = self.get_mut();
207
208 match this {
209 UrlEncodedFormBody::Body { buf, payload, .. } => loop {
210 let res = ready!(Pin::new(&mut *payload).poll_next(cx));
211
212 match res {
213 Some(chunk) => {
214 let chunk = chunk?;
215 let buf_len = buf.len() + chunk.len();
216 if buf_len > LIMIT {
217 return Poll::Ready(Err(UrlencodedError::Overflow {
218 size: buf_len,
219 limit: LIMIT,
220 }));
221 } else {
222 buf.extend_from_slice(&chunk);
223 }
224 }
225
226 None => {
227 let form = serde_html_form::from_bytes::<T>(buf)
228 .map_err(UrlencodedError::Parse)?;
229 return Poll::Ready(Ok(form));
230 }
231 }
232 },
233
234 UrlEncodedFormBody::Error(e) => Poll::Ready(Err(e.take().unwrap())),
235 }
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use actix_web::{http::header, test::TestRequest, web::Bytes};
242 use serde::{Deserialize, Serialize};
243
244 use super::*;
245
246 #[derive(Serialize, Deserialize, PartialEq, Debug)]
247 struct MyObject {
248 name: String,
249 }
250
251 fn err_eq(err: UrlencodedError, other: UrlencodedError) -> bool {
252 match err {
253 UrlencodedError::Overflow { .. } => {
254 matches!(other, UrlencodedError::Overflow { .. })
255 }
256
257 UrlencodedError::ContentType => matches!(other, UrlencodedError::ContentType),
258
259 _ => false,
260 }
261 }
262
263 #[actix_web::test]
264 async fn test_extract() {
265 let (req, mut pl) = TestRequest::default()
266 .insert_header(header::ContentType::form_url_encoded())
267 .insert_header((
268 header::CONTENT_LENGTH,
269 header::HeaderValue::from_static("9"),
270 ))
271 .set_payload(Bytes::from_static(b"name=test"))
272 .to_http_parts();
273
274 let s =
275 UrlEncodedForm::<MyObject, DEFAULT_URL_ENCODED_FORM_LIMIT>::from_request(&req, &mut pl)
276 .await
277 .unwrap();
278 assert_eq!(s.name, "test");
279 assert_eq!(
280 s.into_inner(),
281 MyObject {
282 name: "test".to_string()
283 }
284 );
285
286 let (req, mut pl) = TestRequest::default()
287 .insert_header(header::ContentType::form_url_encoded())
288 .insert_header((
289 header::CONTENT_LENGTH,
290 header::HeaderValue::from_static("9"),
291 ))
292 .set_payload(Bytes::from_static(b"name=test"))
293 .to_http_parts();
294
295 let s = UrlEncodedForm::<MyObject, 8>::from_request(&req, &mut pl).await;
296 let err = format!("{}", s.unwrap_err());
297 assert_eq!(
298 err,
299 "URL encoded payload is larger (9 bytes) than allowed (limit: 8 bytes).",
300 );
301
302 let (req, mut pl) = TestRequest::default()
303 .insert_header(header::ContentType::form_url_encoded())
304 .insert_header((
305 header::CONTENT_LENGTH,
306 header::HeaderValue::from_static("9"),
307 ))
308 .set_payload(Bytes::from_static(b"name=test"))
309 .to_http_parts();
310 let s = UrlEncodedForm::<MyObject, 8>::from_request(&req, &mut pl).await;
311 let err = format!("{}", s.unwrap_err());
312 assert!(
313 err.contains("payload is larger") && err.contains("than allowed"),
314 "unexpected error string: {err:?}"
315 );
316 }
317
318 #[actix_web::test]
319 async fn test_form_body() {
320 let (req, mut pl) = TestRequest::default().to_http_parts();
321 let form =
322 UrlEncodedFormBody::<MyObject, DEFAULT_URL_ENCODED_FORM_LIMIT>::new(&req, &mut pl)
323 .await;
324 assert!(err_eq(form.unwrap_err(), UrlencodedError::ContentType));
325
326 let (req, mut pl) = TestRequest::default()
327 .insert_header((
328 header::CONTENT_TYPE,
329 header::HeaderValue::from_static("application/text"),
330 ))
331 .to_http_parts();
332 let form =
333 UrlEncodedFormBody::<MyObject, DEFAULT_URL_ENCODED_FORM_LIMIT>::new(&req, &mut pl)
334 .await;
335 assert!(err_eq(form.unwrap_err(), UrlencodedError::ContentType));
336
337 let (req, mut pl) = TestRequest::default()
338 .insert_header(header::ContentType::form_url_encoded())
339 .insert_header((
340 header::CONTENT_LENGTH,
341 header::HeaderValue::from_static("10000"),
342 ))
343 .to_http_parts();
344
345 let form = UrlEncodedFormBody::<MyObject, 100>::new(&req, &mut pl).await;
346 assert!(err_eq(
347 form.unwrap_err(),
348 UrlencodedError::Overflow {
349 size: 10000,
350 limit: 100
351 }
352 ));
353
354 let (req, mut pl) = TestRequest::default()
355 .insert_header(header::ContentType::form_url_encoded())
356 .set_payload(Bytes::from_static(&[0u8; 1000]))
357 .to_http_parts();
358
359 let form = UrlEncodedFormBody::<MyObject, 100>::new(&req, &mut pl).await;
360
361 assert!(err_eq(
362 form.unwrap_err(),
363 UrlencodedError::Overflow {
364 size: 1000,
365 limit: 100
366 }
367 ));
368
369 let (req, mut pl) = TestRequest::default()
370 .insert_header(header::ContentType::form_url_encoded())
371 .insert_header((
372 header::CONTENT_LENGTH,
373 header::HeaderValue::from_static("9"),
374 ))
375 .set_payload(Bytes::from_static(b"name=test"))
376 .to_http_parts();
377
378 let form =
379 UrlEncodedFormBody::<MyObject, DEFAULT_URL_ENCODED_FORM_LIMIT>::new(&req, &mut pl)
380 .await;
381 assert_eq!(
382 form.ok().unwrap(),
383 MyObject {
384 name: "test".to_owned()
385 }
386 );
387 }
388
389 #[actix_web::test]
390 async fn test_with_form_and_bad_content_type() {
391 let (req, mut pl) = TestRequest::default()
392 .insert_header((
393 header::CONTENT_TYPE,
394 header::HeaderValue::from_static("text/plain"),
395 ))
396 .insert_header((
397 header::CONTENT_LENGTH,
398 header::HeaderValue::from_static("9"),
399 ))
400 .set_payload(Bytes::from_static(b"name=test"))
401 .to_http_parts();
402
403 let s = UrlEncodedForm::<MyObject, 4096>::from_request(&req, &mut pl).await;
404 assert!(s.is_err())
405 }
406
407 #[actix_web::test]
408 async fn test_with_config_in_data_wrapper() {
409 let (req, mut pl) = TestRequest::default()
410 .insert_header(header::ContentType::form_url_encoded())
411 .insert_header((header::CONTENT_LENGTH, 9))
412 .set_payload(Bytes::from_static(b"name=test"))
413 .to_http_parts();
414
415 let s = UrlEncodedForm::<MyObject, 8>::from_request(&req, &mut pl).await;
416 assert!(s.is_err());
417
418 let err_str = s.unwrap_err().to_string();
419 assert_eq!(
420 err_str,
421 "URL encoded payload is larger (9 bytes) than allowed (limit: 8 bytes).",
422 );
423 }
424}