2024-10-14 22:45:37 +00:00
|
|
|
use super::rejection::{FailedToResolveHost, HostRejection};
|
|
|
|
|
use axum::extract::FromRequestParts;
|
2022-08-22 12:23:20 +02:00
|
|
|
use http::{
|
|
|
|
|
header::{HeaderMap, FORWARDED},
|
|
|
|
|
request::Parts,
|
|
|
|
|
};
|
2022-03-07 03:29:10 +11:00
|
|
|
|
2022-03-09 10:18:12 +01:00
|
|
|
const X_FORWARDED_HOST_HEADER_KEY: &str = "X-Forwarded-Host";
|
2022-03-07 03:29:10 +11:00
|
|
|
|
|
|
|
|
/// Extractor that resolves the hostname of the request.
|
|
|
|
|
///
|
|
|
|
|
/// Hostname is resolved through the following, in order:
|
2022-06-10 22:30:01 +02:00
|
|
|
/// - `Forwarded` header
|
2022-03-07 03:29:10 +11:00
|
|
|
/// - `X-Forwarded-Host` header
|
|
|
|
|
/// - `Host` header
|
|
|
|
|
/// - request target / URI
|
2022-03-09 10:18:12 +01:00
|
|
|
///
|
|
|
|
|
/// 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.
|
2022-03-07 03:29:10 +11:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct Host(pub String);
|
|
|
|
|
|
2022-08-22 12:23:20 +02:00
|
|
|
impl<S> FromRequestParts<S> for Host
|
2022-03-07 03:29:10 +11:00
|
|
|
where
|
2022-08-17 22:08:24 +02:00
|
|
|
S: Send + Sync,
|
2022-03-07 03:29:10 +11:00
|
|
|
{
|
|
|
|
|
type Rejection = HostRejection;
|
|
|
|
|
|
2022-08-22 12:23:20 +02:00
|
|
|
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
|
|
|
|
if let Some(host) = parse_forwarded(&parts.headers) {
|
2022-06-10 22:30:01 +02:00
|
|
|
return Ok(Host(host.to_owned()));
|
|
|
|
|
}
|
2022-03-07 03:29:10 +11:00
|
|
|
|
2022-08-22 12:23:20 +02:00
|
|
|
if let Some(host) = parts
|
|
|
|
|
.headers
|
2022-03-07 03:29:10 +11:00
|
|
|
.get(X_FORWARDED_HOST_HEADER_KEY)
|
|
|
|
|
.and_then(|host| host.to_str().ok())
|
|
|
|
|
{
|
|
|
|
|
return Ok(Host(host.to_owned()));
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-22 12:23:20 +02:00
|
|
|
if let Some(host) = parts
|
|
|
|
|
.headers
|
2022-03-07 03:29:10 +11:00
|
|
|
.get(http::header::HOST)
|
|
|
|
|
.and_then(|host| host.to_str().ok())
|
|
|
|
|
{
|
|
|
|
|
return Ok(Host(host.to_owned()));
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-22 12:23:20 +02:00
|
|
|
if let Some(host) = parts.uri.host() {
|
2022-03-07 03:29:10 +11:00
|
|
|
return Ok(Host(host.to_owned()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Err(HostRejection::FailedToResolveHost(FailedToResolveHost))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:30:01 +02:00
|
|
|
#[allow(warnings)]
|
|
|
|
|
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(',').nth(0)?;
|
|
|
|
|
|
|
|
|
|
// find the value of the `host` field
|
|
|
|
|
first_value.split(';').find_map(|pair| {
|
|
|
|
|
let (key, value) = pair.split_once('=')?;
|
|
|
|
|
key.trim()
|
|
|
|
|
.eq_ignore_ascii_case("host")
|
|
|
|
|
.then(|| value.trim().trim_matches('"'))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 03:29:10 +11:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
2024-10-14 22:45:37 +00:00
|
|
|
use crate::test_helpers::TestClient;
|
|
|
|
|
use axum::{routing::get, Router};
|
2022-06-10 22:30:01 +02:00
|
|
|
use http::header::HeaderName;
|
2022-03-07 03:29:10 +11:00
|
|
|
|
|
|
|
|
fn test_client() -> TestClient {
|
|
|
|
|
async fn host_as_body(Host(host): Host) -> String {
|
|
|
|
|
host
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TestClient::new(Router::new().route("/", get(host_as_body)))
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 15:12:01 +01:00
|
|
|
#[crate::test]
|
2022-03-07 03:29:10 +11:00
|
|
|
async fn host_header() {
|
|
|
|
|
let original_host = "some-domain:123";
|
|
|
|
|
let host = test_client()
|
|
|
|
|
.get("/")
|
|
|
|
|
.header(http::header::HOST, original_host)
|
|
|
|
|
.await
|
|
|
|
|
.text()
|
|
|
|
|
.await;
|
|
|
|
|
assert_eq!(host, original_host);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 15:12:01 +01:00
|
|
|
#[crate::test]
|
2022-03-07 03:29:10 +11:00
|
|
|
async fn x_forwarded_host_header() {
|
|
|
|
|
let original_host = "some-domain:456";
|
|
|
|
|
let host = test_client()
|
|
|
|
|
.get("/")
|
|
|
|
|
.header(X_FORWARDED_HOST_HEADER_KEY, original_host)
|
|
|
|
|
.await
|
|
|
|
|
.text()
|
|
|
|
|
.await;
|
|
|
|
|
assert_eq!(host, original_host);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 15:12:01 +01:00
|
|
|
#[crate::test]
|
2022-03-07 03:29:10 +11:00
|
|
|
async fn x_forwarded_host_precedence_over_host_header() {
|
|
|
|
|
let x_forwarded_host_header = "some-domain:456";
|
|
|
|
|
let host_header = "some-domain:123";
|
|
|
|
|
let host = test_client()
|
|
|
|
|
.get("/")
|
|
|
|
|
.header(X_FORWARDED_HOST_HEADER_KEY, x_forwarded_host_header)
|
|
|
|
|
.header(http::header::HOST, host_header)
|
|
|
|
|
.await
|
|
|
|
|
.text()
|
|
|
|
|
.await;
|
|
|
|
|
assert_eq!(host, x_forwarded_host_header);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 15:12:01 +01:00
|
|
|
#[crate::test]
|
2022-03-07 03:29:10 +11:00
|
|
|
async fn uri_host() {
|
2023-12-30 18:23:53 +01:00
|
|
|
let host = test_client().get("/").await.text().await;
|
2022-03-07 03:29:10 +11:00
|
|
|
assert!(host.contains("127.0.0.1"));
|
|
|
|
|
}
|
2022-06-10 22:30:01 +02:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn forwarded_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, "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 value = parse_forwarded(&headers).unwrap();
|
|
|
|
|
assert_eq!(value, "192.0.2.60");
|
|
|
|
|
|
|
|
|
|
// ipv6
|
|
|
|
|
let headers = header_map(&[(FORWARDED, "host=\"[2001:db8:cafe::17]:4711\"")]);
|
|
|
|
|
let value = parse_forwarded(&headers).unwrap();
|
|
|
|
|
assert_eq!(value, "[2001:db8:cafe::17]:4711");
|
|
|
|
|
|
|
|
|
|
// multiple values in one header
|
|
|
|
|
let headers = header_map(&[(FORWARDED, "host=192.0.2.60, host=127.0.0.1")]);
|
|
|
|
|
let value = parse_forwarded(&headers).unwrap();
|
|
|
|
|
assert_eq!(value, "192.0.2.60");
|
|
|
|
|
|
|
|
|
|
// multiple header values
|
|
|
|
|
let headers = header_map(&[
|
|
|
|
|
(FORWARDED, "host=192.0.2.60"),
|
|
|
|
|
(FORWARDED, "host=127.0.0.1"),
|
|
|
|
|
]);
|
|
|
|
|
let value = parse_forwarded(&headers).unwrap();
|
|
|
|
|
assert_eq!(value, "192.0.2.60");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn header_map(values: &[(HeaderName, &str)]) -> HeaderMap {
|
|
|
|
|
let mut headers = HeaderMap::new();
|
|
|
|
|
for (key, value) in values {
|
|
|
|
|
headers.append(key, value.parse().unwrap());
|
|
|
|
|
}
|
|
|
|
|
headers
|
|
|
|
|
}
|
2022-03-07 03:29:10 +11:00
|
|
|
}
|