#[cfg(test)]
mod rfc7541_vectors;
#[cfg(test)]
mod tests;
use super::{
HpackDecodeError,
dynamic_table::{DynamicTable, Entry},
static_table::{StaticHeaderName, static_entry},
};
use crate::{
HeaderName, HeaderValue, Headers, Method, Status,
headers::{
compression_error::CompressionError,
entry_name::{EntryName, PseudoHeaderName},
field_section::{FieldLineValue, FieldSection, PseudoHeaders},
huffman, integer_prefix,
},
};
use std::borrow::Cow;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum MalformedRequest {
DuplicatePseudoHeader,
PseudoHeaderAfterRegular,
}
const INDEXED: u8 = 0b1000_0000; const LITERAL_WITH_INDEXING: u8 = 0b0100_0000; const SIZE_UPDATE: u8 = 0b0010_0000; const LITERAL_NEVER_INDEXED: u8 = 0b0001_0000;
pub(in crate::headers) fn decode(
mut input: &[u8],
table: &mut DynamicTable,
protocol_max_table_size: usize,
) -> Result<FieldSection<'static>, HpackDecodeError> {
let mut pseudo_headers = PseudoHeaders::default();
let mut headers = Headers::new();
let mut size_updates_allowed = true;
let mut saw_regular = false;
let mut malformed: Option<MalformedRequest> = None;
while !input.is_empty() {
let first = input[0];
if first & INDEXED != 0 {
let (index, rest) = integer_prefix::decode(input, 7)?;
if index == 0 {
return Err(CompressionError::InvalidStaticIndex(0).into());
}
size_updates_allowed = false;
input = rest;
emit_indexed(
index,
table,
&mut pseudo_headers,
&mut headers,
&mut saw_regular,
&mut malformed,
)?;
} else if first & LITERAL_WITH_INDEXING != 0 {
let (index, rest) = integer_prefix::decode(input, 6)?;
let (name, value, rest) = read_literal_name_value(rest, index, table)?;
size_updates_allowed = false;
input = rest;
table.insert(name.clone(), value.clone());
emit_literal(
name,
value,
false,
&mut pseudo_headers,
&mut headers,
&mut saw_regular,
&mut malformed,
)?;
} else if first & SIZE_UPDATE != 0 {
if !size_updates_allowed {
return Err(CompressionError::UnexpectedEnd.into());
}
let (new_max, rest) = integer_prefix::decode(input, 5)?;
if new_max > protocol_max_table_size {
return Err(CompressionError::InvalidStaticIndex(new_max).into());
}
input = rest;
table.set_max_size(new_max);
} else if first & LITERAL_NEVER_INDEXED != 0 {
let (index, rest) = integer_prefix::decode(input, 4)?;
let (name, value, rest) = read_literal_name_value(rest, index, table)?;
size_updates_allowed = false;
input = rest;
emit_literal(
name,
value,
true,
&mut pseudo_headers,
&mut headers,
&mut saw_regular,
&mut malformed,
)?;
} else {
let (index, rest) = integer_prefix::decode(input, 4)?;
let (name, value, rest) = read_literal_name_value(rest, index, table)?;
size_updates_allowed = false;
input = rest;
emit_literal(
name,
value,
false,
&mut pseudo_headers,
&mut headers,
&mut saw_regular,
&mut malformed,
)?;
}
}
if let Some(reason) = malformed {
return Err(HpackDecodeError::MalformedRequest(reason));
}
Ok(FieldSection::from_owned(pseudo_headers, headers))
}
fn emit_indexed(
index: usize,
table: &DynamicTable,
pseudo_headers: &mut PseudoHeaders<'static>,
headers: &mut Headers,
saw_regular: &mut bool,
malformed: &mut Option<MalformedRequest>,
) -> Result<(), CompressionError> {
if index <= 61 {
let (name, value) = static_entry(index)?;
let value_bytes = FieldLineValue::Static(value.as_bytes());
emit_from_entry_ref(
*name,
value_bytes,
pseudo_headers,
headers,
saw_regular,
malformed,
)
} else {
let dyn_index = index - 61;
let entry = table
.get(dyn_index)
.ok_or(CompressionError::InvalidStaticIndex(index))?;
emit_from_entry(
entry,
false,
pseudo_headers,
headers,
saw_regular,
malformed,
)
}
}
fn read_literal_name_value<'a>(
input: &'a [u8],
index: usize,
table: &DynamicTable,
) -> Result<(EntryName<'static>, FieldLineValue<'static>, &'a [u8]), CompressionError> {
let (name, rest) = if index == 0 {
let (bytes, rest) = read_string(input)?;
let name = EntryName::try_from(bytes).map_err(|()| CompressionError::InvalidHeaderName)?;
(name, rest)
} else if index <= 61 {
let (name, _) = static_entry(index)?;
(EntryName::from(*name), input)
} else {
let dyn_index = index - 61;
let entry = table
.get(dyn_index)
.ok_or(CompressionError::InvalidStaticIndex(index))?;
(entry.name.clone(), input)
};
let (value_bytes, rest) = read_string(rest)?;
let value = FieldLineValue::Owned(value_bytes);
Ok((name, value, rest))
}
fn read_string(input: &[u8]) -> Result<(Vec<u8>, &[u8]), CompressionError> {
let [first, ..] = input else {
return Err(CompressionError::UnexpectedEnd);
};
let huffman_encoded = first & 0x80 != 0;
let (length, rest) = integer_prefix::decode(input, 7)?;
if rest.len() < length {
return Err(CompressionError::UnexpectedEnd);
}
let (bytes, rest) = rest.split_at(length);
let decoded = if huffman_encoded {
huffman::decode(bytes)?
} else {
bytes.to_vec()
};
Ok((decoded, rest))
}
fn emit_literal(
name: EntryName<'static>,
value: FieldLineValue<'static>,
never_indexed: bool,
pseudo_headers: &mut PseudoHeaders<'static>,
headers: &mut Headers,
saw_regular: &mut bool,
malformed: &mut Option<MalformedRequest>,
) -> Result<(), CompressionError> {
emit_from_entry(
&Entry { name, value },
never_indexed,
pseudo_headers,
headers,
saw_regular,
malformed,
)
}
fn emit_from_entry_ref(
name: StaticHeaderName,
value: FieldLineValue<'static>,
pseudo_headers: &mut PseudoHeaders<'static>,
headers: &mut Headers,
saw_regular: &mut bool,
malformed: &mut Option<MalformedRequest>,
) -> Result<(), CompressionError> {
let entry_name: EntryName<'static> = name.into();
emit_from_entry(
&Entry {
name: entry_name,
value,
},
false,
pseudo_headers,
headers,
saw_regular,
malformed,
)
}
fn emit_from_entry(
entry: &Entry,
never_indexed: bool,
pseudo_headers: &mut PseudoHeaders<'static>,
headers: &mut Headers,
saw_regular: &mut bool,
malformed: &mut Option<MalformedRequest>,
) -> Result<(), CompressionError> {
let value_bytes: &[u8] = entry.value.as_bytes();
let make_value = || {
let mut v = HeaderValue::from(value_bytes.to_vec());
v.set_never_indexed(never_indexed);
v
};
match &entry.name {
EntryName::Known(k) => {
*saw_regular = true;
headers.append(HeaderName::from(*k), make_value());
}
EntryName::Unknown(u) => {
*saw_regular = true;
headers.append(HeaderName::from(u.clone().into_owned()), make_value());
}
EntryName::UnknownStatic(s) => {
*saw_regular = true;
headers.append(HeaderName::from(*s), make_value());
}
EntryName::Pseudo(pseudo) => {
if *saw_regular {
log::trace!("hpack: pseudo-header after regular: {pseudo:?}");
malformed.get_or_insert(MalformedRequest::PseudoHeaderAfterRegular);
}
insert_pseudo(*pseudo, value_bytes, pseudo_headers, malformed)?;
}
}
Ok(())
}
fn insert_pseudo(
pseudo: PseudoHeaderName,
value_bytes: &[u8],
pseudo_headers: &mut PseudoHeaders<'static>,
malformed: &mut Option<MalformedRequest>,
) -> Result<(), CompressionError> {
let slot_filled = match pseudo {
PseudoHeaderName::Method => pseudo_headers.method().is_some(),
PseudoHeaderName::Status => pseudo_headers.status().is_some(),
PseudoHeaderName::Authority => pseudo_headers.authority().is_some(),
PseudoHeaderName::Path => pseudo_headers.path().is_some(),
PseudoHeaderName::Scheme => pseudo_headers.scheme().is_some(),
PseudoHeaderName::Protocol => pseudo_headers.protocol().is_some(),
};
if slot_filled {
log::trace!("hpack: duplicate pseudo-header: {pseudo:?}");
malformed.get_or_insert(MalformedRequest::DuplicatePseudoHeader);
return Ok(());
}
match pseudo {
PseudoHeaderName::Method => {
let m = Method::parse(value_bytes).map_err(|_| CompressionError::InvalidHeaderName)?;
pseudo_headers.set_method(Some(m));
}
PseudoHeaderName::Status => {
let s: Status = std::str::from_utf8(value_bytes)
.map_err(|_| CompressionError::InvalidHeaderName)?
.parse()
.map_err(|_| CompressionError::InvalidHeaderName)?;
pseudo_headers.set_status(Some(s));
}
PseudoHeaderName::Authority
| PseudoHeaderName::Path
| PseudoHeaderName::Scheme
| PseudoHeaderName::Protocol => {
let s = std::str::from_utf8(value_bytes)
.map_err(|_| CompressionError::InvalidHeaderName)?
.to_owned();
let cow: Cow<'static, str> = Cow::Owned(s);
match pseudo {
PseudoHeaderName::Authority => pseudo_headers.set_authority(Some(cow)),
PseudoHeaderName::Path => pseudo_headers.set_path(Some(cow)),
PseudoHeaderName::Scheme => pseudo_headers.set_scheme(Some(cow)),
PseudoHeaderName::Protocol => pseudo_headers.set_protocol(Some(cow)),
PseudoHeaderName::Method | PseudoHeaderName::Status => unreachable!(),
};
}
}
Ok(())
}