diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 5775c17d344..65691195a77 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,10 @@ ## Unreleased +- Update `HttpMessage::content_type()` to return `Option<&str>`. [#3797] + +[#3797]: https://github.com/actix/actix-web/issues/3797 + ## 3.13.5 - Reject invalid WebSocket close-frame status codes, malformed payloads, and invalid UTF-8 close reasons. diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index 2800f40baf9..2c59d0f6c51 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -44,15 +44,15 @@ pub trait HttpMessage: Sized { } } - /// Read the request content type. If request did not contain a *Content-Type* header, an empty - /// string is returned. - fn content_type(&self) -> &str { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return content_type.split(';').next().unwrap().trim(); - } - } - "" + /// Read the request content type. + /// + /// If request does not contain a *Content-Type* header, or if the header value is not valid + /// UTF-8, `None` is returned. + fn content_type(&self) -> Option<&str> { + self.headers() + .get(header::CONTENT_TYPE) + .and_then(|ct| ct.to_str().ok()) + .map(|ct| ct.split_once(';').map_or(ct, |(ct, _)| ct).trim()) } /// Get content type encoding. @@ -142,13 +142,20 @@ mod tests { let req = TestRequest::default() .insert_header(("content-type", "text/plain")) .finish(); - assert_eq!(req.content_type(), "text/plain"); + assert_eq!(req.content_type(), Some("text/plain")); let req = TestRequest::default() .insert_header(("content-type", "application/json; charset=utf-8")) .finish(); - assert_eq!(req.content_type(), "application/json"); + assert_eq!(req.content_type(), Some("application/json")); let req = TestRequest::default().finish(); - assert_eq!(req.content_type(), ""); + assert_eq!(req.content_type(), None); + let req = TestRequest::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_bytes(b"\xff").unwrap(), + )) + .finish(); + assert_eq!(req.content_type(), None); } #[test] diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 2f097547dae..67b28d82885 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,6 +2,10 @@ ## Unreleased +- Update `HttpMessage::content_type()` (and `HttpRequest` / `ServiceRequest` implementations) to return `Option<&str>`. [#3797] + +[#3797]: https://github.com/actix/actix-web/issues/3797 + ## 4.15.0 - Add `Error::add_response_mapper()` to allow middleware to modify error-generated responses before they are sent. diff --git a/actix-web/src/test/test_utils.rs b/actix-web/src/test/test_utils.rs index 4540d8a6b61..449fc4cc782 100644 --- a/actix-web/src/test/test_utils.rs +++ b/actix-web/src/test/test_utils.rs @@ -505,7 +505,10 @@ mod tests { .set_form(&payload) .to_request(); - assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); + assert_eq!( + req.content_type(), + Some("application/x-www-form-urlencoded") + ); let result: Person = call_and_read_body_json(&app, req).await; assert_eq!(&result.id, "12345"); @@ -549,7 +552,7 @@ mod tests { .set_json(&payload) .to_request(); - assert_eq!(req.content_type(), "application/json"); + assert_eq!(req.content_type(), Some("application/json")); let result: Person = call_and_read_body_json(&app, req).await; assert_eq!(&result.id, "12345"); diff --git a/actix-web/src/types/form.rs b/actix-web/src/types/form.rs index d6381b99017..e28b636b133 100644 --- a/actix-web/src/types/form.rs +++ b/actix-web/src/types/form.rs @@ -289,7 +289,10 @@ impl UrlEncoded { /// Create a new future to decode a URL encoded request payload. pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self { // check content type - if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { + if !req + .content_type() + .is_some_and(|ct| ct.eq_ignore_ascii_case("application/x-www-form-urlencoded")) + { return Self::err(UrlencodedError::ContentType); } let encoding = match req.encoding() { @@ -484,6 +487,12 @@ mod tests { .to_http_parts(); let info = UrlEncoded::::new(&req, &mut pl).await; assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); + + let (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_LENGTH, 10)) + .to_http_parts(); + let info = UrlEncoded::::new(&req, &mut pl).await; + assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); } #[actix_rt::test] @@ -520,6 +529,21 @@ mod tests { counter: 123 } ); + + let (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "APPLICATION/X-WWW-FORM-URLENCODED")) + .insert_header((CONTENT_LENGTH, 11)) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); + + let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); + assert_eq!( + info, + Info { + hello: "world".to_owned(), + counter: 123 + } + ); } #[actix_rt::test]