use std::net::IpAddr;
use http::header::{
CONNECTION, CONTENT_LENGTH, FORWARDED, HeaderName, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION,
SET_COOKIE, TE, TRAILER, TRANSFER_ENCODING, UPGRADE,
};
use http::{HeaderMap, HeaderValue, Uri};
pub const LAMBDA_REQUEST_ID_HEADER: HeaderName =
HeaderName::from_static("lambda-runtime-aws-request-id");
const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
const X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host");
const X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip");
const HOP_BY_HOP: [HeaderName; 9] = [
CONNECTION,
HeaderName::from_static("keep-alive"),
HeaderName::from_static("proxy-connection"),
TRANSFER_ENCODING,
TE,
TRAILER,
UPGRADE,
PROXY_AUTHENTICATE,
PROXY_AUTHORIZATION,
];
pub fn strip_hop_by_hop(headers: &mut HeaderMap) {
let listed: Vec<HeaderName> = headers
.get_all(CONNECTION)
.iter()
.flat_map(|value| value.as_bytes().split(|b| *b == b','))
.filter_map(|token| HeaderName::from_bytes(token.trim_ascii()).ok())
.collect();
for name in HOP_BY_HOP.iter().chain(&listed) {
headers.remove(name);
}
}
pub fn prepare_request_headers(headers: &mut HeaderMap, request_id: Option<&str>) {
for name in HOP_BY_HOP
.iter()
.chain(&[CONTENT_LENGTH, X_FORWARDED_HOST, X_REAL_IP, FORWARDED])
{
headers.remove(name);
}
headers.remove(LAMBDA_REQUEST_ID_HEADER);
if let Some(value) = request_id.and_then(|id| HeaderValue::from_str(id).ok()) {
headers.insert(LAMBDA_REQUEST_ID_HEADER, value);
}
}
#[must_use]
pub fn forwarded_client_ip(source_ip: Option<&str>, forwarded_for: Option<&str>) -> Option<IpAddr> {
match source_ip {
Some(ip) => ip.trim().parse().ok(),
None => forwarded_for?.rsplit(',').next()?.trim().parse().ok(),
}
}
pub fn set_forwarded_for(headers: &mut HeaderMap, source_ip: Option<&str>) {
let forwarded_for = headers
.get(X_FORWARDED_FOR)
.and_then(|v| v.to_str().ok())
.map(ToOwned::to_owned);
headers.remove(X_FORWARDED_FOR);
if let Some(ip) = forwarded_client_ip(source_ip, forwarded_for.as_deref())
&& let Ok(value) = HeaderValue::from_str(&ip.to_string())
{
headers.insert(X_FORWARDED_FOR, value);
}
}
pub fn fold_repeated_headers(headers: &mut HeaderMap) {
let repeated: Vec<HeaderName> = headers
.keys()
.filter(|name| **name != SET_COOKIE && headers.get_all(*name).iter().nth(1).is_some())
.cloned()
.collect();
for name in repeated {
let joined = headers
.get_all(&name)
.iter()
.map(HeaderValue::as_bytes)
.collect::<Vec<_>>()
.join(&b", "[..]);
if let Ok(value) = HeaderValue::from_bytes(&joined) {
headers.insert(name, value);
}
}
}
#[must_use]
pub fn text_body_is_lossless(content_type: Option<&str>, body: &[u8]) -> bool {
if body.is_ascii() {
return true;
}
let charset = content_type.and_then(|ct| {
ct.split(';').skip(1).find_map(|param| {
let (key, value) = param.split_once('=')?;
key.trim()
.eq_ignore_ascii_case("charset")
.then(|| value.trim().trim_matches('"').to_ascii_lowercase())
})
});
matches!(charset.as_deref(), None | Some("utf-8" | "utf8")) && std::str::from_utf8(body).is_ok()
}
#[must_use]
pub fn upstream_path_and_query(raw_path: Option<&str>, uri: &Uri) -> String {
let mut out = String::new();
match raw_path {
Some(raw) => {
if !raw.starts_with('/') {
out.push('/');
}
encode_path(raw, &mut out);
}
None => out.push_str(uri.path()),
}
if let Some(query) = uri.query().filter(|q| !q.is_empty()) {
out.push('?');
out.push_str(query);
}
out
}
fn encode_path(raw: &str, out: &mut String) {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
let bytes = raw.as_bytes();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == b'%'
&& bytes.len() - i > 2
&& bytes[i + 1].is_ascii_hexdigit()
&& bytes[i + 2].is_ascii_hexdigit()
{
out.push_str(&raw[i..i + 3]);
i += 3;
} else if is_path_byte(b) {
out.push(char::from(b));
i += 1;
} else {
out.push('%');
out.push(char::from(HEX[usize::from(b / 16)]));
out.push(char::from(HEX[usize::from(b % 16)]));
i += 1;
}
}
}
const fn is_path_byte(b: u8) -> bool {
b.is_ascii_alphanumeric()
|| matches!(
b,
b'-' | b'.'
| b'_'
| b'~'
| b'!'
| b'$'
| b'&'
| b'\''
| b'('
| b')'
| b'*'
| b'+'
| b','
| b';'
| b'='
| b':'
| b'@'
| b'/'
)
}