use std::borrow::{Borrow, Cow};
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::error;
use std::fmt;
use crate::message_ref::HeaderRef;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Headers(pub(crate) BTreeMap<HeaderName, HeaderValue>);
impl Headers {
pub(crate) fn new() -> Headers {
Headers(BTreeMap::new())
}
pub(crate) fn from_headers_ref<'a, V: AsRef<[HeaderRef<'a>]>>(headers: V) -> Headers {
let headers = headers.as_ref();
let mut owned_headers = Headers::new();
for header in headers.iter() {
let mut value = Vec::with_capacity(header.value.len());
let mut raw_value = header.value.as_bytes();
while !raw_value.is_empty() {
if raw_value.starts_with(b"\r\n") {
raw_value = raw_value.split_at(2).1;
if let Some((non_space_pos, _)) = raw_value
.iter()
.enumerate()
.find(|(_, b)| **b != b' ' && **b != b'\t')
{
value.push(b' ');
raw_value = raw_value.split_at(non_space_pos).1;
} else {
raw_value = &[];
}
} else {
value.push(raw_value[0]);
raw_value = raw_value.split_at(1).1;
}
}
let name = HeaderName::try_from(header.name).expect("Non-ASCII characters");
let value = String::from_utf8(value).expect("Non-UTF8 characters");
owned_headers.append(name, HeaderValue::from(value));
}
owned_headers
}
pub fn insert<V: Into<HeaderValue>>(&mut self, name: HeaderName, value: V) {
let value = value.into();
self.0.insert(name, value);
}
pub fn append<V: Into<HeaderValue>>(&mut self, name: HeaderName, value: V) {
let value = value.into();
self.0
.entry(name)
.and_modify(|old_value| {
old_value.0.push_str(", ");
old_value.0.push_str(&value.0);
})
.or_insert(value);
}
pub fn insert_typed<H: TypedHeader>(&mut self, header: &H) {
header.insert_into(self);
}
pub fn append_typed<H: TypedAppendableHeader>(&mut self, header: &H) {
header.append_to(self);
}
pub fn remove(&mut self, name: &HeaderName) {
self.0.remove(name);
}
pub fn get(&self, name: &HeaderName) -> Option<&HeaderValue> {
self.0.get(name)
}
pub fn get_typed<H: TypedHeader>(&self) -> Result<Option<H>, HeaderParseError> {
H::from_headers(self)
}
pub fn get_mut(&mut self, name: &HeaderName) -> Option<&mut HeaderValue> {
self.0.get_mut(name)
}
pub fn iter(&self) -> impl Iterator<Item = (&HeaderName, &HeaderValue)> {
self.0.iter()
}
pub fn names(&self) -> impl Iterator<Item = &HeaderName> {
self.0.keys()
}
pub fn values(&self) -> impl Iterator<Item = &HeaderValue> {
self.0.values()
}
}
impl AsRef<Headers> for Headers {
fn as_ref(&self) -> &Headers {
self
}
}
impl AsMut<Headers> for Headers {
fn as_mut(&mut self) -> &mut Headers {
self
}
}
#[derive(Debug, Clone, Eq)]
pub struct HeaderName(Cow<'static, str>);
impl HeaderName {
pub fn as_str(&self) -> &str {
self.0.borrow()
}
pub fn from_static_str(v: &'static str) -> Result<HeaderName, AsciiError> {
if !v.is_ascii() {
return Err(AsciiError);
}
Ok(HeaderName(Cow::Borrowed(v)))
}
pub(crate) const fn from_static_str_unchecked(v: &'static str) -> HeaderName {
Self(Cow::Borrowed(v))
}
}
impl<'a> TryFrom<&'a [u8]> for HeaderName {
type Error = AsciiError;
fn try_from(v: &'a [u8]) -> Result<HeaderName, AsciiError> {
if !v.is_ascii() {
return Err(AsciiError);
}
let v = String::from_utf8(v.into()).map_err(|_| AsciiError)?;
Ok(HeaderName(Cow::Owned(v)))
}
}
impl<'a> TryFrom<&'a str> for HeaderName {
type Error = AsciiError;
fn try_from(v: &'a str) -> Result<HeaderName, AsciiError> {
Self::try_from(v.as_bytes())
}
}
impl TryFrom<String> for HeaderName {
type Error = AsciiError;
fn try_from(v: String) -> Result<HeaderName, AsciiError> {
if !v.is_ascii() {
return Err(AsciiError);
}
Ok(HeaderName(Cow::Owned(v)))
}
}
impl PartialEq for HeaderName {
fn eq(&self, other: &Self) -> bool {
self.eq(other.as_str())
}
}
impl PartialOrd for HeaderName {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HeaderName {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let s = self.0.as_bytes();
let o = other.0.as_bytes();
let len = std::cmp::min(s.len(), o.len());
let s = &s[..len];
let o = &o[..len];
for (s, o) in Iterator::zip(s.iter(), o.iter()) {
let mut s = *s;
let mut o = *o;
s.make_ascii_lowercase();
o.make_ascii_lowercase();
match s.cmp(&o) {
std::cmp::Ordering::Equal => (),
non_eq => return non_eq,
}
}
s.len().cmp(&o.len())
}
}
impl std::hash::Hash for HeaderName {
fn hash<H>(&self, h: &mut H)
where
H: std::hash::Hasher,
{
for b in self.0.as_bytes() {
b.hash(h)
}
}
}
impl PartialEq<HeaderName> for &HeaderName {
fn eq(&self, other: &HeaderName) -> bool {
(*self).eq(other)
}
}
impl PartialOrd<HeaderName> for &HeaderName {
fn partial_cmp(&self, other: &HeaderName) -> Option<std::cmp::Ordering> {
(*self).partial_cmp(other)
}
}
impl PartialEq<String> for HeaderName {
fn eq(&self, other: &String) -> bool {
self.eq(other.as_str())
}
}
impl PartialEq<str> for HeaderName {
fn eq(&self, other: &str) -> bool {
if self.0.len() != other.len() {
return false;
}
for (s, o) in Iterator::zip(self.0.as_bytes().iter(), other.as_bytes().iter()) {
let mut s = *s;
let mut o = *o;
s.make_ascii_lowercase();
o.make_ascii_lowercase();
if s != o {
return false;
}
}
true
}
}
impl fmt::Display for HeaderName {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str(self.as_str())
}
}
#[allow(clippy::derive_ord_xor_partial_ord)]
#[allow(clippy::derive_hash_xor_eq)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HeaderValue(String);
impl HeaderValue {
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl From<String> for HeaderValue {
fn from(v: String) -> HeaderValue {
HeaderValue(v)
}
}
impl<'a> From<&'a str> for HeaderValue {
fn from(v: &'a str) -> HeaderValue {
HeaderValue(String::from(v))
}
}
impl<'a> TryFrom<&'a [u8]> for HeaderValue {
type Error = Utf8Error;
fn try_from(v: &'a [u8]) -> Result<HeaderValue, Utf8Error> {
std::str::from_utf8(v)
.map(|s| HeaderValue::from(String::from(s)))
.map_err(|_| Utf8Error)
}
}
impl TryFrom<Vec<u8>> for HeaderValue {
type Error = Utf8Error;
fn try_from(v: Vec<u8>) -> Result<HeaderValue, Utf8Error> {
String::from_utf8(v).map(HeaderValue).map_err(|_| Utf8Error)
}
}
impl PartialEq<HeaderValue> for &HeaderValue {
fn eq(&self, other: &HeaderValue) -> bool {
(*self).eq(other)
}
}
impl PartialOrd<HeaderValue> for &HeaderValue {
fn partial_cmp(&self, other: &HeaderValue) -> Option<std::cmp::Ordering> {
(*self).partial_cmp(other)
}
}
impl PartialEq<String> for HeaderValue {
fn eq(&self, other: &String) -> bool {
self.0.eq(other)
}
}
impl PartialEq<str> for HeaderValue {
fn eq(&self, other: &str) -> bool {
self.0.eq(other)
}
}
impl fmt::Display for HeaderValue {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str(self.0.as_str())
}
}
pub trait TypedHeader: Sized {
fn from_headers(headers: impl AsRef<Headers>) -> Result<Option<Self>, HeaderParseError>;
fn insert_into(&self, headers: impl AsMut<Headers>);
}
pub trait TypedAppendableHeader: TypedHeader {
fn append_to(&self, headers: impl AsMut<Headers>);
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AsciiError;
impl error::Error for AsciiError {}
impl fmt::Display for AsciiError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "Invalid ASCII")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Utf8Error;
impl error::Error for Utf8Error {}
impl fmt::Display for Utf8Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "Invalid UTF-8")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeaderParseError;
impl error::Error for HeaderParseError {}
impl fmt::Display for HeaderParseError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "Error parsing error")
}
}