use std::fmt::Display;
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub(crate) struct Headers(Vec<HttpHeader>);
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct HttpHeader {
pub name: HttpHeaderName,
pub value: String,
}
impl Headers {
pub fn new() -> Self {
Self::default()
}
pub fn from(headers: Vec<HttpHeader>) -> Self {
Self(headers)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn add(&mut self, name: impl AsRef<str>, value: impl ToString) {
self.0.push(HttpHeader::new(name, value));
}
pub fn push(&mut self, header: HttpHeader) {
self.0.push(header);
}
pub fn get(&self, name: impl AsRef<str>) -> Option<&str> {
self.0.iter().find(|h| h.name == name.as_ref().into()).map(|h| h.value.as_str())
}
pub fn set(&mut self, name: impl AsRef<str>, value: impl ToString) {
self.remove(&name);
self.add(name, value);
}
pub fn try_set(&mut self, header: impl AsRef<str>, value: impl ToString) -> Option<&str> {
if self.get(&header).is_some() {
return self.get(header);
}
self.0.push(HttpHeader::new(header, value));
None
}
pub fn replace_all(&mut self, name: impl AsRef<str>, value: impl ToString) -> Vec<HttpHeader> {
let mut hcopy = Vec::with_capacity(self.len());
let mut hrem = Vec::new();
std::mem::swap(&mut self.0, &mut hcopy);
for h in hcopy {
if h.name == name.as_ref().into() {
hrem.push(h);
continue;
}
self.0.push(h);
}
self.0.push(HttpHeader::new(name, value));
hrem
}
pub fn get_all(&self, name: impl AsRef<str>) -> Vec<&str> {
self.0.iter().filter(|h| h.name == name.as_ref().into()).map(|h| h.value.as_str()).collect()
}
pub fn remove(&mut self, name: impl AsRef<str>) {
self.0.retain(|h| h.name != name.as_ref().into());
}
pub fn iter(&self) -> impl Iterator<Item = &HttpHeader> {
self.0.iter()
}
}
impl HttpHeader {
pub fn new(name: impl AsRef<str>, value: impl ToString) -> Self {
Self { name: HttpHeaderName::from(name.as_ref()), value: value.to_string() }
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum HttpHeaderName {
Accept,
AcceptCharset,
AcceptEncoding,
AcceptLanguage,
AccessControlRequestMethod,
AccessControlRequestHeaders,
Authorization,
CacheControl,
Connection,
ContentEncoding,
ContentLength,
ContentType,
Cookie,
Date,
Expect,
Forwarded,
From,
Host,
Origin,
Pragma,
Referer,
Upgrade,
UserAgent,
Via,
Warning,
AccessControlAllowOrigin,
AccessControlAllowHeaders,
AccessControlAllowMethods,
Age,
Allow,
ContentDisposition,
ContentLanguage,
ContentLocation,
ETag,
Expires,
LastModified,
Link,
Location,
Server,
SetCookie,
TransferEncoding,
TE,
Trailer,
ProxyAuthenticate,
Custom(String),
}
static WELL_KNOWN: &[HttpHeaderName] = &[
HttpHeaderName::Accept,
HttpHeaderName::Accept,
HttpHeaderName::AcceptCharset,
HttpHeaderName::AcceptEncoding,
HttpHeaderName::AcceptLanguage,
HttpHeaderName::AccessControlRequestMethod,
HttpHeaderName::AccessControlRequestHeaders,
HttpHeaderName::Authorization,
HttpHeaderName::CacheControl,
HttpHeaderName::Connection,
HttpHeaderName::ContentEncoding,
HttpHeaderName::ContentLength,
HttpHeaderName::ContentType,
HttpHeaderName::Cookie,
HttpHeaderName::Date,
HttpHeaderName::Expect,
HttpHeaderName::Forwarded,
HttpHeaderName::From,
HttpHeaderName::Host,
HttpHeaderName::Origin,
HttpHeaderName::Pragma,
HttpHeaderName::Referer,
HttpHeaderName::Upgrade,
HttpHeaderName::UserAgent,
HttpHeaderName::Via,
HttpHeaderName::Warning,
HttpHeaderName::AccessControlAllowOrigin,
HttpHeaderName::AccessControlAllowHeaders,
HttpHeaderName::AccessControlAllowMethods,
HttpHeaderName::Age,
HttpHeaderName::Allow,
HttpHeaderName::ContentDisposition,
HttpHeaderName::ContentLanguage,
HttpHeaderName::ContentLocation,
HttpHeaderName::ETag,
HttpHeaderName::Expires,
HttpHeaderName::LastModified,
HttpHeaderName::Link,
HttpHeaderName::Location,
HttpHeaderName::Server,
HttpHeaderName::SetCookie,
HttpHeaderName::TransferEncoding,
HttpHeaderName::Trailer,
HttpHeaderName::TE,
HttpHeaderName::ProxyAuthenticate,
];
impl HttpHeaderName {
#[must_use]
pub fn well_known() -> &'static [HttpHeaderName] {
WELL_KNOWN
}
#[must_use]
pub fn is_custom(&self) -> bool {
self.well_known_str().is_none()
}
#[must_use]
pub fn is_well_known(&self) -> bool {
self.well_known_str().is_some()
}
#[must_use]
pub fn to_str(&self) -> &str {
match self {
HttpHeaderName::Accept => "Accept",
HttpHeaderName::AcceptCharset => "Accept-Charset",
HttpHeaderName::AcceptEncoding => "Accept-Encoding",
HttpHeaderName::AcceptLanguage => "Accept-Language",
HttpHeaderName::AccessControlRequestMethod => "Access-Control-Request-Method",
HttpHeaderName::AccessControlRequestHeaders => "Access-Control-Request-Headers",
HttpHeaderName::Authorization => "Authorization",
HttpHeaderName::CacheControl => "Cache-Control",
HttpHeaderName::Connection => "Connection",
HttpHeaderName::ContentEncoding => "Content-Encoding",
HttpHeaderName::ContentLength => "Content-Length",
HttpHeaderName::ContentType => "Content-Type",
HttpHeaderName::Cookie => "Cookie",
HttpHeaderName::Date => "Date",
HttpHeaderName::Expect => "Expect",
HttpHeaderName::Forwarded => "Forwarded",
HttpHeaderName::From => "From",
HttpHeaderName::Host => "Host",
HttpHeaderName::Origin => "Origin",
HttpHeaderName::Pragma => "Pragma",
HttpHeaderName::Referer => "Referer",
HttpHeaderName::Upgrade => "Upgrade",
HttpHeaderName::UserAgent => "User-Agent",
HttpHeaderName::Via => "Via",
HttpHeaderName::Warning => "Warning",
HttpHeaderName::AccessControlAllowOrigin => "Access-Control-Allow-Origin",
HttpHeaderName::AccessControlAllowHeaders => "Access-Control-Allow-Headers",
HttpHeaderName::AccessControlAllowMethods => "Access-Control-Allow-Methods",
HttpHeaderName::Age => "Age",
HttpHeaderName::Allow => "Allow",
HttpHeaderName::ContentDisposition => "Content-Disposition",
HttpHeaderName::ContentLanguage => "Content-Language",
HttpHeaderName::ContentLocation => "Content-Location",
HttpHeaderName::ETag => "ETag",
HttpHeaderName::Expires => "Expires",
HttpHeaderName::LastModified => "Last-Modified",
HttpHeaderName::Link => "Link",
HttpHeaderName::Location => "Location",
HttpHeaderName::Server => "Server",
HttpHeaderName::SetCookie => "Set-Cookie",
HttpHeaderName::TransferEncoding => "Transfer-Encoding",
HttpHeaderName::ProxyAuthenticate => "Proxy-Authenticate",
HttpHeaderName::TE => "TE",
HttpHeaderName::Trailer => "Trailer",
HttpHeaderName::Custom(name) => name.as_str(),
}
}
#[must_use]
pub fn well_known_str(&self) -> Option<&'static str> {
Some(match self {
HttpHeaderName::Accept => "Accept",
HttpHeaderName::AcceptCharset => "Accept-Charset",
HttpHeaderName::AcceptEncoding => "Accept-Encoding",
HttpHeaderName::AcceptLanguage => "Accept-Language",
HttpHeaderName::AccessControlRequestMethod => "Access-Control-Request-Method",
HttpHeaderName::AccessControlRequestHeaders => "Access-Control-Request-Headers",
HttpHeaderName::Authorization => "Authorization",
HttpHeaderName::CacheControl => "Cache-Control",
HttpHeaderName::Connection => "Connection",
HttpHeaderName::ContentEncoding => "Content-Encoding",
HttpHeaderName::ContentLength => "Content-Length",
HttpHeaderName::ContentType => "Content-Type",
HttpHeaderName::Cookie => "Cookie",
HttpHeaderName::Date => "Date",
HttpHeaderName::Expect => "Expect",
HttpHeaderName::Forwarded => "Forwarded",
HttpHeaderName::From => "From",
HttpHeaderName::Host => "Host",
HttpHeaderName::Origin => "Origin",
HttpHeaderName::Pragma => "Pragma",
HttpHeaderName::Referer => "Referer",
HttpHeaderName::Upgrade => "Upgrade",
HttpHeaderName::UserAgent => "User-Agent",
HttpHeaderName::Via => "Via",
HttpHeaderName::Warning => "Warning",
HttpHeaderName::AccessControlAllowOrigin => "Access-Control-Allow-Origin",
HttpHeaderName::AccessControlAllowHeaders => "Access-Control-Allow-Headers",
HttpHeaderName::AccessControlAllowMethods => "Access-Control-Allow-Methods",
HttpHeaderName::Age => "Age",
HttpHeaderName::Allow => "Allow",
HttpHeaderName::ContentDisposition => "Content-Disposition",
HttpHeaderName::ContentLanguage => "Content-Language",
HttpHeaderName::ContentLocation => "Content-Location",
HttpHeaderName::ETag => "ETag",
HttpHeaderName::Expires => "Expires",
HttpHeaderName::LastModified => "Last-Modified",
HttpHeaderName::Link => "Link",
HttpHeaderName::Location => "Location",
HttpHeaderName::Server => "Server",
HttpHeaderName::SetCookie => "Set-Cookie",
HttpHeaderName::TransferEncoding => "Transfer-Encoding",
HttpHeaderName::ProxyAuthenticate => "Proxy-Authenticate",
HttpHeaderName::Trailer => "Trailer",
HttpHeaderName::TE => "TE",
HttpHeaderName::Custom(_) => return None,
})
}
}
impl PartialOrd for HttpHeaderName {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HttpHeaderName {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.to_str().cmp(other.to_str())
}
}
impl From<&str> for HttpHeaderName {
fn from(name: &str) -> Self {
let mut stack_buffer = [0u8; 64];
match crate::util::ascii_to_lower_first_n(&mut stack_buffer, name) {
"accept" => Self::Accept,
"accept-charset" => Self::AcceptCharset,
"accept-encoding" => Self::AcceptEncoding,
"accept-language" => Self::AcceptLanguage,
"access-control-request-method" => Self::AccessControlRequestMethod,
"access-control-request-headers" => Self::AccessControlRequestHeaders,
"authorization" => Self::Authorization,
"cache-control" => Self::CacheControl,
"connection" => Self::Connection,
"content-encoding" => Self::ContentEncoding,
"content-length" => Self::ContentLength,
"content-type" => Self::ContentType,
"cookie" => Self::Cookie,
"date" => Self::Date,
"expect" => Self::Expect,
"forwarded" => Self::Forwarded,
"from" => Self::From,
"host" => Self::Host,
"origin" => Self::Origin,
"pragma" => Self::Pragma,
"referer" => Self::Referer,
"upgrade" => Self::Upgrade,
"user-agent" => Self::UserAgent,
"via" => Self::Via,
"warning" => Self::Warning,
"access-control-allow-origin" => Self::AccessControlAllowOrigin,
"access-control-allow-headers" => Self::AccessControlAllowHeaders,
"access-control-allow-methods" => Self::AccessControlAllowMethods,
"age" => Self::Age,
"allow" => Self::Allow,
"content-disposition" => Self::ContentDisposition,
"content-language" => Self::ContentLanguage,
"content-location" => Self::ContentLocation,
"etag" => Self::ETag,
"expires" => Self::Expires,
"last-modified" => Self::LastModified,
"link" => Self::Link,
"location" => Self::Location,
"server" => Self::Server,
"set-cookie" => Self::SetCookie,
"transfer-encoding" => Self::TransferEncoding,
"proxy-authenticate" => Self::ProxyAuthenticate,
"te" => Self::TE,
"trailer" => Self::Trailer,
_ => Self::Custom(name.to_string()),
}
}
}
impl Display for HttpHeaderName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.to_str())
}
}
impl AsRef<str> for HttpHeaderName {
fn as_ref(&self) -> &str {
self.to_str()
}
}
#[test]
fn test_header_replace_all() {
let mut n = Headers::new();
assert!(n.0.is_empty());
n.add("Some", "Header");
n.add("Another", "Value");
n.add("Another", "Meep");
n.add("Mop", "Dop");
let mut it = n.iter();
assert_eq!(HttpHeader::new("Some", "Header"), it.next().unwrap().clone());
assert_eq!(HttpHeader::new("Another", "Value"), it.next().unwrap().clone());
assert_eq!(HttpHeader::new("Another", "Meep"), it.next().unwrap().clone());
assert_eq!(HttpHeader::new("Mop", "Dop"), it.next().unwrap().clone());
assert!(it.next().is_none());
drop(it);
let rmoved = n.replace_all("Another", "Friend");
let mut it = n.iter();
assert_eq!(HttpHeader::new("Some", "Header"), it.next().unwrap().clone());
assert_eq!(HttpHeader::new("Mop", "Dop"), it.next().unwrap().clone());
assert_eq!(HttpHeader::new("Another", "Friend"), it.next().unwrap().clone());
assert!(it.next().is_none());
let mut it = rmoved.iter();
assert_eq!(HttpHeader::new("Another", "Value"), it.next().unwrap().clone());
assert_eq!(HttpHeader::new("Another", "Meep"), it.next().unwrap().clone());
assert!(it.next().is_none());
}