mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-29 00:00:18 +02:00
PoC of introducing SpoofableValue
PoC to check which solution to pick for https://github.com/tokio-rs/axum/issues/2998
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
use super::rejection::{FailedToResolveHost, HostRejection};
|
use super::{
|
||||||
|
rejection::{FailedToResolveHost, HostRejection},
|
||||||
|
SpoofableValue,
|
||||||
|
};
|
||||||
use axum::extract::FromRequestParts;
|
use axum::extract::FromRequestParts;
|
||||||
use http::{
|
use http::{
|
||||||
header::{HeaderMap, FORWARDED},
|
header::{HeaderMap, FORWARDED},
|
||||||
@@ -18,7 +21,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.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Host(pub String);
|
pub struct Host(pub SpoofableValue);
|
||||||
|
|
||||||
impl<S> FromRequestParts<S> for Host
|
impl<S> FromRequestParts<S> for Host
|
||||||
where
|
where
|
||||||
@@ -28,7 +31,7 @@ where
|
|||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||||
if let Some(host) = parse_forwarded(&parts.headers) {
|
if let Some(host) = parse_forwarded(&parts.headers) {
|
||||||
return Ok(Host(host.to_owned()));
|
return Ok(Host(SpoofableValue::new(host.to_owned())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(host) = parts
|
if let Some(host) = parts
|
||||||
@@ -36,7 +39,7 @@ where
|
|||||||
.get(X_FORWARDED_HOST_HEADER_KEY)
|
.get(X_FORWARDED_HOST_HEADER_KEY)
|
||||||
.and_then(|host| host.to_str().ok())
|
.and_then(|host| host.to_str().ok())
|
||||||
{
|
{
|
||||||
return Ok(Host(host.to_owned()));
|
return Ok(Host(SpoofableValue::new(host.to_owned())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(host) = parts
|
if let Some(host) = parts
|
||||||
@@ -44,11 +47,11 @@ where
|
|||||||
.get(http::header::HOST)
|
.get(http::header::HOST)
|
||||||
.and_then(|host| host.to_str().ok())
|
.and_then(|host| host.to_str().ok())
|
||||||
{
|
{
|
||||||
return Ok(Host(host.to_owned()));
|
return Ok(Host(SpoofableValue::new(host.to_owned())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(host) = parts.uri.host() {
|
if let Some(host) = parts.uri.host() {
|
||||||
return Ok(Host(host.to_owned()));
|
return Ok(Host(SpoofableValue::new(host.to_owned())));
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(HostRejection::FailedToResolveHost(FailedToResolveHost))
|
Err(HostRejection::FailedToResolveHost(FailedToResolveHost))
|
||||||
@@ -81,7 +84,7 @@ mod tests {
|
|||||||
|
|
||||||
fn test_client() -> TestClient {
|
fn test_client() -> TestClient {
|
||||||
async fn host_as_body(Host(host): Host) -> String {
|
async fn host_as_body(Host(host): Host) -> String {
|
||||||
host
|
host.spoofable_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
TestClient::new(Router::new().route("/", get(host_as_body)))
|
TestClient::new(Router::new().route("/", get(host_as_body)))
|
||||||
|
|||||||
@@ -63,3 +63,16 @@ pub use crate::json_lines::JsonLines;
|
|||||||
#[cfg(feature = "typed-header")]
|
#[cfg(feature = "typed-header")]
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use crate::typed_header::TypedHeader;
|
pub use crate::typed_header::TypedHeader;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SpoofableValue(String);
|
||||||
|
|
||||||
|
impl SpoofableValue {
|
||||||
|
pub fn new(value: String) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spoofable_value(self) -> String {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ use http::{
|
|||||||
header::{HeaderMap, FORWARDED},
|
header::{HeaderMap, FORWARDED},
|
||||||
request::Parts,
|
request::Parts,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::SpoofableValue;
|
||||||
const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto";
|
const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto";
|
||||||
|
|
||||||
/// Extractor that resolves the scheme / protocol of a request.
|
/// Extractor that resolves the scheme / protocol of a request.
|
||||||
@@ -21,7 +23,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.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Scheme(pub String);
|
pub struct Scheme(pub SpoofableValue);
|
||||||
|
|
||||||
/// Rejection type used if the [`Scheme`] extractor is unable to
|
/// Rejection type used if the [`Scheme`] extractor is unable to
|
||||||
/// resolve a scheme.
|
/// resolve a scheme.
|
||||||
@@ -43,7 +45,7 @@ where
|
|||||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||||
// Within Forwarded header
|
// Within Forwarded header
|
||||||
if let Some(scheme) = parse_forwarded(&parts.headers) {
|
if let Some(scheme) = parse_forwarded(&parts.headers) {
|
||||||
return Ok(Scheme(scheme.to_owned()));
|
return Ok(Scheme(SpoofableValue::new(scheme.to_owned())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// X-Forwarded-Proto
|
// X-Forwarded-Proto
|
||||||
@@ -52,12 +54,12 @@ where
|
|||||||
.get(X_FORWARDED_PROTO_HEADER_KEY)
|
.get(X_FORWARDED_PROTO_HEADER_KEY)
|
||||||
.and_then(|scheme| scheme.to_str().ok())
|
.and_then(|scheme| scheme.to_str().ok())
|
||||||
{
|
{
|
||||||
return Ok(Scheme(scheme.to_owned()));
|
return Ok(Scheme(SpoofableValue::new(scheme.to_owned())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// From parts of an HTTP/2 request
|
// From parts of an HTTP/2 request
|
||||||
if let Some(scheme) = parts.uri.scheme_str() {
|
if let Some(scheme) = parts.uri.scheme_str() {
|
||||||
return Ok(Scheme(scheme.to_owned()));
|
return Ok(Scheme(SpoofableValue::new(scheme.to_owned())));
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(SchemeMissing)
|
Err(SchemeMissing)
|
||||||
@@ -89,7 +91,7 @@ mod tests {
|
|||||||
|
|
||||||
fn test_client() -> TestClient {
|
fn test_client() -> TestClient {
|
||||||
async fn scheme_as_body(Scheme(scheme): Scheme) -> String {
|
async fn scheme_as_body(Scheme(scheme): Scheme) -> String {
|
||||||
scheme
|
scheme.spoofable_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
TestClient::new(Router::new().route("/", get(scheme_as_body)))
|
TestClient::new(Router::new().route("/", get(scheme_as_body)))
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let redirect = move |Host(host): Host, uri: Uri| async move {
|
let redirect = move |Host(host): Host, uri: Uri| async move {
|
||||||
match make_https(host, uri, ports) {
|
match make_https(host.spoofable_value(), uri, ports) {
|
||||||
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");
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ async fn redirect_http_to_https(ports: Ports) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let redirect = move |Host(host): Host, uri: Uri| async move {
|
let redirect = move |Host(host): Host, uri: Uri| async move {
|
||||||
match make_https(host, uri, ports) {
|
match make_https(host.spoofable_value(), uri, ports) {
|
||||||
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");
|
||||||
|
|||||||
Reference in New Issue
Block a user