use std::collections::HashMap;
use std::fmt;
use std::hash::{BuildHasherDefault, Hasher};
use crate::helpers::huffman;
use crate::helpers::text::Text;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeaderField {
pub name: Text,
pub value: Text,
}
impl HeaderField {
pub const SENSITIVE: &'static [&'static str] = &["authorization", "proxy-authorization", "cookie", "set-cookie"];
pub const OVERHEAD: usize = 32;
pub const SECTION_FLOOR: usize = 8;
pub const SECTION_SPARE: usize = 1;
pub const SECTION_CEILING: usize = 128;
pub fn new(name: impl Into<Text>, value: impl Into<Text>) -> Self {
Self { name: name.into(), value: value.into() }
}
pub fn section_hint(previous: usize) -> usize {
previous.saturating_add(Self::SECTION_SPARE).clamp(Self::SECTION_FLOOR, Self::SECTION_CEILING)
}
pub fn size(&self) -> usize {
self.name.len() + self.value.len() + Self::OVERHEAD
}
pub fn sensitive(&self) -> bool {
matches!(self.name.len(), 6 | 10 | 13 | 19) && Self::SENSITIVE.contains(&self.name.as_str())
}
}
#[derive(Default)]
pub struct FieldHasher(u64);
impl Hasher for FieldHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
let mut hash = if self.0 == 0 { 0xcbf2_9ce4_8422_2325 } else { self.0 };
for byte in bytes {
hash ^= *byte as u64;
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
self.0 = hash;
}
}
pub type FieldMap<K, V> = HashMap<K, V, BuildHasherDefault<FieldHasher>>;
pub struct NameEntry {
pub first: usize,
pub values: Vec<(&'static str, usize)>,
}
pub struct StaticIndex {
by_name: FieldMap<&'static str, NameEntry>,
}
impl StaticIndex {
pub fn new(entries: &'static [HeaderField], base: usize) -> Self {
let mut by_name: FieldMap<&'static str, NameEntry> = FieldMap::default();
for (offset, entry) in entries.iter().enumerate() {
let index = base + offset;
by_name
.entry(entry.name.as_str())
.or_insert_with(|| NameEntry { first: index, values: Vec::new() })
.values
.push((entry.value.as_str(), index));
}
Self { by_name }
}
pub fn lookup(&self, name: &str, value: &str) -> (Option<usize>, Option<usize>) {
let Some(entry) = self.by_name.get(name) else {
return (None, None);
};
let exact = entry.values.iter().find(|(candidate, _)| *candidate == value).map(|(_, index)| *index);
(Some(entry.first), exact)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
IntegerOverflow,
Incomplete,
Huffman(huffman::DecodeError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IntegerOverflow => write!(f, "integer representation overflowed"),
Self::Incomplete => write!(f, "representation ends before the input does"),
Self::Huffman(err) => write!(f, "huffman error: {err}"),
}
}
}
impl std::error::Error for Error {}
impl From<huffman::DecodeError> for Error {
fn from(err: huffman::DecodeError) -> Self {
Self::Huffman(err)
}
}
pub struct Integer;
impl Integer {
pub fn limit(prefix_bits: u8) -> u64 {
(1u64 << prefix_bits.min(63)) - 1
}
pub fn encode(out: &mut Vec<u8>, value: u64, prefix_bits: u8, flags: u8) {
let limit = Self::limit(prefix_bits);
if value < limit {
out.push(flags | value as u8);
return;
}
out.push(flags | limit as u8);
let mut rest = value - limit;
while rest >= 128 {
out.push((rest % 128) as u8 | 0x80);
rest /= 128;
}
out.push(rest as u8);
}
pub fn decode(input: &[u8], prefix_bits: u8) -> Result<(usize, u64), Error> {
let limit = Self::limit(prefix_bits);
let first = *input.first().ok_or(Error::Incomplete)?;
let mut value = first as u64 & limit;
if value < limit {
return Ok((1, value));
}
let mut consumed = 1;
let mut shift = 0;
loop {
let octet = *input.get(consumed).ok_or(Error::Incomplete)?;
consumed += 1;
value = (octet as u64 & 0x7f)
.checked_shl(shift)
.and_then(|part| value.checked_add(part))
.ok_or(Error::IntegerOverflow)?;
if octet & 0x80 == 0 {
return Ok((consumed, value));
}
shift += 7;
if shift >= 64 {
return Err(Error::IntegerOverflow);
}
}
}
}
pub struct StringLiteral;
impl StringLiteral {
pub fn encode(out: &mut Vec<u8>, value: &[u8], prefix_bits: u8, flags: u8, huffman: bool) {
if huffman {
let encoded = huffman::encoded_len(value);
Integer::encode(out, encoded as u64, prefix_bits, flags | 1 << prefix_bits);
huffman::encode_sized(value, encoded, out);
} else {
Integer::encode(out, value.len() as u64, prefix_bits, flags);
out.extend_from_slice(value);
}
}
pub fn encode_shorter(out: &mut Vec<u8>, value: &[u8], prefix_bits: u8, flags: u8) {
let encoded = huffman::encoded_len(value);
if encoded < value.len() {
Integer::encode(out, encoded as u64, prefix_bits, flags | 1 << prefix_bits);
huffman::encode_sized(value, encoded, out);
} else {
Integer::encode(out, value.len() as u64, prefix_bits, flags);
out.extend_from_slice(value);
}
}
pub fn prefers_huffman(value: &[u8]) -> bool {
huffman::encoded_len(value) < value.len()
}
pub fn decode(input: &[u8], prefix_bits: u8) -> Result<(usize, Vec<u8>), Error> {
let mut value = Vec::new();
let consumed = Self::decode_into(input, prefix_bits, &mut value)?;
Ok((consumed, value))
}
pub fn decode_into(input: &[u8], prefix_bits: u8, scratch: &mut Vec<u8>) -> Result<usize, Error> {
Self::decode_into_ascii(input, prefix_bits, scratch).map(|(consumed, _)| consumed)
}
pub fn decode_into_ascii(input: &[u8], prefix_bits: u8, scratch: &mut Vec<u8>) -> Result<(usize, bool), Error> {
let huffman = input.first().ok_or(Error::Incomplete)? & 1 << prefix_bits != 0;
let (prefix, length) = Integer::decode(input, prefix_bits)?;
let length = length as usize;
let end = prefix.checked_add(length).ok_or(Error::Incomplete)?;
let octets = input.get(prefix..end).ok_or(Error::Incomplete)?;
scratch.clear();
let ascii = if huffman {
huffman::decode_into_ascii(octets, scratch)?
} else {
scratch.extend_from_slice(octets);
octets.is_ascii()
};
Ok((end, ascii))
}
pub fn decode_text(input: &[u8], prefix_bits: u8) -> Result<(usize, Text), Error> {
let mut scratch = Vec::new();
Self::decode_text_into(input, prefix_bits, &mut scratch)
}
pub fn decode_text_into(input: &[u8], prefix_bits: u8, scratch: &mut Vec<u8>) -> Result<(usize, Text), Error> {
let (consumed, ascii) = Self::decode_into_ascii(input, prefix_bits, scratch)?;
Ok((consumed, Self::text(scratch, ascii)))
}
#[inline]
pub fn text(octets: &[u8], ascii: bool) -> Text {
match ascii {
true => Text::from_verified_ascii(octets),
false => Text::from_utf8_lossy(octets),
}
}
}