use crate::config::Route;
use crate::vendored::hyper_ext::{empty_body, string_body, ProxyBody};
use hyper::header::{
HeaderName, HeaderValue, CONNECTION, CONTENT_LENGTH, HOST, TRANSFER_ENCODING, UPGRADE,
};
use hyper::{Method, Version};
use hyper::{Request, Response, StatusCode};
use std::net::SocketAddr;
pub mod headers {
use hyper::header::HeaderName;
pub static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
pub static X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto");
pub static X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host");
pub static X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip");
}
const HOP_BY_HOP_HEADERS: &[&str] = &[
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade",
];
const CONNECTION_TOKEN_EXEMPT: &[&str] = &["host", "content-length"];
pub struct MessageHandler;
impl MessageHandler {
pub fn upgrade_protocol(request: &Request<ProxyBody>) -> Option<String> {
if request.version() != Version::HTTP_11 {
return None;
}
let headers = request.headers();
let asks_for_upgrade = headers.get_all(CONNECTION).iter().any(|value| {
value
.to_str()
.map(|v| {
v.split(',')
.any(|t| t.trim().eq_ignore_ascii_case("upgrade"))
})
.unwrap_or(false)
});
if !asks_for_upgrade {
return None;
}
headers
.get(UPGRADE)
.and_then(|v| v.to_str().ok())
.map(|v| v.trim().to_ascii_lowercase())
.filter(|v| !v.is_empty())
}
pub fn h2_connect_protocol(request: &Request<ProxyBody>) -> Option<String> {
if request.version() != Version::HTTP_2 || request.method() != Method::CONNECT {
return None;
}
request
.extensions()
.get::<hyper::ext::Protocol>()
.map(|protocol| protocol.as_str().to_ascii_lowercase())
}
pub fn restore_upgrade_headers(request: &mut Request<ProxyBody>, protocol: &str) {
if let Ok(value) = HeaderValue::from_str(protocol) {
let headers = request.headers_mut();
headers.insert(UPGRADE, value);
headers.insert(CONNECTION, HeaderValue::from_static("upgrade"));
}
}
pub fn has_ambiguous_framing(request: &Request<ProxyBody>) -> bool {
let headers = request.headers();
headers.contains_key(CONTENT_LENGTH) && headers.contains_key(TRANSFER_ENCODING)
}
pub fn strip_hop_by_hop_headers(request: &mut Request<ProxyBody>) {
let headers = request.headers_mut();
let mut named: Vec<String> = Vec::new();
for value in headers.get_all(CONNECTION).iter() {
let Ok(value) = value.to_str() else { continue };
for token in value.split(',') {
let token = token.trim().to_ascii_lowercase();
if !token.is_empty() {
named.push(token);
}
}
}
for name in HOP_BY_HOP_HEADERS {
headers.remove(*name);
}
for token in named {
if CONNECTION_TOKEN_EXEMPT.contains(&token.as_str()) {
continue;
}
if let Ok(name) = HeaderName::from_bytes(token.as_bytes()) {
headers.remove(name);
}
}
}
pub fn add_forwarding_headers(
request: &mut Request<ProxyBody>,
client_addr: SocketAddr,
proto: &str,
) {
let authority = request.uri().authority().map(|a| a.to_string());
let headers = request.headers_mut();
let client_ip = client_addr.ip().to_string();
if let Some(existing) = headers.get(&headers::X_FORWARDED_FOR) {
let mut new_value = existing.to_str().unwrap_or("").to_string();
new_value.push_str(", ");
new_value.push_str(&client_ip);
if let Ok(value) = HeaderValue::from_str(&new_value) {
headers.insert(headers::X_FORWARDED_FOR.clone(), value);
}
} else if let Ok(value) = HeaderValue::from_str(&client_ip) {
headers.insert(headers::X_FORWARDED_FOR.clone(), value);
}
if !headers.contains_key(&headers::X_REAL_IP) {
if let Ok(value) = HeaderValue::from_str(&client_ip) {
headers.insert(headers::X_REAL_IP.clone(), value);
}
}
if let Ok(value) = HeaderValue::from_str(proto) {
headers.insert(headers::X_FORWARDED_PROTO.clone(), value);
}
let original_host = headers
.get(HOST)
.cloned()
.or_else(|| authority.and_then(|a| HeaderValue::from_str(&a).ok()));
if let Some(host) = original_host {
headers.insert(headers::X_FORWARDED_HOST.clone(), host);
}
}
pub fn rewrite_host_header(request: &mut Request<ProxyBody>, upstream_host: &str) {
if let Ok(value) = HeaderValue::from_str(upstream_host) {
request.headers_mut().insert(HOST, value);
}
}
pub fn strip_path_prefix(request: &mut Request<ProxyBody>, prefix: &str) {
let uri = request.uri();
let path = uri.path();
if let Some(new_path) = path.strip_prefix(prefix) {
let new_path = if new_path.is_empty() || !new_path.starts_with('/') {
format!("/{}", new_path.trim_start_matches('/'))
} else {
new_path.to_string()
};
let new_uri = if let Some(query) = uri.query() {
format!("{}?{}", new_path, query)
} else {
new_path
};
if let Ok(new_uri) = new_uri.parse() {
*request.uri_mut() = new_uri;
}
}
}
pub fn apply_route_headers(request: &mut Request<ProxyBody>, route: &Route) {
let headers = request.headers_mut();
for (name, value) in &route.add_headers {
if let (Ok(name), Ok(value)) = (
HeaderName::from_bytes(name.as_bytes()),
HeaderValue::from_str(value),
) {
headers.insert(name, value);
}
}
for name in &route.remove_headers {
if let Ok(name) = HeaderName::from_bytes(name.as_bytes()) {
headers.remove(name);
}
}
}
pub fn error_response(status: StatusCode, message: &str) -> Response<ProxyBody> {
Response::builder()
.status(status)
.header("content-type", "text/plain; charset=utf-8")
.body(string_body(message.to_string()))
.unwrap_or_else(|_| {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(empty_body())
.unwrap()
})
}
pub fn bad_request(message: &str) -> Response<ProxyBody> {
Self::error_response(StatusCode::BAD_REQUEST, message)
}
pub fn bad_gateway(message: &str) -> Response<ProxyBody> {
Self::error_response(StatusCode::BAD_GATEWAY, message)
}
pub fn service_unavailable(message: &str) -> Response<ProxyBody> {
Self::error_response(StatusCode::SERVICE_UNAVAILABLE, message)
}
pub fn gateway_timeout() -> Response<ProxyBody> {
Self::error_response(StatusCode::GATEWAY_TIMEOUT, "Gateway Timeout")
}
pub fn too_many_requests() -> Response<ProxyBody> {
Self::error_response(StatusCode::TOO_MANY_REQUESTS, "Too Many Requests")
}
pub fn not_found() -> Response<ProxyBody> {
Self::error_response(StatusCode::NOT_FOUND, "Not Found")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vendored::hyper_ext::empty_body;
#[test]
fn test_strip_path_prefix() {
let mut request = Request::builder()
.uri("/api/v1/users")
.body(empty_body())
.unwrap();
MessageHandler::strip_path_prefix(&mut request, "/api/v1");
assert_eq!(request.uri().path(), "/users");
}
#[test]
fn test_strip_path_prefix_root() {
let mut request = Request::builder().uri("/api").body(empty_body()).unwrap();
MessageHandler::strip_path_prefix(&mut request, "/api");
assert_eq!(request.uri().path(), "/");
}
#[test]
fn test_h2_connect_protocol_detected() {
let mut request = Request::builder()
.method(Method::CONNECT)
.version(Version::HTTP_2)
.uri("/chat")
.body(empty_body())
.unwrap();
request
.extensions_mut()
.insert(hyper::ext::Protocol::from_static("websocket"));
assert_eq!(
MessageHandler::h2_connect_protocol(&request).as_deref(),
Some("websocket")
);
assert!(MessageHandler::upgrade_protocol(&request).is_none());
}
#[test]
fn test_h2_connect_without_protocol_is_a_plain_connect() {
let request = Request::builder()
.method(Method::CONNECT)
.version(Version::HTTP_2)
.uri("/chat")
.body(empty_body())
.unwrap();
assert!(MessageHandler::h2_connect_protocol(&request).is_none());
}
#[test]
fn test_h1_connect_is_not_extended_connect() {
let mut request = Request::builder()
.method(Method::CONNECT)
.version(Version::HTTP_11)
.uri("/chat")
.body(empty_body())
.unwrap();
request
.extensions_mut()
.insert(hyper::ext::Protocol::from_static("websocket"));
assert!(MessageHandler::h2_connect_protocol(&request).is_none());
}
#[test]
fn test_upgrade_protocol_detected() {
let request = Request::builder()
.uri("/chat")
.header("host", "a")
.header("upgrade", "websocket")
.header("connection", "Upgrade")
.body(empty_body())
.unwrap();
assert_eq!(
MessageHandler::upgrade_protocol(&request).as_deref(),
Some("websocket")
);
}
#[test]
fn test_upgrade_needs_connection_token() {
let request = Request::builder()
.uri("/chat")
.header("host", "a")
.header("upgrade", "websocket")
.body(empty_body())
.unwrap();
assert!(MessageHandler::upgrade_protocol(&request).is_none());
}
#[test]
fn test_upgrade_token_found_among_others() {
let request = Request::builder()
.uri("/chat")
.header("host", "a")
.header("upgrade", "WebSocket")
.header("connection", "keep-alive, UPGRADE")
.body(empty_body())
.unwrap();
assert_eq!(
MessageHandler::upgrade_protocol(&request).as_deref(),
Some("websocket")
);
}
#[test]
fn test_no_upgrade_without_upgrade_header() {
let request = Request::builder()
.uri("/chat")
.header("host", "a")
.header("connection", "upgrade")
.body(empty_body())
.unwrap();
assert!(MessageHandler::upgrade_protocol(&request).is_none());
}
#[test]
fn test_http2_never_upgrades() {
let request = Request::builder()
.uri("/chat")
.version(Version::HTTP_2)
.header("upgrade", "websocket")
.header("connection", "Upgrade")
.body(empty_body())
.unwrap();
assert!(MessageHandler::upgrade_protocol(&request).is_none());
}
#[test]
fn test_restore_upgrade_headers_normalises_connection() {
let mut request = Request::builder()
.uri("/chat")
.header("host", "a")
.header("upgrade", "websocket")
.header("connection", "Upgrade, X-Sneaky")
.header("x-sneaky", "leaked")
.header("x-legit", "keep-me")
.body(empty_body())
.unwrap();
let protocol = MessageHandler::upgrade_protocol(&request).unwrap();
MessageHandler::strip_hop_by_hop_headers(&mut request);
MessageHandler::restore_upgrade_headers(&mut request, &protocol);
assert_eq!(request.headers().get("upgrade").unwrap(), "websocket");
assert_eq!(request.headers().get("connection").unwrap(), "upgrade");
assert!(!request.headers().contains_key("x-sneaky"));
assert_eq!(request.headers().get("x-legit").unwrap(), "keep-me");
}
#[test]
fn test_ambiguous_framing_detected() {
let request = Request::builder()
.uri("/test")
.header("host", "a")
.header("content-length", "6")
.header("transfer-encoding", "chunked")
.body(empty_body())
.unwrap();
assert!(MessageHandler::has_ambiguous_framing(&request));
}
#[test]
fn test_content_length_alone_is_not_ambiguous() {
let request = Request::builder()
.uri("/test")
.header("host", "a")
.header("content-length", "6")
.body(empty_body())
.unwrap();
assert!(!MessageHandler::has_ambiguous_framing(&request));
}
#[test]
fn test_transfer_encoding_alone_is_not_ambiguous() {
let request = Request::builder()
.uri("/test")
.header("host", "a")
.header("transfer-encoding", "chunked")
.body(empty_body())
.unwrap();
assert!(!MessageHandler::has_ambiguous_framing(&request));
}
#[test]
fn test_strips_each_hop_by_hop_header() {
for name in HOP_BY_HOP_HEADERS {
let mut request = Request::builder()
.uri("/test")
.header("host", "example.com")
.header(*name, "some-value")
.body(empty_body())
.unwrap();
MessageHandler::strip_hop_by_hop_headers(&mut request);
assert!(
!request.headers().contains_key(*name),
"hop-by-hop header {} was forwarded",
name
);
assert_eq!(request.headers().get("host").unwrap(), "example.com");
}
}
#[test]
fn test_strips_headers_named_in_connection() {
let mut request = Request::builder()
.uri("/test")
.header("host", "example.com")
.header("connection", "keep-alive, X-Hop")
.header("x-hop", "SHOULD-BE-STRIPPED")
.body(empty_body())
.unwrap();
MessageHandler::strip_hop_by_hop_headers(&mut request);
assert!(!request.headers().contains_key("connection"));
assert!(!request.headers().contains_key("x-hop"));
assert_eq!(request.headers().get("host").unwrap(), "example.com");
}
#[test]
fn test_connection_tokens_are_case_insensitive_and_trimmed() {
let mut request = Request::builder()
.uri("/test")
.header("connection", " X-UPPER ,\tx-lower,,")
.header("x-upper", "a")
.header("x-lower", "b")
.body(empty_body())
.unwrap();
MessageHandler::strip_hop_by_hop_headers(&mut request);
assert!(!request.headers().contains_key("x-upper"));
assert!(!request.headers().contains_key("x-lower"));
}
#[test]
fn test_connection_token_cannot_strip_host_or_content_length() {
let mut request = Request::builder()
.uri("/test")
.header("host", "example.com")
.header("content-length", "0")
.header("connection", "host, content-length")
.body(empty_body())
.unwrap();
MessageHandler::strip_hop_by_hop_headers(&mut request);
assert!(!request.headers().contains_key("connection"));
assert_eq!(request.headers().get("host").unwrap(), "example.com");
assert_eq!(request.headers().get("content-length").unwrap(), "0");
}
#[test]
fn test_connection_token_naming_forwarded_header_does_not_suppress_ours() {
let mut request = Request::builder()
.uri("/test")
.header("host", "example.com")
.header("connection", "x-forwarded-for")
.header("x-forwarded-for", "1.2.3.4")
.body(empty_body())
.unwrap();
MessageHandler::strip_hop_by_hop_headers(&mut request);
let client_addr: SocketAddr = "192.168.1.100:12345".parse().unwrap();
MessageHandler::add_forwarding_headers(&mut request, client_addr, "http");
assert_eq!(
request.headers().get("x-forwarded-for").unwrap(),
"192.168.1.100"
);
}
#[test]
fn test_strip_is_a_no_op_without_hop_by_hop_headers() {
let mut request = Request::builder()
.uri("/test")
.header("host", "example.com")
.header("accept", "*/*")
.body(empty_body())
.unwrap();
MessageHandler::strip_hop_by_hop_headers(&mut request);
assert_eq!(request.headers().len(), 2);
}
#[test]
fn test_forwarding_headers() {
let mut request = Request::builder()
.uri("/test")
.header("host", "example.com")
.body(empty_body())
.unwrap();
let client_addr: SocketAddr = "192.168.1.100:12345".parse().unwrap();
MessageHandler::add_forwarding_headers(&mut request, client_addr, "https");
assert_eq!(
request.headers().get("x-forwarded-for").unwrap(),
"192.168.1.100"
);
assert_eq!(request.headers().get("x-forwarded-proto").unwrap(), "https");
assert_eq!(
request.headers().get("x-forwarded-host").unwrap(),
"example.com"
);
}
}