Repository: actix/actix-web (language: rust)

Issue:
Setting `JsonConfig::content_type_required(false)` results in weird behavior
When spinning up the following server

```rs
#[derive(Debug, Serialize, Deserialize)]
struct MyObj {
    name: String,
    number: i32,
}

/// This handler uses json extractor
async fn index(item: web::Json<MyObj>) -> HttpResponse {
    println!("model: {:?}", &item);
    HttpResponse::Ok().json(item.0) // <- send response
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("trace"));
    info!("starting HTTP server at http://localhost:8080");

    HttpServer::new(|| {
        App::new().wrap(middleware::Logger::default()).service(
            web::resource("/")
                .route(web::post().to(index))
                .app_data(web::JsonConfig::default().content_type_required(false)),
        )
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}
```

We get the following outputs when running requests against the server 

```bash
$ curl -d '{"name": "biz", "number": 2}' localhost:8080
Content type error # fails

$ curl -H 'Content-Type: muhahah' -d '{"name": "biz", "number": 2}' localhost:8080
{"name":"biz","number":2} # works

$ curl -H 'Content-Type: muhah/ah' -d '{"name": "biz", "number": 2}' localhost:8080
Content type error # fails
```

It seems like the server still tries to parse the content-type header even though the API seems to be telling it to ignore it 


Discussion / hints:
I would like to work on the fix if possible.

Fix patch:
diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md
index 50728a839f0..0079a412057 100644
--- a/actix-web/CHANGES.md
+++ b/actix-web/CHANGES.md
@@ -6,6 +6,10 @@
 
 - Updated `zstd` dependency to `0.13`.
 
+### Fixed
+
+- Fix validation of `Json` extractor when `JsonConfig::validate_content_type()` is set to false.
+
 ## 4.4.0
 
 ### Added
diff --git a/actix-web/src/types/json.rs b/actix-web/src/types/json.rs
index 59c95da4cb8..6b75c0cfe81 100644
--- a/actix-web/src/types/json.rs
+++ b/actix-web/src/types/json.rs
@@ -328,14 +328,19 @@ impl<T: DeserializeOwned> JsonBody<T> {
         ctype_required: bool,
     ) -> Self {
         // check content-type
-        let can_parse_json = if let Ok(Some(mime)) = req.mime_type() {
-            mime.subtype() == mime::JSON
-                || mime.suffix() == Some(mime::JSON)
-                || ctype_fn.map_or(false, |predicate| predicate(mime))
-        } else {
-            // if `ctype_required` is false, assume payload is
-            // json even when content-type header is missing
-            !ctype_required
+        let can_parse_json = match (ctype_required, req.mime_type()) {
+            (true, Ok(Some(mime))) => {
+                mime.subtype() == mime::JSON
+                    || mime.suffix() == Some(mime::JSON)
+                    || ctype_fn.map_or(false, |predicate| predicate(mime))
+            }
+
+            // if content-type is expected but not parsable as mime type, bail
+            (true, _) => false,
+
+            // if content-type validation is disabled, assume payload is JSON
+            // even when content-type header is missing or invalid mime type
+            (false, _) => true,
         };
 
         if !can_parse_json {
@@ -725,6 +730,25 @@ mod tests {
         assert!(s.is_ok())
     }
 
+    #[actix_rt::test]
+    async fn test_json_ignoring_content_type() {
+        let (req, mut pl) = TestRequest::default()
+            .insert_header((
+                header::CONTENT_LENGTH,
+                header::HeaderValue::from_static("16"),
+            ))
+            .insert_header((
+                header::CONTENT_TYPE,
+                header::HeaderValue::from_static("invalid/value"),
+            ))
+            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
+            .app_data(JsonConfig::default().content_type_required(false))
+            .to_http_parts();
+
+        let s = Json::<MyObject>::from_request(&req, &mut pl).await;
+        assert!(s.is_ok());
+    }
+
     #[actix_rt::test]
     async fn test_with_config_in_data_wrapper() {
         let (req, mut pl) = TestRequest::default()


Test patch: