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
This commit is contained in:
David Pedersen 2021-10-08 16:51:22 +02:00 committed by GitHub
parent fe4c0ae386
commit ce5834ab80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 5 deletions

View file

@ -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)

View file

@ -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"]

View file

@ -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<B>) -> Result<Self, Self::Rejection> {
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<B>(req: &RequestParts<B>) -> Result<bool, HeadersAlreadyExtracted> {
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::Mime>() {
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<T> Deref for Json<T> {
type Target = T;

View file

@ -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<Value>| 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<T: Send>() {}
pub(crate) fn assert_sync<T: Sync>() {}
pub(crate) fn assert_unpin<T: Unpin>() {}