use crate::{Error, HeaderDecode, HeaderEncode, TypedHeader};
use rama_core::telemetry::tracing;
use rama_http_types::header;
use rama_http_types::{HeaderName, HeaderValue};
use rama_net::address::Host;
use rama_net::forwarded::{ForwardedAuthority, ForwardedElement};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XForwardedHost(ForwardedAuthority);
impl TypedHeader for XForwardedHost {
fn name() -> &'static HeaderName {
&header::X_FORWARDED_HOST
}
}
impl HeaderDecode for XForwardedHost {
fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
Ok(Self(
values
.next()
.and_then(|value| value.to_str().ok().and_then(|s| s.parse().ok()))
.ok_or_else(Error::invalid)?,
))
}
}
impl HeaderEncode for XForwardedHost {
fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
let s = self.0.to_string();
match HeaderValue::try_from(s) {
Ok(value) => values.extend(::std::iter::once(value)),
Err(err) => {
tracing::debug!("failed to encode x-forwarded-host as header value: {err}")
}
}
}
}
impl XForwardedHost {
#[inline]
#[must_use]
pub fn host(&self) -> &Host {
&self.0.0.host
}
#[inline]
#[must_use]
pub fn port(&self) -> Option<u16> {
self.0.0.port.as_u16()
}
#[must_use]
pub fn inner(&self) -> &ForwardedAuthority {
&self.0
}
#[must_use]
pub fn into_inner(self) -> ForwardedAuthority {
self.0
}
}
impl IntoIterator for XForwardedHost {
type Item = ForwardedElement;
type IntoIter = XForwardedHostIterator;
fn into_iter(self) -> Self::IntoIter {
XForwardedHostIterator(Some(self.0))
}
}
impl super::ForwardHeader for XForwardedHost {
fn try_from_forwarded<'a, I>(input: I) -> Option<Self>
where
I: IntoIterator<Item = &'a ForwardedElement>,
{
let el = input.into_iter().next()?;
let host = el.forwarded_host().cloned()?;
Some(Self(host))
}
}
#[derive(Debug, Clone)]
pub struct XForwardedHostIterator(Option<ForwardedAuthority>);
impl Iterator for XForwardedHostIterator {
type Item = ForwardedElement;
fn next(&mut self) -> Option<Self::Item> {
self.0.take().map(ForwardedElement::new_forwarded_host)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rama_http_types::HeaderValue;
macro_rules! test_header {
($name: ident, $input: expr, $expected: expr) => {
#[test]
fn $name() {
assert_eq!(
XForwardedHost::decode(
&mut $input
.into_iter()
.map(|s| HeaderValue::from_bytes(s.as_bytes()).unwrap())
.collect::<Vec<_>>()
.iter()
)
.ok(),
$expected,
);
}
};
}
test_header!(
test1,
vec!["id42.example-cdn.com"],
Some(XForwardedHost("id42.example-cdn.com".parse().unwrap()))
);
test_header!(
test2,
vec!["id42.example-cdn.com", "example.com"],
Some(XForwardedHost("id42.example-cdn.com".parse().unwrap()))
);
test_header!(
test3,
vec!["id42.example-cdn.com:443"],
Some(XForwardedHost("id42.example-cdn.com:443".parse().unwrap()))
);
test_header!(
test4,
vec!["203.0.113.195"],
Some(XForwardedHost("203.0.113.195".parse().unwrap()))
);
test_header!(
test5,
vec!["203.0.113.195:80"],
Some(XForwardedHost("203.0.113.195:80".parse().unwrap()))
);
test_header!(
test6,
vec!["2001:db8:85a3:8d3:1319:8a2e:370:7348"],
Some(XForwardedHost(
"2001:db8:85a3:8d3:1319:8a2e:370:7348".parse().unwrap()
))
);
test_header!(
test7,
vec!["[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080"],
Some(XForwardedHost(
"[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080"
.parse()
.unwrap()
))
);
#[test]
fn test_x_forwarded_host_symmetry_encode() {
for input in [
XForwardedHost("id42.example-cdn.com".parse().unwrap()),
XForwardedHost("id42.example-cdn.com:443".parse().unwrap()),
XForwardedHost("127.0.0.1".parse().unwrap()),
] {
let mut values = Vec::new();
input.encode(&mut values);
assert_eq!(XForwardedHost::decode(&mut values.iter()).unwrap(), input);
}
}
}