web: add option to not require content type header

This commit is contained in:
Jake Heinz 2021-08-10 22:46:50 +00:00
parent f6e69919ed
commit c3814205c7
1 changed files with 34 additions and 9 deletions

View File

@ -146,12 +146,13 @@ where
let config = JsonConfig::from_req(req); let config = JsonConfig::from_req(req);
let limit = config.limit; let limit = config.limit;
let require_content_type = config.require_content_type;
let ctype = config.content_type.as_deref(); let ctype = config.content_type.as_deref();
let err_handler = config.err_handler.clone(); let err_handler = config.err_handler.clone();
JsonExtractFut { JsonExtractFut {
req: Some(req.clone()), req: Some(req.clone()),
fut: JsonBody::new(req, payload, ctype).limit(limit), fut: JsonBody::new(req, payload, ctype, require_content_type).limit(limit),
err_handler, err_handler,
} }
} }
@ -237,6 +238,7 @@ pub struct JsonConfig {
limit: usize, limit: usize,
err_handler: JsonErrorHandler, err_handler: JsonErrorHandler,
content_type: Option<Arc<dyn Fn(mime::Mime) -> bool + Send + Sync>>, content_type: Option<Arc<dyn Fn(mime::Mime) -> bool + Send + Sync>>,
require_content_type: bool,
} }
impl JsonConfig { impl JsonConfig {
@ -264,6 +266,12 @@ impl JsonConfig {
self self
} }
/// Allows requets without any `Content-Type` header to be parsed as Json.
pub fn require_content_type(mut self, require_content_type: bool) -> Self {
self.require_content_type = require_content_type;
self
}
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall /// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
/// back to the default payload config. /// back to the default payload config.
fn from_req(req: &HttpRequest) -> &Self { fn from_req(req: &HttpRequest) -> &Self {
@ -280,6 +288,7 @@ const DEFAULT_CONFIG: JsonConfig = JsonConfig {
limit: DEFAULT_LIMIT, limit: DEFAULT_LIMIT,
err_handler: None, err_handler: None,
content_type: None, content_type: None,
require_content_type: true,
}; };
impl Default for JsonConfig { impl Default for JsonConfig {
@ -321,17 +330,18 @@ where
req: &HttpRequest, req: &HttpRequest,
payload: &mut Payload, payload: &mut Payload,
ctype: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>, ctype: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>,
require_content_type: bool,
) -> Self { ) -> Self {
// check content-type // check content-type
let json = if let Ok(Some(mime)) = req.mime_type() { let has_valid_content_type = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON mime.subtype() == mime::JSON
|| mime.suffix() == Some(mime::JSON) || mime.suffix() == Some(mime::JSON)
|| ctype.map_or(false, |predicate| predicate(mime)) || ctype.map_or(false, |predicate| predicate(mime))
} else { } else {
false !require_content_type
}; };
if !json { if !has_valid_content_type {
return JsonBody::Error(Some(JsonPayloadError::ContentType)); return JsonBody::Error(Some(JsonPayloadError::ContentType));
} }
@ -581,7 +591,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_json_body() { async fn test_json_body() {
let (req, mut pl) = TestRequest::default().to_http_parts(); let (req, mut pl) = TestRequest::default().to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await; let json = JsonBody::<MyObject>::new(&req, &mut pl, None, false).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default() let (req, mut pl) = TestRequest::default()
@ -590,7 +600,7 @@ mod tests {
header::HeaderValue::from_static("application/text"), header::HeaderValue::from_static("application/text"),
)) ))
.to_http_parts(); .to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await; let json = JsonBody::<MyObject>::new(&req, &mut pl, None, false).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default() let (req, mut pl) = TestRequest::default()
@ -604,7 +614,7 @@ mod tests {
)) ))
.to_http_parts(); .to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None) let json = JsonBody::<MyObject>::new(&req, &mut pl, None, false)
.limit(100) .limit(100)
.await; .await;
assert!(json_eq( assert!(json_eq(
@ -623,7 +633,7 @@ mod tests {
.set_payload(Bytes::from_static(&[0u8; 1000])) .set_payload(Bytes::from_static(&[0u8; 1000]))
.to_http_parts(); .to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None) let json = JsonBody::<MyObject>::new(&req, &mut pl, None, false)
.limit(100) .limit(100)
.await; .await;
@ -644,7 +654,7 @@ mod tests {
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.to_http_parts(); .to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await; let json = JsonBody::<MyObject>::new(&req, &mut pl, None, false).await;
assert_eq!( assert_eq!(
json.ok().unwrap(), json.ok().unwrap(),
MyObject { MyObject {
@ -714,6 +724,21 @@ mod tests {
assert!(s.is_err()) assert!(s.is_err())
} }
#[actix_rt::test]
async fn test_json_with_no_content_type() {
let (req, mut pl) = TestRequest::default()
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.app_data(JsonConfig::default().require_content_type(false))
.to_http_parts();
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
assert!(s.is_ok())
}
#[actix_rt::test] #[actix_rt::test]
async fn test_with_config_in_data_wrapper() { async fn test_with_config_in_data_wrapper() {
let (req, mut pl) = TestRequest::default() let (req, mut pl) = TestRequest::default()