Deprecate Host and Scheme extractors

This commit is contained in:
Jonas Platte
2025-12-26 14:48:47 +01:00
parent adf2e6c6bf
commit aba8046921
5 changed files with 16 additions and 35 deletions
+3
View File
@@ -1,3 +1,5 @@
#![allow(deprecated)]
use super::rejection::{FailedToResolveHost, HostRejection}; use super::rejection::{FailedToResolveHost, HostRejection};
use axum_core::{ use axum_core::{
extract::{FromRequestParts, OptionalFromRequestParts}, extract::{FromRequestParts, OptionalFromRequestParts},
@@ -25,6 +27,7 @@ const X_FORWARDED_HOST_HEADER_KEY: &str = "X-Forwarded-Host";
/// ///
/// Note that user agents can set `X-Forwarded-Host` and `Host` headers to arbitrary values so make /// Note that user agents can set `X-Forwarded-Host` and `Host` headers to arbitrary values so make
/// sure to validate them to avoid security issues. /// sure to validate them to avoid security issues.
#[deprecated = "will be removed in the next version; see https://github.com/tokio-rs/axum/issues/3442"]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Host(pub String); pub struct Host(pub String);
+2
View File
@@ -34,6 +34,7 @@ mod scheme;
#[cfg(feature = "optional-path")] #[cfg(feature = "optional-path")]
pub use self::optional_path::OptionalPath; pub use self::optional_path::OptionalPath;
#[allow(deprecated)]
pub use self::host::Host; pub use self::host::Host;
#[cfg(feature = "cached")] #[cfg(feature = "cached")]
@@ -62,6 +63,7 @@ pub use self::query::{OptionalQueryRejection, Query, QueryRejection};
#[cfg(feature = "multipart")] #[cfg(feature = "multipart")]
pub use self::multipart::Multipart; pub use self::multipart::Multipart;
#[allow(deprecated)]
#[cfg(feature = "scheme")] #[cfg(feature = "scheme")]
#[doc(no_inline)] #[doc(no_inline)]
pub use self::scheme::{Scheme, SchemeMissing}; pub use self::scheme::{Scheme, SchemeMissing};
+2
View File
@@ -1,5 +1,6 @@
//! Extractor that parses the scheme of a request. //! Extractor that parses the scheme of a request.
//! See [`Scheme`] for more details. //! See [`Scheme`] for more details.
#![allow(deprecated)]
use axum_core::{__define_rejection as define_rejection, extract::FromRequestParts}; use axum_core::{__define_rejection as define_rejection, extract::FromRequestParts};
use http::{ use http::{
@@ -17,6 +18,7 @@ const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto";
/// ///
/// Note that user agents can set the `X-Forwarded-Proto` 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. /// sure to validate them to avoid security issues.
#[deprecated = "will be removed in the next version; see https://github.com/tokio-rs/axum/issues/3442"]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Scheme(pub String); pub struct Scheme(pub String);
+5 -18
View File
@@ -6,12 +6,11 @@
use axum::{ use axum::{
handler::HandlerWithoutStateExt, handler::HandlerWithoutStateExt,
http::{uri::Authority, StatusCode, Uri}, http::{StatusCode, Uri},
response::Redirect, response::Redirect,
routing::get, routing::get,
BoxError, Router, BoxError, Router,
}; };
use axum_extra::extract::Host;
use axum_server::tls_rustls::RustlsConfig; use axum_server::tls_rustls::RustlsConfig;
use std::{future::Future, net::SocketAddr, path::PathBuf, time::Duration}; use std::{future::Future, net::SocketAddr, path::PathBuf, time::Duration};
use tokio::signal; use tokio::signal;
@@ -106,33 +105,21 @@ async fn redirect_http_to_https<F>(ports: Ports, signal: F)
where where
F: Future<Output = ()> + Send + 'static, F: Future<Output = ()> + Send + 'static,
{ {
fn make_https(host: &str, uri: Uri, https_port: u16) -> Result<Uri, BoxError> { fn make_https(uri: Uri, https_port: u16) -> Result<Uri, BoxError> {
let mut parts = uri.into_parts(); let mut parts = uri.into_parts();
parts.scheme = Some(axum::http::uri::Scheme::HTTPS); parts.scheme = Some(axum::http::uri::Scheme::HTTPS);
parts.authority = Some(format!("localhost:{https_port}").parse()?);
if parts.path_and_query.is_none() { if parts.path_and_query.is_none() {
parts.path_and_query = Some("/".parse().unwrap()); parts.path_and_query = Some("/".parse().unwrap());
} }
let authority: Authority = host.parse()?;
let bare_host = match authority.port() {
Some(port_struct) => authority
.as_str()
.strip_suffix(port_struct.as_str())
.unwrap()
.strip_suffix(':')
.unwrap(), // if authority.port() is Some(port) then we can be sure authority ends with :{port}
None => authority.as_str(),
};
parts.authority = Some(format!("{bare_host}:{https_port}").parse()?);
Ok(Uri::from_parts(parts)?) Ok(Uri::from_parts(parts)?)
} }
let redirect = move |Host(host): Host, uri: Uri| async move { let redirect = move |uri: Uri| async move {
match make_https(&host, uri, ports.https) { match make_https(uri, ports.https) {
Ok(uri) => Ok(Redirect::permanent(&uri.to_string())), Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
Err(error) => { Err(error) => {
tracing::warn!(%error, "failed to convert URI to HTTPS"); tracing::warn!(%error, "failed to convert URI to HTTPS");
+4 -17
View File
@@ -13,7 +13,6 @@ use axum::{
routing::get, routing::get,
BoxError, Router, BoxError, Router,
}; };
use axum_extra::extract::Host;
use axum_server::tls_rustls::RustlsConfig; use axum_server::tls_rustls::RustlsConfig;
use std::{net::SocketAddr, path::PathBuf}; use std::{net::SocketAddr, path::PathBuf};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@@ -72,33 +71,21 @@ async fn handler() -> &'static str {
#[allow(dead_code)] #[allow(dead_code)]
async fn redirect_http_to_https(ports: Ports) { async fn redirect_http_to_https(ports: Ports) {
fn make_https(host: &str, uri: Uri, https_port: u16) -> Result<Uri, BoxError> { fn make_https(uri: Uri, https_port: u16) -> Result<Uri, BoxError> {
let mut parts = uri.into_parts(); let mut parts = uri.into_parts();
parts.scheme = Some(axum::http::uri::Scheme::HTTPS); parts.scheme = Some(axum::http::uri::Scheme::HTTPS);
parts.authority = Some(format!("localhost:{https_port}").parse()?);
if parts.path_and_query.is_none() { if parts.path_and_query.is_none() {
parts.path_and_query = Some("/".parse().unwrap()); parts.path_and_query = Some("/".parse().unwrap());
} }
let authority: Authority = host.parse()?;
let bare_host = match authority.port() {
Some(port_struct) => authority
.as_str()
.strip_suffix(port_struct.as_str())
.unwrap()
.strip_suffix(':')
.unwrap(), // if authority.port() is Some(port) then we can be sure authority ends with :{port}
None => authority.as_str(),
};
parts.authority = Some(format!("{bare_host}:{https_port}").parse()?);
Ok(Uri::from_parts(parts)?) Ok(Uri::from_parts(parts)?)
} }
let redirect = move |Host(host): Host, uri: Uri| async move { let redirect = move |uri: Uri| async move {
match make_https(&host, uri, ports.https) { match make_https(uri, ports.https) {
Ok(uri) => Ok(Redirect::permanent(&uri.to_string())), Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
Err(error) => { Err(error) => {
tracing::warn!(%error, "failed to convert URI to HTTPS"); tracing::warn!(%error, "failed to convert URI to HTTPS");