From 36ddb2a8fc284f9119b7b2c2d3383b5ff351ad22 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Wed, 10 Jan 2024 15:41:52 +0100 Subject: [PATCH 01/17] Implement, test and export Scheme extractor --- axum/src/extract/mod.rs | 2 + axum/src/extract/rejection.rs | 18 +++++ axum/src/extract/scheme.rs | 144 ++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 axum/src/extract/scheme.rs diff --git a/axum/src/extract/mod.rs b/axum/src/extract/mod.rs index c02bc6f0a6..06181d0d3c 100644 --- a/axum/src/extract/mod.rs +++ b/axum/src/extract/mod.rs @@ -15,6 +15,7 @@ pub(crate) mod nested_path; mod raw_form; mod raw_query; mod request_parts; +mod scheme; mod state; #[doc(inline)] @@ -31,6 +32,7 @@ pub use self::{ path::{Path, RawPathParams}, raw_form::RawForm, raw_query::RawQuery, + scheme::Scheme, state::State, }; diff --git a/axum/src/extract/rejection.rs b/axum/src/extract/rejection.rs index cba76af054..355264ef74 100644 --- a/axum/src/extract/rejection.rs +++ b/axum/src/extract/rejection.rs @@ -73,6 +73,14 @@ define_rejection! { pub struct FailedToResolveHost; } +define_rejection! { + #[status = BAD_REQUEST] + #[body = "No scheme found in request"] + /// Rejection type used if the [`Scheme`](super::Scheme) extractor is unable to + /// resolve a host. + pub struct FailedToResolveScheme; +} + define_rejection! { #[status = BAD_REQUEST] #[body = "Failed to deserialize form"] @@ -188,6 +196,16 @@ composite_rejection! { } } +composite_rejection! { + /// Rejection used for [`Scheme`](super::Scheme). + /// + /// Contains one variant for each way the [`Scheme`](super::Scheme) extractor + /// can fail. + pub enum SchemeRejection { + FailedToResolveScheme, + } +} + #[cfg(feature = "matched-path")] define_rejection! { #[status = INTERNAL_SERVER_ERROR] diff --git a/axum/src/extract/scheme.rs b/axum/src/extract/scheme.rs new file mode 100644 index 0000000000..73cd6fcc8c --- /dev/null +++ b/axum/src/extract/scheme.rs @@ -0,0 +1,144 @@ +use super::{ + rejection::{FailedToResolveScheme, SchemeRejection}, + FromRequestParts, +}; +use async_trait::async_trait; +use http::{ + header::{HeaderMap, FORWARDED}, + request::Parts, +}; +const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto"; + +/// Extractor that resolves the hostname of the request. +/// +/// Hostname is resolved through the following, in order: +/// - `Forwarded` header +/// - `X-Forwarded-Proto` header +/// - request target / URI +/// +/// Note that user agents can set the `X-Forwarded-Scheme` header to arbitrary values so make +/// sure to validate them to avoid security issues. +#[derive(Debug, Clone)] +pub struct Scheme(pub String); + +#[async_trait] +impl FromRequestParts for Scheme +where + S: Send + Sync, +{ + type Rejection = SchemeRejection; + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + // Within Forwarded header + if let Some(scheme) = parse_forwarded(&parts.headers) { + return Ok(Scheme(scheme.to_owned())); + } + + // X-Forwarded-Proto + if let Some(scheme) = parts + .headers + .get(X_FORWARDED_PROTO_HEADER_KEY) + .and_then(|host| host.to_str().ok()) + { + return Ok(Scheme(scheme.to_owned())); + } + + // From parts + if let Some(scheme) = parts.uri.scheme_str() { + return Ok(Scheme(scheme.to_owned())); + } + + Err(SchemeRejection::FailedToResolveScheme( + FailedToResolveScheme, + )) + } +} + +fn parse_forwarded(headers: &HeaderMap) -> Option<&str> { + // if there are multiple `Forwarded` `HeaderMap::get` will return the first one + let forwarded_values = headers.get(FORWARDED)?.to_str().ok()?; + + // get the first set of values + let first_value = forwarded_values.split(',').next()?; + + // find the value of the `proto` field + first_value.split(';').find_map(|pair| { + let (key, value) = pair.split_once('=')?; + key.trim() + .eq_ignore_ascii_case("proto") + .then(|| value.trim().trim_matches('"')) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{routing::get, test_helpers::TestClient, Router}; + use http::header::HeaderName; + + fn test_client() -> TestClient { + async fn scheme_as_body(Scheme(scheme): Scheme) -> String { + scheme + } + + TestClient::new(Router::new().route("/", get(scheme_as_body))) + } + + #[test] + fn forwarded_scheme_parsing() { + // the basic case + let headers = header_map(&[(FORWARDED, "host=192.0.2.60;proto=http;by=203.0.113.43")]); + let value = parse_forwarded(&headers).unwrap(); + assert_eq!(value, "http"); + + // is case insensitive + let headers = header_map(&[(FORWARDED, "host=192.0.2.60;PROTO=https;by=203.0.113.43")]); + let value = parse_forwarded(&headers).unwrap(); + assert_eq!(value, "https"); + + // multiple values in one header + let headers = header_map(&[(FORWARDED, "proto=ftp, proto=https")]); + let value = parse_forwarded(&headers).unwrap(); + assert_eq!(value, "ftp"); + + // multiple header values + let headers = header_map(&[ + (FORWARDED, "proto=ftp"), + (FORWARDED, "proto=https"), + ]); + let value = parse_forwarded(&headers).unwrap(); + assert_eq!(value, "ftp"); + } + + #[crate::test] + async fn x_forwarded_scheme_header() { + let original_scheme = "https"; + let scheme = test_client() + .get("/") + .header(X_FORWARDED_PROTO_HEADER_KEY, original_scheme) + .await + .text() + .await; + assert_eq!(scheme, original_scheme); + } + + #[crate::test] + async fn precedence_forwarded_over_x_forwarded() { + let scheme = test_client() + .get("/") + .header(X_FORWARDED_PROTO_HEADER_KEY, "https") + .header(FORWARDED, "proto=ftp") + .await + .text() + .await; + assert_eq!(scheme, "ftp"); + } + + fn header_map(values: &[(HeaderName, &str)]) -> HeaderMap { + let mut headers = HeaderMap::new(); + for (key, value) in values { + headers.append(key, value.parse().unwrap()); + } + headers + } +} From acaa0a8fbb756c62f30f2575365614c14a5b5831 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Thu, 11 Jan 2024 12:24:25 +0100 Subject: [PATCH 02/17] Correct duplicate test --- axum/src/extract/host.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum/src/extract/host.rs b/axum/src/extract/host.rs index f1d179a545..e1cb480fb6 100644 --- a/axum/src/extract/host.rs +++ b/axum/src/extract/host.rs @@ -142,7 +142,7 @@ mod tests { assert_eq!(value, "192.0.2.60"); // is case insensitive - let headers = header_map(&[(FORWARDED, "host=192.0.2.60;proto=http;by=203.0.113.43")]); + let headers = header_map(&[(FORWARDED, "HOST=192.0.2.60;proto=http;by=203.0.113.43")]); let value = parse_forwarded(&headers).unwrap(); assert_eq!(value, "192.0.2.60"); From 0aa577eb1fbb8b7dfaf1878b6b64207afb882b61 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Thu, 11 Jan 2024 12:32:33 +0100 Subject: [PATCH 03/17] Documentation and naming tweaks --- axum/src/extract/scheme.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/axum/src/extract/scheme.rs b/axum/src/extract/scheme.rs index 73cd6fcc8c..3471802fec 100644 --- a/axum/src/extract/scheme.rs +++ b/axum/src/extract/scheme.rs @@ -9,9 +9,9 @@ use http::{ }; const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto"; -/// Extractor that resolves the hostname of the request. +/// Extractor that resolves the scheme / protocol of the request. /// -/// Hostname is resolved through the following, in order: +/// The scheme is resolved through the following, in order: /// - `Forwarded` header /// - `X-Forwarded-Proto` header /// - request target / URI @@ -38,7 +38,7 @@ where if let Some(scheme) = parts .headers .get(X_FORWARDED_PROTO_HEADER_KEY) - .and_then(|host| host.to_str().ok()) + .and_then(|scheme| scheme.to_str().ok()) { return Ok(Scheme(scheme.to_owned())); } From 285a6f09d7e6152a8e8dd7364061c2e287d1a780 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 22 Jan 2024 10:27:55 +0100 Subject: [PATCH 04/17] Fix formatting issues --- axum/src/extract/scheme.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/axum/src/extract/scheme.rs b/axum/src/extract/scheme.rs index 3471802fec..81c678c933 100644 --- a/axum/src/extract/scheme.rs +++ b/axum/src/extract/scheme.rs @@ -102,10 +102,7 @@ mod tests { assert_eq!(value, "ftp"); // multiple header values - let headers = header_map(&[ - (FORWARDED, "proto=ftp"), - (FORWARDED, "proto=https"), - ]); + let headers = header_map(&[(FORWARDED, "proto=ftp"), (FORWARDED, "proto=https")]); let value = parse_forwarded(&headers).unwrap(); assert_eq!(value, "ftp"); } From 40858f740628ed5389c69304c6bc1d9e9fb88bb5 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 4 Mar 2024 11:04:26 +0100 Subject: [PATCH 05/17] Correct comment; X-Forwarded-Scheme does not exist --- axum/src/extract/scheme.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum/src/extract/scheme.rs b/axum/src/extract/scheme.rs index 81c678c933..93d105075c 100644 --- a/axum/src/extract/scheme.rs +++ b/axum/src/extract/scheme.rs @@ -16,7 +16,7 @@ const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto"; /// - `X-Forwarded-Proto` header /// - request target / URI /// -/// Note that user agents can set the `X-Forwarded-Scheme` header to arbitrary values so make +/// Note that user agents can set the `X-Forwarded-Proto` header to arbitrary values so make /// sure to validate them to avoid security issues. #[derive(Debug, Clone)] pub struct Scheme(pub String); From d7b4d95c13313f2702455978ac3a8ae27d0d29a9 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 4 Mar 2024 11:04:38 +0100 Subject: [PATCH 06/17] Use axum's test macro --- axum/src/extract/scheme.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/axum/src/extract/scheme.rs b/axum/src/extract/scheme.rs index 93d105075c..9adb01acda 100644 --- a/axum/src/extract/scheme.rs +++ b/axum/src/extract/scheme.rs @@ -84,8 +84,8 @@ mod tests { TestClient::new(Router::new().route("/", get(scheme_as_body))) } - #[test] - fn forwarded_scheme_parsing() { + #[crate::test] + async fn forwarded_scheme_parsing() { // the basic case let headers = header_map(&[(FORWARDED, "host=192.0.2.60;proto=http;by=203.0.113.43")]); let value = parse_forwarded(&headers).unwrap(); From 19dd6930a08f80312887d5402ea437ce1674518e Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 4 Mar 2024 11:41:10 +0100 Subject: [PATCH 07/17] Build request manually to test extraction from parts --- axum/src/extract/scheme.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/axum/src/extract/scheme.rs b/axum/src/extract/scheme.rs index 9adb01acda..503c7d9e92 100644 --- a/axum/src/extract/scheme.rs +++ b/axum/src/extract/scheme.rs @@ -74,7 +74,9 @@ fn parse_forwarded(headers: &HeaderMap) -> Option<&str> { mod tests { use super::*; use crate::{routing::get, test_helpers::TestClient, Router}; - use http::header::HeaderName; + use http::{header::HeaderName, Request}; + + use axum_core::{body::Body, extract::FromRequest}; fn test_client() -> TestClient { async fn scheme_as_body(Scheme(scheme): Scheme) -> String { @@ -119,6 +121,19 @@ mod tests { assert_eq!(scheme, original_scheme); } + #[crate::test] + async fn from_parts() { + let original_scheme = "http"; + let req = Request::builder() + .uri(format!("{original_scheme}://localhost/")) + .body(Body::empty()) + .unwrap(); + assert_eq!( + Scheme::from_request(req, &()).await.unwrap().0, + original_scheme + ); + } + #[crate::test] async fn precedence_forwarded_over_x_forwarded() { let scheme = test_client() From 8d04a6fff255c14aed183d2b8f73f6d334f9ebf1 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 4 Mar 2024 16:07:18 +0100 Subject: [PATCH 08/17] Use OriginalUri extractor --- axum/src/extract/scheme.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/axum/src/extract/scheme.rs b/axum/src/extract/scheme.rs index 503c7d9e92..47c399ea98 100644 --- a/axum/src/extract/scheme.rs +++ b/axum/src/extract/scheme.rs @@ -1,6 +1,6 @@ use super::{ rejection::{FailedToResolveScheme, SchemeRejection}, - FromRequestParts, + FromRequestParts, OriginalUri, }; use async_trait::async_trait; use http::{ @@ -28,7 +28,7 @@ where { type Rejection = SchemeRejection; - async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { // Within Forwarded header if let Some(scheme) = parse_forwarded(&parts.headers) { return Ok(Scheme(scheme.to_owned())); @@ -43,8 +43,9 @@ where return Ok(Scheme(scheme.to_owned())); } - // From parts - if let Some(scheme) = parts.uri.scheme_str() { + // From parts; Infallible conversion + let original = OriginalUri::from_request_parts(parts, state).await.unwrap(); + if let Some(scheme) = original.scheme_str() { return Ok(Scheme(scheme.to_owned())); } From 11a5263a7ca8d0493c7276f0a1dc34f48ee07523 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Fri, 15 Mar 2024 11:42:57 +0100 Subject: [PATCH 09/17] Revert "Use OriginalUri extractor" This reverts commit 000e3b717a3cdfc1e831fadea0d8c1d02afb4062. --- axum/src/extract/scheme.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/axum/src/extract/scheme.rs b/axum/src/extract/scheme.rs index 47c399ea98..503c7d9e92 100644 --- a/axum/src/extract/scheme.rs +++ b/axum/src/extract/scheme.rs @@ -1,6 +1,6 @@ use super::{ rejection::{FailedToResolveScheme, SchemeRejection}, - FromRequestParts, OriginalUri, + FromRequestParts, }; use async_trait::async_trait; use http::{ @@ -28,7 +28,7 @@ where { type Rejection = SchemeRejection; - async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { // Within Forwarded header if let Some(scheme) = parse_forwarded(&parts.headers) { return Ok(Scheme(scheme.to_owned())); @@ -43,9 +43,8 @@ where return Ok(Scheme(scheme.to_owned())); } - // From parts; Infallible conversion - let original = OriginalUri::from_request_parts(parts, state).await.unwrap(); - if let Some(scheme) = original.scheme_str() { + // From parts + if let Some(scheme) = parts.uri.scheme_str() { return Ok(Scheme(scheme.to_owned())); } From 6b61b268de9060d824abb1443e501ed69cd2f11f Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Thu, 5 Sep 2024 23:40:48 +0200 Subject: [PATCH 10/17] HTTP/2 requests contain the scheme in the URI I cannot see a way to test this right now, but cURL shows it works --- axum/src/extract/scheme.rs | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/axum/src/extract/scheme.rs b/axum/src/extract/scheme.rs index 503c7d9e92..2023016363 100644 --- a/axum/src/extract/scheme.rs +++ b/axum/src/extract/scheme.rs @@ -14,7 +14,7 @@ const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto"; /// The scheme is resolved through the following, in order: /// - `Forwarded` header /// - `X-Forwarded-Proto` header -/// - request target / URI +/// - request URI (If the request is an HTTP/2 request! e.g. use `--http2(-prior-knowledge)` with cURL) /// /// Note that user agents can set the `X-Forwarded-Proto` header to arbitrary values so make /// sure to validate them to avoid security issues. @@ -43,7 +43,7 @@ where return Ok(Scheme(scheme.to_owned())); } - // From parts + // From parts of an HTTP/2 request if let Some(scheme) = parts.uri.scheme_str() { return Ok(Scheme(scheme.to_owned())); } @@ -74,9 +74,7 @@ fn parse_forwarded(headers: &HeaderMap) -> Option<&str> { mod tests { use super::*; use crate::{routing::get, test_helpers::TestClient, Router}; - use http::{header::HeaderName, Request}; - - use axum_core::{body::Body, extract::FromRequest}; + use http::header::HeaderName; fn test_client() -> TestClient { async fn scheme_as_body(Scheme(scheme): Scheme) -> String { @@ -121,19 +119,6 @@ mod tests { assert_eq!(scheme, original_scheme); } - #[crate::test] - async fn from_parts() { - let original_scheme = "http"; - let req = Request::builder() - .uri(format!("{original_scheme}://localhost/")) - .body(Body::empty()) - .unwrap(); - assert_eq!( - Scheme::from_request(req, &()).await.unwrap().0, - original_scheme - ); - } - #[crate::test] async fn precedence_forwarded_over_x_forwarded() { let scheme = test_client() From a75172baaf9cd9f06dd3a49af0775c1e4fad7960 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 9 Sep 2024 13:56:58 +0200 Subject: [PATCH 11/17] Implement Scheme extractor within axum-extra --- axum-extra/Cargo.toml | 1 + axum-extra/src/extract/mod.rs | 7 +++++ {axum => axum-extra}/src/extract/scheme.rs | 36 ++++++++++++++-------- axum/src/extract/mod.rs | 2 -- 4 files changed, 32 insertions(+), 14 deletions(-) rename {axum => axum-extra}/src/extract/scheme.rs (83%) diff --git a/axum-extra/Cargo.toml b/axum-extra/Cargo.toml index e091cd9c81..9fdc306835 100644 --- a/axum-extra/Cargo.toml +++ b/axum-extra/Cargo.toml @@ -33,6 +33,7 @@ json-lines = [ ] multipart = ["dep:multer"] protobuf = ["dep:prost"] +scheme = [] query = ["dep:serde_html_form"] tracing = ["dep:tracing", "axum-core/tracing", "axum/tracing"] typed-header = ["dep:headers"] diff --git a/axum-extra/src/extract/mod.rs b/axum-extra/src/extract/mod.rs index 1f9974de02..ff2671b288 100644 --- a/axum-extra/src/extract/mod.rs +++ b/axum-extra/src/extract/mod.rs @@ -52,3 +52,10 @@ pub use crate::json_lines::JsonLines; #[cfg(feature = "typed-header")] #[doc(no_inline)] pub use crate::typed_header::TypedHeader; + +#[cfg(feature = "scheme")] +pub mod scheme; + +#[cfg(feature = "scheme")] +#[doc(no_inline)] +pub use self::scheme::{SchemeMissing, Scheme}; diff --git a/axum/src/extract/scheme.rs b/axum-extra/src/extract/scheme.rs similarity index 83% rename from axum/src/extract/scheme.rs rename to axum-extra/src/extract/scheme.rs index 2023016363..915eca1d70 100644 --- a/axum/src/extract/scheme.rs +++ b/axum-extra/src/extract/scheme.rs @@ -1,32 +1,45 @@ -use super::{ - rejection::{FailedToResolveScheme, SchemeRejection}, - FromRequestParts, +//! Extractor that parses the scheme of a request. +//! See [`Scheme`] for more details. + +use axum::{ + extract::FromRequestParts, + response::{IntoResponse, Response}, }; -use async_trait::async_trait; use http::{ header::{HeaderMap, FORWARDED}, request::Parts, }; const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto"; -/// Extractor that resolves the scheme / protocol of the request. +/// Extractor that resolves the scheme / protocol of a request. /// /// The scheme is resolved through the following, in order: /// - `Forwarded` header /// - `X-Forwarded-Proto` header -/// - request URI (If the request is an HTTP/2 request! e.g. use `--http2(-prior-knowledge)` with cURL) +/// - Request URI (If the request is an HTTP/2 request! e.g. use `--http2(-prior-knowledge)` with cURL) /// /// Note that user agents can set the `X-Forwarded-Proto` header to arbitrary values so make /// sure to validate them to avoid security issues. #[derive(Debug, Clone)] pub struct Scheme(pub String); -#[async_trait] +/// Rejection type used if the [`Scheme`] extractor is unable to +/// resolve a scheme. +#[derive(Debug)] +pub struct SchemeMissing; + +impl IntoResponse for SchemeMissing { + fn into_response(self) -> Response { + (http::StatusCode::BAD_REQUEST, "No scheme found in request").into_response() + } +} + +#[axum::async_trait] impl FromRequestParts for Scheme where S: Send + Sync, { - type Rejection = SchemeRejection; + type Rejection = SchemeMissing; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { // Within Forwarded header @@ -48,9 +61,7 @@ where return Ok(Scheme(scheme.to_owned())); } - Err(SchemeRejection::FailedToResolveScheme( - FailedToResolveScheme, - )) + Err(SchemeMissing) } } @@ -73,7 +84,8 @@ fn parse_forwarded(headers: &HeaderMap) -> Option<&str> { #[cfg(test)] mod tests { use super::*; - use crate::{routing::get, test_helpers::TestClient, Router}; + use crate::test_helpers::TestClient; + use axum::{routing::get, Router}; use http::header::HeaderName; fn test_client() -> TestClient { diff --git a/axum/src/extract/mod.rs b/axum/src/extract/mod.rs index 06181d0d3c..c02bc6f0a6 100644 --- a/axum/src/extract/mod.rs +++ b/axum/src/extract/mod.rs @@ -15,7 +15,6 @@ pub(crate) mod nested_path; mod raw_form; mod raw_query; mod request_parts; -mod scheme; mod state; #[doc(inline)] @@ -32,7 +31,6 @@ pub use self::{ path::{Path, RawPathParams}, raw_form::RawForm, raw_query::RawQuery, - scheme::Scheme, state::State, }; From 0c75bf5f5609313e0971baaae537f24b154a61b0 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 9 Sep 2024 14:29:39 +0200 Subject: [PATCH 12/17] Remove unused rejection in `axum` --- axum/src/extract/rejection.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/axum/src/extract/rejection.rs b/axum/src/extract/rejection.rs index 355264ef74..cba76af054 100644 --- a/axum/src/extract/rejection.rs +++ b/axum/src/extract/rejection.rs @@ -73,14 +73,6 @@ define_rejection! { pub struct FailedToResolveHost; } -define_rejection! { - #[status = BAD_REQUEST] - #[body = "No scheme found in request"] - /// Rejection type used if the [`Scheme`](super::Scheme) extractor is unable to - /// resolve a host. - pub struct FailedToResolveScheme; -} - define_rejection! { #[status = BAD_REQUEST] #[body = "Failed to deserialize form"] @@ -196,16 +188,6 @@ composite_rejection! { } } -composite_rejection! { - /// Rejection used for [`Scheme`](super::Scheme). - /// - /// Contains one variant for each way the [`Scheme`](super::Scheme) extractor - /// can fail. - pub enum SchemeRejection { - FailedToResolveScheme, - } -} - #[cfg(feature = "matched-path")] define_rejection! { #[status = INTERNAL_SERVER_ERROR] From d917a73e7a25ebe69dec5919f8790136cdf2e8c2 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 9 Sep 2024 14:33:46 +0200 Subject: [PATCH 13/17] Formatter --- axum-extra/src/extract/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum-extra/src/extract/mod.rs b/axum-extra/src/extract/mod.rs index ff2671b288..5be24b388f 100644 --- a/axum-extra/src/extract/mod.rs +++ b/axum-extra/src/extract/mod.rs @@ -58,4 +58,4 @@ pub mod scheme; #[cfg(feature = "scheme")] #[doc(no_inline)] -pub use self::scheme::{SchemeMissing, Scheme}; +pub use self::scheme::{Scheme, SchemeMissing}; From d05f56b8c0c5ad1f96e66e94017d8cfb0e585bd2 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 9 Sep 2024 15:01:33 +0200 Subject: [PATCH 14/17] Adjust derives, reference axum's http2 feature --- axum-extra/src/extract/scheme.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/axum-extra/src/extract/scheme.rs b/axum-extra/src/extract/scheme.rs index 915eca1d70..abacc9d7cd 100644 --- a/axum-extra/src/extract/scheme.rs +++ b/axum-extra/src/extract/scheme.rs @@ -18,14 +18,15 @@ const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto"; /// - `X-Forwarded-Proto` header /// - Request URI (If the request is an HTTP/2 request! e.g. use `--http2(-prior-knowledge)` with cURL) /// +/// If you are intending to match on the URI, make sure to enable axum's `http2` feature. /// Note that user agents can set the `X-Forwarded-Proto` header to arbitrary values so make /// sure to validate them to avoid security issues. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Scheme(pub String); /// Rejection type used if the [`Scheme`] extractor is unable to /// resolve a scheme. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct SchemeMissing; impl IntoResponse for SchemeMissing { From ba3dabcac25a99deed080f535811307668485463 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 9 Sep 2024 15:12:15 +0200 Subject: [PATCH 15/17] Follow example of other errors, return non-exhaustive enum instead --- axum-extra/src/extract/mod.rs | 2 +- axum-extra/src/extract/scheme.rs | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/axum-extra/src/extract/mod.rs b/axum-extra/src/extract/mod.rs index 5be24b388f..e9dfeec643 100644 --- a/axum-extra/src/extract/mod.rs +++ b/axum-extra/src/extract/mod.rs @@ -58,4 +58,4 @@ pub mod scheme; #[cfg(feature = "scheme")] #[doc(no_inline)] -pub use self::scheme::{Scheme, SchemeMissing}; +pub use self::scheme::{Scheme, SchemeRejection}; diff --git a/axum-extra/src/extract/scheme.rs b/axum-extra/src/extract/scheme.rs index abacc9d7cd..34456d6866 100644 --- a/axum-extra/src/extract/scheme.rs +++ b/axum-extra/src/extract/scheme.rs @@ -26,10 +26,14 @@ pub struct Scheme(pub String); /// Rejection type used if the [`Scheme`] extractor is unable to /// resolve a scheme. -#[derive(Debug, Default)] -pub struct SchemeMissing; +#[derive(Debug)] +#[non_exhaustive] +pub enum SchemeRejection { + #[allow(missing_docs)] + MissingScheme, +} -impl IntoResponse for SchemeMissing { +impl IntoResponse for SchemeRejection { fn into_response(self) -> Response { (http::StatusCode::BAD_REQUEST, "No scheme found in request").into_response() } @@ -40,7 +44,7 @@ impl FromRequestParts for Scheme where S: Send + Sync, { - type Rejection = SchemeMissing; + type Rejection = SchemeRejection; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { // Within Forwarded header @@ -62,7 +66,7 @@ where return Ok(Scheme(scheme.to_owned())); } - Err(SchemeMissing) + Err(SchemeRejection::MissingScheme) } } From 59168e763f8a9fad888d88e18500472c7e146105 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Mon, 9 Sep 2024 15:43:33 +0200 Subject: [PATCH 16/17] Document new feature flag --- axum-extra/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/axum-extra/src/lib.rs b/axum-extra/src/lib.rs index 02dd6e697a..b1af9e33b1 100644 --- a/axum-extra/src/lib.rs +++ b/axum-extra/src/lib.rs @@ -21,6 +21,7 @@ //! `multipart` | Enables the `Multipart` extractor | No //! `protobuf` | Enables the `Protobuf` extractor and response | No //! `query` | Enables the `Query` extractor | No +//! `scheme` | Enables the `Scheme` extractor | No //! `tracing` | Log rejections from built-in extractors | Yes //! `typed-routing` | Enables the `TypedPath` routing utilities | No //! `typed-header` | Enables the `TypedHeader` extractor and response | No From abc0b30c4c72a89a9a4f92a904b6e8286d92fab1 Mon Sep 17 00:00:00 2001 From: Benjamin Sparks Date: Tue, 10 Sep 2024 12:36:07 +0200 Subject: [PATCH 17/17] Remove incorrect comment --- axum-extra/src/extract/scheme.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/axum-extra/src/extract/scheme.rs b/axum-extra/src/extract/scheme.rs index 34456d6866..f4ac880d52 100644 --- a/axum-extra/src/extract/scheme.rs +++ b/axum-extra/src/extract/scheme.rs @@ -18,7 +18,6 @@ const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto"; /// - `X-Forwarded-Proto` header /// - Request URI (If the request is an HTTP/2 request! e.g. use `--http2(-prior-knowledge)` with cURL) /// -/// If you are intending to match on the URI, make sure to enable axum's `http2` feature. /// Note that user agents can set the `X-Forwarded-Proto` header to arbitrary values so make /// sure to validate them to avoid security issues. #[derive(Debug)]