mirror of
https://github.com/tokio-rs/axum.git
synced 2025-03-28 09:17:24 +01: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:
parent
ffeb4f9407
commit
591d9c9ca5
5 changed files with 32 additions and 14 deletions
|
@ -1,4 +1,7 @@
|
|||
use super::rejection::{FailedToResolveHost, HostRejection};
|
||||
use super::{
|
||||
rejection::{FailedToResolveHost, HostRejection},
|
||||
SpoofableValue,
|
||||
};
|
||||
use axum::extract::FromRequestParts;
|
||||
use http::{
|
||||
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
|
||||
/// sure to validate them to avoid security issues.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Host(pub String);
|
||||
pub struct Host(pub SpoofableValue);
|
||||
|
||||
impl<S> FromRequestParts<S> for Host
|
||||
where
|
||||
|
@ -28,7 +31,7 @@ where
|
|||
|
||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||
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
|
||||
|
@ -36,7 +39,7 @@ where
|
|||
.get(X_FORWARDED_HOST_HEADER_KEY)
|
||||
.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
|
||||
|
@ -44,11 +47,11 @@ where
|
|||
.get(http::header::HOST)
|
||||
.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() {
|
||||
return Ok(Host(host.to_owned()));
|
||||
return Ok(Host(SpoofableValue::new(host.to_owned())));
|
||||
}
|
||||
|
||||
Err(HostRejection::FailedToResolveHost(FailedToResolveHost))
|
||||
|
@ -81,7 +84,7 @@ mod tests {
|
|||
|
||||
fn test_client() -> TestClient {
|
||||
async fn host_as_body(Host(host): Host) -> String {
|
||||
host
|
||||
host.spoofable_value()
|
||||
}
|
||||
|
||||
TestClient::new(Router::new().route("/", get(host_as_body)))
|
||||
|
|
|
@ -63,3 +63,16 @@ pub use crate::json_lines::JsonLines;
|
|||
#[cfg(feature = "typed-header")]
|
||||
#[doc(no_inline)]
|
||||
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},
|
||||
request::Parts,
|
||||
};
|
||||
|
||||
use super::SpoofableValue;
|
||||
const X_FORWARDED_PROTO_HEADER_KEY: &str = "X-Forwarded-Proto";
|
||||
|
||||
/// 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
|
||||
/// sure to validate them to avoid security issues.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Scheme(pub String);
|
||||
pub struct Scheme(pub SpoofableValue);
|
||||
|
||||
/// Rejection type used if the [`Scheme`] extractor is unable to
|
||||
/// resolve a scheme.
|
||||
|
@ -43,7 +45,7 @@ where
|
|||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||
// Within Forwarded header
|
||||
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
|
||||
|
@ -52,12 +54,12 @@ where
|
|||
.get(X_FORWARDED_PROTO_HEADER_KEY)
|
||||
.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
|
||||
if let Some(scheme) = parts.uri.scheme_str() {
|
||||
return Ok(Scheme(scheme.to_owned()));
|
||||
return Ok(Scheme(SpoofableValue::new(scheme.to_owned())));
|
||||
}
|
||||
|
||||
Err(SchemeMissing)
|
||||
|
@ -89,7 +91,7 @@ mod tests {
|
|||
|
||||
fn test_client() -> TestClient {
|
||||
async fn scheme_as_body(Scheme(scheme): Scheme) -> String {
|
||||
scheme
|
||||
scheme.spoofable_value()
|
||||
}
|
||||
|
||||
TestClient::new(Router::new().route("/", get(scheme_as_body)))
|
||||
|
|
|
@ -122,7 +122,7 @@ where
|
|||
}
|
||||
|
||||
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())),
|
||||
Err(error) => {
|
||||
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 {
|
||||
match make_https(host, uri, ports) {
|
||||
match make_https(host.spoofable_value(), uri, ports) {
|
||||
Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, "failed to convert URI to HTTPS");
|
||||
|
|
Loading…
Add table
Reference in a new issue