diff --git a/CHANGES.md b/CHANGES.md index 14400add..1794a812 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,8 @@ * Allow to test an app that uses async actors #897 +* Re-apply patch from #637 #894 + ## [1.0.0] - 2019-06-05 diff --git a/actix-identity/Cargo.toml b/actix-identity/Cargo.toml index 3b1b9086..e645275a 100644 --- a/actix-identity/Cargo.toml +++ b/actix-identity/Cargo.toml @@ -27,3 +27,4 @@ time = "0.1.42" [dev-dependencies] actix-rt = "0.2.2" actix-http = "0.2.3" +bytes = "0.4" \ No newline at end of file diff --git a/src/types/json.rs b/src/types/json.rs index 4e827942..0789fb61 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -175,15 +175,15 @@ where #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req2 = req.clone(); - let (limit, err) = req + let (limit, err, ctype) = req .app_data::() - .map(|c| (c.limit, c.ehandler.clone())) - .unwrap_or((32768, None)); + .map(|c| (c.limit, c.ehandler.clone(), c.content_type.clone())) + .unwrap_or((32768, None, None)); let path = req.path().to_string(); Box::new( - JsonBody::new(req, payload) + JsonBody::new(req, payload, ctype) .limit(limit) .map_err(move |e| { log::debug!( @@ -224,6 +224,9 @@ where /// // change json extractor configuration /// web::Json::::configure(|cfg| { /// cfg.limit(4096) +/// .content_type(|mime| { // <- accept text/plain content type +/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN +/// }) /// .error_handler(|err, req| { // <- create custom error response /// error::InternalError::from_response( /// err, HttpResponse::Conflict().finish()).into() @@ -237,6 +240,7 @@ where pub struct JsonConfig { limit: usize, ehandler: Option Error + Send + Sync>>, + content_type: Option bool + Send + Sync>>, } impl JsonConfig { @@ -254,6 +258,15 @@ impl JsonConfig { self.ehandler = Some(Arc::new(f)); self } + + /// Set predicate for allowed content types + pub fn content_type(mut self, predicate: F) -> Self + where + F: Fn(mime::Mime) -> bool + Send + Sync + 'static, + { + self.content_type = Some(Arc::new(predicate)); + self + } } impl Default for JsonConfig { @@ -261,6 +274,7 @@ impl Default for JsonConfig { JsonConfig { limit: 32768, ehandler: None, + content_type: None, } } } @@ -271,6 +285,7 @@ impl Default for JsonConfig { /// Returns error: /// /// * content type is not `application/json` +/// (unless specified in [`JsonConfig`](struct.JsonConfig.html)) /// * content length is greater than 256k pub struct JsonBody { limit: usize, @@ -285,13 +300,20 @@ where U: DeserializeOwned + 'static, { /// Create `JsonBody` for request. - pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self { + pub fn new( + req: &HttpRequest, + payload: &mut Payload, + ctype: Option bool + Send + Sync>>, + ) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { - mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) + mime.subtype() == mime::JSON + || mime.suffix() == Some(mime::JSON) + || ctype.as_ref().map_or(false, |predicate| predicate(mime)) } else { false }; + if !json { return JsonBody { limit: 262_144, @@ -512,7 +534,7 @@ mod tests { #[test] fn test_json_body() { let (req, mut pl) = TestRequest::default().to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl)); + let json = block_on(JsonBody::::new(&req, &mut pl, None)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() @@ -521,7 +543,7 @@ mod tests { header::HeaderValue::from_static("application/text"), ) .to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl)); + let json = block_on(JsonBody::::new(&req, &mut pl, None)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() @@ -535,7 +557,7 @@ mod tests { ) .to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl).limit(100)); + let json = block_on(JsonBody::::new(&req, &mut pl, None).limit(100)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); let (req, mut pl) = TestRequest::default() @@ -550,7 +572,7 @@ mod tests { .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl)); + let json = block_on(JsonBody::::new(&req, &mut pl, None)); assert_eq!( json.ok().unwrap(), MyObject { @@ -558,4 +580,62 @@ mod tests { } ); } + + #[test] + fn test_with_json_and_bad_content_type() { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().limit(4096)) + .to_http_parts(); + + let s = block_on(Json::::from_request(&req, &mut pl)); + assert!(s.is_err()) + } + + #[test] + fn test_with_json_and_good_custom_content_type() { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); + + let s = block_on(Json::::from_request(&req, &mut pl)); + assert!(s.is_ok()) + } + + #[test] + fn test_with_json_and_bad_custom_content_type() { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/html"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); + + let s = block_on(Json::::from_request(&req, &mut pl)); + assert!(s.is_err()) + } }