From ce5834ab8060742de992d8792f5be97e8cc3d9b0 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Fri, 8 Oct 2021 16:51:22 +0200 Subject: [PATCH] Expand accepted content types for JSON requests (#378) * Expand accepted content types for JSON requests Fixes https://github.com/tokio-rs/axum/issues/375 * changelog * add test for content type without spaces * Don't accept `text/json` * small clean up --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- src/json.rs | 33 +++++++++++++++++++++++++++++++-- src/tests/mod.rs | 28 ++++++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68e4b00c..023bb58c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased - Improve performance of `BoxRoute` ([#339]) +- Expand accepted content types for JSON requests ([#378]) - **breaking:** Automatically do percent decoding in `extract::Path` ([#272]) - **breaking:** `Router::boxed` now the inner service to implement `Clone` and @@ -19,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#339]: https://github.com/tokio-rs/axum/pull/339 [#286]: https://github.com/tokio-rs/axum/pull/286 [#272]: https://github.com/tokio-rs/axum/pull/272 +[#378]: https://github.com/tokio-rs/axum/pull/378 # 0.2.8 (07. October, 2021) diff --git a/Cargo.toml b/Cargo.toml index 682b92f2..6709a533 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = ["examples/*"] default = ["http1", "json", "tower-log"] http1 = ["hyper/http1"] http2 = ["hyper/http2"] -json = ["serde_json"] +json = ["serde_json", "mime"] multipart = ["multer", "mime"] tower-log = ["tower/log"] ws = ["tokio-tungstenite", "sha-1", "base64"] diff --git a/src/json.rs b/src/json.rs index 38d20593..11db3360 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,6 +1,6 @@ use crate::BoxError; use crate::{ - extract::{has_content_type, rejection::*, take_body, FromRequest, RequestParts}, + extract::{rejection::*, take_body, FromRequest, RequestParts}, response::IntoResponse, }; use async_trait::async_trait; @@ -103,7 +103,7 @@ where async fn from_request(req: &mut RequestParts) -> Result { use bytes::Buf; - if has_content_type(req, "application/json")? { + if json_content_type(req)? { let body = take_body(req)?; let buf = hyper::body::aggregate(body) @@ -119,6 +119,35 @@ where } } +fn json_content_type(req: &RequestParts) -> Result { + let content_type = if let Some(content_type) = req + .headers() + .ok_or(HeadersAlreadyExtracted)? + .get(header::CONTENT_TYPE) + { + content_type + } else { + return Ok(false); + }; + + let content_type = if let Ok(content_type) = content_type.to_str() { + content_type + } else { + return Ok(false); + }; + + let mime = if let Ok(mime) = content_type.parse::() { + mime + } else { + return Ok(false); + }; + + let is_json_content_type = mime.type_() == "application" + && (mime.subtype() == "json" || mime.suffix().filter(|name| *name == "json").is_some()); + + Ok(is_json_content_type) +} + impl Deref for Json { type Target = T; diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 6a8f0255..5d8d1516 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -6,7 +6,7 @@ use crate::{ handler::{any, delete, get, on, patch, post, Handler}, response::IntoResponse, routing::MethodFilter, - service, Router, + service, Json, Router, }; use bytes::Bytes; use http::{ @@ -15,7 +15,7 @@ use http::{ }; use hyper::Body; use serde::Deserialize; -use serde_json::json; +use serde_json::{json, Value}; use std::future::Ready; use std::{ collections::HashMap, @@ -515,6 +515,30 @@ async fn captures_dont_match_empty_segments() { assert_eq!(res.status(), StatusCode::OK); } +#[tokio::test] +async fn json_content_types() { + async fn valid_json_content_type(content_type: &str) -> bool { + println!("testing {:?}", content_type); + + let app = Router::new().route("/", post(|Json(_): Json| async {})); + + let res = TestClient::new(app) + .post("/") + .header("content-type", content_type) + .body("{}") + .send() + .await; + + res.status() == StatusCode::OK + } + + assert!(valid_json_content_type("application/json").await); + assert!(valid_json_content_type("application/json; charset=utf-8").await); + assert!(valid_json_content_type("application/json;charset=utf-8").await); + assert!(valid_json_content_type("application/cloudevents+json").await); + assert!(!valid_json_content_type("text/json").await); +} + pub(crate) fn assert_send() {} pub(crate) fn assert_sync() {} pub(crate) fn assert_unpin() {}