use axum::body::Body;
use axum::extract::FromRequest;
use http::{Request, header};
use serde::de::DeserializeOwned;
use crate::sanitize::Sanitize;
pub struct FormRequest<T>(pub T);
impl<S, T> FromRequest<S> for FormRequest<T>
where
S: Send + Sync,
T: DeserializeOwned + Sanitize,
{
type Rejection = crate::error::Error;
async fn from_request(req: Request<Body>, state: &S) -> Result<Self, Self::Rejection> {
if !has_form_content_type(&req) {
return Err(crate::error::Error::bad_request(
"expected `application/x-www-form-urlencoded` content type",
));
}
let bytes = axum::body::Bytes::from_request(req, state)
.await
.map_err(|e| {
crate::error::Error::new(e.status(), format!("failed to read body: {e}"))
})?;
let mut value: T = serde_qs::Config::new()
.use_form_encoding(true)
.deserialize_bytes(&bytes)
.map_err(|e| crate::error::Error::bad_request(format!("invalid form data: {e}")))?;
value.sanitize();
Ok(FormRequest(value))
}
}
fn has_form_content_type<B>(req: &Request<B>) -> bool {
let Some(value) = req.headers().get(header::CONTENT_TYPE) else {
return false;
};
let Ok(text) = value.to_str() else {
return false;
};
let mime_type = text.split(';').next().unwrap_or("").trim();
mime_type.eq_ignore_ascii_case("application/x-www-form-urlencoded")
}