use axum::http::HeaderMap;
use axum::http::header;
pub const MAX_COOKIE_VALUE_BYTES: usize = 4096;
pub fn extract_named_cookie(
headers: &HeaderMap,
name: &str,
max_value_bytes: usize,
) -> Option<String> {
let raw = headers.get(header::COOKIE)?.to_str().ok()?;
for pair in raw.split(';') {
let pair = pair.trim();
let mut split = pair.splitn(2, '=');
let Some(pair_name) = split.next().map(str::trim) else {
continue;
};
let Some(value) = split.next().map(str::trim) else {
continue;
};
if pair_name != name {
continue;
}
if value.len() > max_value_bytes {
tracing::warn!(
cookie_name = name,
value_bytes = value.len(),
cap_bytes = max_value_bytes,
"cookie value exceeds DoS cap, rejecting"
);
return None;
}
return Some(value.to_string());
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use axum::http::HeaderValue;
fn headers_with_cookie(cookie: &str) -> HeaderMap {
let mut h = HeaderMap::new();
h.insert(header::COOKIE, HeaderValue::from_str(cookie).unwrap());
h
}
#[test]
fn returns_none_when_header_absent() {
let h = HeaderMap::new();
assert_eq!(extract_named_cookie(&h, "axess.sid", 4096), None);
}
#[test]
fn extracts_single_named_cookie() {
let h = headers_with_cookie("axess.sid=abc123");
assert_eq!(
extract_named_cookie(&h, "axess.sid", 4096),
Some("abc123".to_string())
);
}
#[test]
fn extracts_named_cookie_among_many() {
let h = headers_with_cookie("foo=1; axess.sid=abc123; bar=hello");
assert_eq!(
extract_named_cookie(&h, "axess.sid", 4096),
Some("abc123".to_string())
);
}
#[test]
fn whitespace_around_pairs_is_tolerated() {
let h = headers_with_cookie(" foo=1; axess.sid = abc123 ; bar=hello ");
assert_eq!(
extract_named_cookie(&h, "axess.sid", 4096),
Some("abc123".to_string())
);
}
#[test]
fn returns_none_for_unmatched_name() {
let h = headers_with_cookie("foo=1; bar=2");
assert_eq!(extract_named_cookie(&h, "axess.sid", 4096), None);
}
#[test]
fn rejects_value_exceeding_cap() {
let h = headers_with_cookie("axess.sid=12345");
assert_eq!(extract_named_cookie(&h, "axess.sid", 4), None);
}
#[test]
fn cap_at_exact_boundary_accepts() {
let h = headers_with_cookie("axess.sid=1234");
assert_eq!(
extract_named_cookie(&h, "axess.sid", 4),
Some("1234".to_string())
);
}
#[test]
fn malformed_pair_without_equals_skipped() {
let h = headers_with_cookie("noequals; axess.sid=abc");
assert_eq!(
extract_named_cookie(&h, "axess.sid", 4096),
Some("abc".to_string())
);
}
#[test]
fn malformed_pair_trailing_still_finds_earlier_match() {
let h = headers_with_cookie("axess.sid=abc; noequals");
assert_eq!(
extract_named_cookie(&h, "axess.sid", 4096),
Some("abc".to_string())
);
}
#[test]
fn empty_value_is_returned_as_empty_string() {
let h = headers_with_cookie("axess.sid=");
assert_eq!(
extract_named_cookie(&h, "axess.sid", 4096),
Some(String::new())
);
}
}