diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/h2/src/hpack/decoder.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/h2/src/hpack/decoder.rs')
-rw-r--r-- | third_party/rust/h2/src/hpack/decoder.rs | 940 |
1 files changed, 940 insertions, 0 deletions
diff --git a/third_party/rust/h2/src/hpack/decoder.rs b/third_party/rust/h2/src/hpack/decoder.rs new file mode 100644 index 0000000000..988b48db11 --- /dev/null +++ b/third_party/rust/h2/src/hpack/decoder.rs @@ -0,0 +1,940 @@ +use super::{header::BytesStr, huffman, Header}; +use crate::frame; + +use bytes::{Buf, Bytes, BytesMut}; +use http::header; +use http::method::{self, Method}; +use http::status::{self, StatusCode}; + +use std::cmp; +use std::collections::VecDeque; +use std::io::Cursor; +use std::str::Utf8Error; + +/// Decodes headers using HPACK +#[derive(Debug)] +pub struct Decoder { + // Protocol indicated that the max table size will update + max_size_update: Option<usize>, + last_max_update: usize, + table: Table, + buffer: BytesMut, +} + +/// Represents all errors that can be encountered while performing the decoding +/// of an HPACK header set. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum DecoderError { + InvalidRepresentation, + InvalidIntegerPrefix, + InvalidTableIndex, + InvalidHuffmanCode, + InvalidUtf8, + InvalidStatusCode, + InvalidPseudoheader, + InvalidMaxDynamicSize, + IntegerOverflow, + NeedMore(NeedMore), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum NeedMore { + UnexpectedEndOfStream, + IntegerUnderflow, + StringUnderflow, +} + +enum Representation { + /// Indexed header field representation + /// + /// An indexed header field representation identifies an entry in either the + /// static table or the dynamic table (see Section 2.3). + /// + /// # Header encoding + /// + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 1 | Index (7+) | + /// +---+---------------------------+ + /// ``` + Indexed, + + /// Literal Header Field with Incremental Indexing + /// + /// A literal header field with incremental indexing representation results + /// in appending a header field to the decoded header list and inserting it + /// as a new entry into the dynamic table. + /// + /// # Header encoding + /// + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 1 | Index (6+) | + /// +---+---+-----------------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + LiteralWithIndexing, + + /// Literal Header Field without Indexing + /// + /// A literal header field without indexing representation results in + /// appending a header field to the decoded header list without altering the + /// dynamic table. + /// + /// # Header encoding + /// + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 0 | 0 | Index (4+) | + /// +---+---+-----------------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + LiteralWithoutIndexing, + + /// Literal Header Field Never Indexed + /// + /// A literal header field never-indexed representation results in appending + /// a header field to the decoded header list without altering the dynamic + /// table. Intermediaries MUST use the same representation for encoding this + /// header field. + /// + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 0 | 1 | Index (4+) | + /// +---+---+-----------------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + LiteralNeverIndexed, + + /// Dynamic Table Size Update + /// + /// A dynamic table size update signals a change to the size of the dynamic + /// table. + /// + /// # Header encoding + /// + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 1 | Max size (5+) | + /// +---+---------------------------+ + /// ``` + SizeUpdate, +} + +#[derive(Debug)] +struct Table { + entries: VecDeque<Header>, + size: usize, + max_size: usize, +} + +struct StringMarker { + offset: usize, + len: usize, + string: Option<Bytes>, +} + +// ===== impl Decoder ===== + +impl Decoder { + /// Creates a new `Decoder` with all settings set to default values. + pub fn new(size: usize) -> Decoder { + Decoder { + max_size_update: None, + last_max_update: size, + table: Table::new(size), + buffer: BytesMut::with_capacity(4096), + } + } + + /// Queues a potential size update + #[allow(dead_code)] + pub fn queue_size_update(&mut self, size: usize) { + let size = match self.max_size_update { + Some(v) => cmp::max(v, size), + None => size, + }; + + self.max_size_update = Some(size); + } + + /// Decodes the headers found in the given buffer. + pub fn decode<F>( + &mut self, + src: &mut Cursor<&mut BytesMut>, + mut f: F, + ) -> Result<(), DecoderError> + where + F: FnMut(Header), + { + use self::Representation::*; + + let mut can_resize = true; + + if let Some(size) = self.max_size_update.take() { + self.last_max_update = size; + } + + let span = tracing::trace_span!("hpack::decode"); + let _e = span.enter(); + + tracing::trace!("decode"); + + while let Some(ty) = peek_u8(src) { + // At this point we are always at the beginning of the next block + // within the HPACK data. The type of the block can always be + // determined from the first byte. + match Representation::load(ty)? { + Indexed => { + tracing::trace!(rem = src.remaining(), kind = %"Indexed"); + can_resize = false; + let entry = self.decode_indexed(src)?; + consume(src); + f(entry); + } + LiteralWithIndexing => { + tracing::trace!(rem = src.remaining(), kind = %"LiteralWithIndexing"); + can_resize = false; + let entry = self.decode_literal(src, true)?; + + // Insert the header into the table + self.table.insert(entry.clone()); + consume(src); + + f(entry); + } + LiteralWithoutIndexing => { + tracing::trace!(rem = src.remaining(), kind = %"LiteralWithoutIndexing"); + can_resize = false; + let entry = self.decode_literal(src, false)?; + consume(src); + f(entry); + } + LiteralNeverIndexed => { + tracing::trace!(rem = src.remaining(), kind = %"LiteralNeverIndexed"); + can_resize = false; + let entry = self.decode_literal(src, false)?; + consume(src); + + // TODO: Track that this should never be indexed + + f(entry); + } + SizeUpdate => { + tracing::trace!(rem = src.remaining(), kind = %"SizeUpdate"); + if !can_resize { + return Err(DecoderError::InvalidMaxDynamicSize); + } + + // Handle the dynamic table size update + self.process_size_update(src)?; + consume(src); + } + } + } + + Ok(()) + } + + fn process_size_update(&mut self, buf: &mut Cursor<&mut BytesMut>) -> Result<(), DecoderError> { + let new_size = decode_int(buf, 5)?; + + if new_size > self.last_max_update { + return Err(DecoderError::InvalidMaxDynamicSize); + } + + tracing::debug!( + from = self.table.size(), + to = new_size, + "Decoder changed max table size" + ); + + self.table.set_max_size(new_size); + + Ok(()) + } + + fn decode_indexed(&self, buf: &mut Cursor<&mut BytesMut>) -> Result<Header, DecoderError> { + let index = decode_int(buf, 7)?; + self.table.get(index) + } + + fn decode_literal( + &mut self, + buf: &mut Cursor<&mut BytesMut>, + index: bool, + ) -> Result<Header, DecoderError> { + let prefix = if index { 6 } else { 4 }; + + // Extract the table index for the name, or 0 if not indexed + let table_idx = decode_int(buf, prefix)?; + + // First, read the header name + if table_idx == 0 { + let old_pos = buf.position(); + let name_marker = self.try_decode_string(buf)?; + let value_marker = self.try_decode_string(buf)?; + buf.set_position(old_pos); + // Read the name as a literal + let name = name_marker.consume(buf); + let value = value_marker.consume(buf); + Header::new(name, value) + } else { + let e = self.table.get(table_idx)?; + let value = self.decode_string(buf)?; + + e.name().into_entry(value) + } + } + + fn try_decode_string( + &mut self, + buf: &mut Cursor<&mut BytesMut>, + ) -> Result<StringMarker, DecoderError> { + let old_pos = buf.position(); + const HUFF_FLAG: u8 = 0b1000_0000; + + // The first bit in the first byte contains the huffman encoded flag. + let huff = match peek_u8(buf) { + Some(hdr) => (hdr & HUFF_FLAG) == HUFF_FLAG, + None => return Err(DecoderError::NeedMore(NeedMore::UnexpectedEndOfStream)), + }; + + // Decode the string length using 7 bit prefix + let len = decode_int(buf, 7)?; + + if len > buf.remaining() { + tracing::trace!(len, remaining = buf.remaining(), "decode_string underflow",); + return Err(DecoderError::NeedMore(NeedMore::StringUnderflow)); + } + + let offset = (buf.position() - old_pos) as usize; + if huff { + let ret = { + let raw = &buf.chunk()[..len]; + huffman::decode(raw, &mut self.buffer).map(|buf| StringMarker { + offset, + len, + string: Some(BytesMut::freeze(buf)), + }) + }; + + buf.advance(len); + ret + } else { + buf.advance(len); + Ok(StringMarker { + offset, + len, + string: None, + }) + } + } + + fn decode_string(&mut self, buf: &mut Cursor<&mut BytesMut>) -> Result<Bytes, DecoderError> { + let old_pos = buf.position(); + let marker = self.try_decode_string(buf)?; + buf.set_position(old_pos); + Ok(marker.consume(buf)) + } +} + +impl Default for Decoder { + fn default() -> Decoder { + Decoder::new(4096) + } +} + +// ===== impl Representation ===== + +impl Representation { + pub fn load(byte: u8) -> Result<Representation, DecoderError> { + const INDEXED: u8 = 0b1000_0000; + const LITERAL_WITH_INDEXING: u8 = 0b0100_0000; + const LITERAL_WITHOUT_INDEXING: u8 = 0b1111_0000; + const LITERAL_NEVER_INDEXED: u8 = 0b0001_0000; + const SIZE_UPDATE_MASK: u8 = 0b1110_0000; + const SIZE_UPDATE: u8 = 0b0010_0000; + + // TODO: What did I even write here? + + if byte & INDEXED == INDEXED { + Ok(Representation::Indexed) + } else if byte & LITERAL_WITH_INDEXING == LITERAL_WITH_INDEXING { + Ok(Representation::LiteralWithIndexing) + } else if byte & LITERAL_WITHOUT_INDEXING == 0 { + Ok(Representation::LiteralWithoutIndexing) + } else if byte & LITERAL_WITHOUT_INDEXING == LITERAL_NEVER_INDEXED { + Ok(Representation::LiteralNeverIndexed) + } else if byte & SIZE_UPDATE_MASK == SIZE_UPDATE { + Ok(Representation::SizeUpdate) + } else { + Err(DecoderError::InvalidRepresentation) + } + } +} + +fn decode_int<B: Buf>(buf: &mut B, prefix_size: u8) -> Result<usize, DecoderError> { + // The octet limit is chosen such that the maximum allowed *value* can + // never overflow an unsigned 32-bit integer. The maximum value of any + // integer that can be encoded with 5 octets is ~2^28 + const MAX_BYTES: usize = 5; + const VARINT_MASK: u8 = 0b0111_1111; + const VARINT_FLAG: u8 = 0b1000_0000; + + if prefix_size < 1 || prefix_size > 8 { + return Err(DecoderError::InvalidIntegerPrefix); + } + + if !buf.has_remaining() { + return Err(DecoderError::NeedMore(NeedMore::IntegerUnderflow)); + } + + let mask = if prefix_size == 8 { + 0xFF + } else { + (1u8 << prefix_size).wrapping_sub(1) + }; + + let mut ret = (buf.get_u8() & mask) as usize; + + if ret < mask as usize { + // Value fits in the prefix bits + return Ok(ret); + } + + // The int did not fit in the prefix bits, so continue reading. + // + // The total number of bytes used to represent the int. The first byte was + // the prefix, so start at 1. + let mut bytes = 1; + + // The rest of the int is stored as a varint -- 7 bits for the value and 1 + // bit to indicate if it is the last byte. + let mut shift = 0; + + while buf.has_remaining() { + let b = buf.get_u8(); + + bytes += 1; + ret += ((b & VARINT_MASK) as usize) << shift; + shift += 7; + + if b & VARINT_FLAG == 0 { + return Ok(ret); + } + + if bytes == MAX_BYTES { + // The spec requires that this situation is an error + return Err(DecoderError::IntegerOverflow); + } + } + + Err(DecoderError::NeedMore(NeedMore::IntegerUnderflow)) +} + +fn peek_u8<B: Buf>(buf: &mut B) -> Option<u8> { + if buf.has_remaining() { + Some(buf.chunk()[0]) + } else { + None + } +} + +fn take(buf: &mut Cursor<&mut BytesMut>, n: usize) -> Bytes { + let pos = buf.position() as usize; + let mut head = buf.get_mut().split_to(pos + n); + buf.set_position(0); + head.advance(pos); + head.freeze() +} + +impl StringMarker { + fn consume(self, buf: &mut Cursor<&mut BytesMut>) -> Bytes { + buf.advance(self.offset); + match self.string { + Some(string) => { + buf.advance(self.len); + string + } + None => take(buf, self.len), + } + } +} + +fn consume(buf: &mut Cursor<&mut BytesMut>) { + // remove bytes from the internal BytesMut when they have been successfully + // decoded. This is a more permanent cursor position, which will be + // used to resume if decoding was only partial. + take(buf, 0); +} + +// ===== impl Table ===== + +impl Table { + fn new(max_size: usize) -> Table { + Table { + entries: VecDeque::new(), + size: 0, + max_size, + } + } + + fn size(&self) -> usize { + self.size + } + + /// Returns the entry located at the given index. + /// + /// The table is 1-indexed and constructed in such a way that the first + /// entries belong to the static table, followed by entries in the dynamic + /// table. They are merged into a single index address space, though. + /// + /// This is according to the [HPACK spec, section 2.3.3.] + /// (http://http2.github.io/http2-spec/compression.html#index.address.space) + pub fn get(&self, index: usize) -> Result<Header, DecoderError> { + if index == 0 { + return Err(DecoderError::InvalidTableIndex); + } + + if index <= 61 { + return Ok(get_static(index)); + } + + // Convert the index for lookup in the entries structure. + match self.entries.get(index - 62) { + Some(e) => Ok(e.clone()), + None => Err(DecoderError::InvalidTableIndex), + } + } + + fn insert(&mut self, entry: Header) { + let len = entry.len(); + + self.reserve(len); + + if self.size + len <= self.max_size { + self.size += len; + + // Track the entry + self.entries.push_front(entry); + } + } + + fn set_max_size(&mut self, size: usize) { + self.max_size = size; + // Make the table size fit within the new constraints. + self.consolidate(); + } + + fn reserve(&mut self, size: usize) { + while self.size + size > self.max_size { + match self.entries.pop_back() { + Some(last) => { + self.size -= last.len(); + } + None => return, + } + } + } + + fn consolidate(&mut self) { + while self.size > self.max_size { + { + let last = match self.entries.back() { + Some(x) => x, + None => { + // Can never happen as the size of the table must reach + // 0 by the time we've exhausted all elements. + panic!("Size of table != 0, but no headers left!"); + } + }; + + self.size -= last.len(); + } + + self.entries.pop_back(); + } + } +} + +// ===== impl DecoderError ===== + +impl From<Utf8Error> for DecoderError { + fn from(_: Utf8Error) -> DecoderError { + // TODO: Better error? + DecoderError::InvalidUtf8 + } +} + +impl From<header::InvalidHeaderValue> for DecoderError { + fn from(_: header::InvalidHeaderValue) -> DecoderError { + // TODO: Better error? + DecoderError::InvalidUtf8 + } +} + +impl From<header::InvalidHeaderName> for DecoderError { + fn from(_: header::InvalidHeaderName) -> DecoderError { + // TODO: Better error + DecoderError::InvalidUtf8 + } +} + +impl From<method::InvalidMethod> for DecoderError { + fn from(_: method::InvalidMethod) -> DecoderError { + // TODO: Better error + DecoderError::InvalidUtf8 + } +} + +impl From<status::InvalidStatusCode> for DecoderError { + fn from(_: status::InvalidStatusCode) -> DecoderError { + // TODO: Better error + DecoderError::InvalidUtf8 + } +} + +impl From<DecoderError> for frame::Error { + fn from(src: DecoderError) -> Self { + frame::Error::Hpack(src) + } +} + +/// Get an entry from the static table +pub fn get_static(idx: usize) -> Header { + use http::header::HeaderValue; + + match idx { + 1 => Header::Authority(BytesStr::from_static("")), + 2 => Header::Method(Method::GET), + 3 => Header::Method(Method::POST), + 4 => Header::Path(BytesStr::from_static("/")), + 5 => Header::Path(BytesStr::from_static("/index.html")), + 6 => Header::Scheme(BytesStr::from_static("http")), + 7 => Header::Scheme(BytesStr::from_static("https")), + 8 => Header::Status(StatusCode::OK), + 9 => Header::Status(StatusCode::NO_CONTENT), + 10 => Header::Status(StatusCode::PARTIAL_CONTENT), + 11 => Header::Status(StatusCode::NOT_MODIFIED), + 12 => Header::Status(StatusCode::BAD_REQUEST), + 13 => Header::Status(StatusCode::NOT_FOUND), + 14 => Header::Status(StatusCode::INTERNAL_SERVER_ERROR), + 15 => Header::Field { + name: header::ACCEPT_CHARSET, + value: HeaderValue::from_static(""), + }, + 16 => Header::Field { + name: header::ACCEPT_ENCODING, + value: HeaderValue::from_static("gzip, deflate"), + }, + 17 => Header::Field { + name: header::ACCEPT_LANGUAGE, + value: HeaderValue::from_static(""), + }, + 18 => Header::Field { + name: header::ACCEPT_RANGES, + value: HeaderValue::from_static(""), + }, + 19 => Header::Field { + name: header::ACCEPT, + value: HeaderValue::from_static(""), + }, + 20 => Header::Field { + name: header::ACCESS_CONTROL_ALLOW_ORIGIN, + value: HeaderValue::from_static(""), + }, + 21 => Header::Field { + name: header::AGE, + value: HeaderValue::from_static(""), + }, + 22 => Header::Field { + name: header::ALLOW, + value: HeaderValue::from_static(""), + }, + 23 => Header::Field { + name: header::AUTHORIZATION, + value: HeaderValue::from_static(""), + }, + 24 => Header::Field { + name: header::CACHE_CONTROL, + value: HeaderValue::from_static(""), + }, + 25 => Header::Field { + name: header::CONTENT_DISPOSITION, + value: HeaderValue::from_static(""), + }, + 26 => Header::Field { + name: header::CONTENT_ENCODING, + value: HeaderValue::from_static(""), + }, + 27 => Header::Field { + name: header::CONTENT_LANGUAGE, + value: HeaderValue::from_static(""), + }, + 28 => Header::Field { + name: header::CONTENT_LENGTH, + value: HeaderValue::from_static(""), + }, + 29 => Header::Field { + name: header::CONTENT_LOCATION, + value: HeaderValue::from_static(""), + }, + 30 => Header::Field { + name: header::CONTENT_RANGE, + value: HeaderValue::from_static(""), + }, + 31 => Header::Field { + name: header::CONTENT_TYPE, + value: HeaderValue::from_static(""), + }, + 32 => Header::Field { + name: header::COOKIE, + value: HeaderValue::from_static(""), + }, + 33 => Header::Field { + name: header::DATE, + value: HeaderValue::from_static(""), + }, + 34 => Header::Field { + name: header::ETAG, + value: HeaderValue::from_static(""), + }, + 35 => Header::Field { + name: header::EXPECT, + value: HeaderValue::from_static(""), + }, + 36 => Header::Field { + name: header::EXPIRES, + value: HeaderValue::from_static(""), + }, + 37 => Header::Field { + name: header::FROM, + value: HeaderValue::from_static(""), + }, + 38 => Header::Field { + name: header::HOST, + value: HeaderValue::from_static(""), + }, + 39 => Header::Field { + name: header::IF_MATCH, + value: HeaderValue::from_static(""), + }, + 40 => Header::Field { + name: header::IF_MODIFIED_SINCE, + value: HeaderValue::from_static(""), + }, + 41 => Header::Field { + name: header::IF_NONE_MATCH, + value: HeaderValue::from_static(""), + }, + 42 => Header::Field { + name: header::IF_RANGE, + value: HeaderValue::from_static(""), + }, + 43 => Header::Field { + name: header::IF_UNMODIFIED_SINCE, + value: HeaderValue::from_static(""), + }, + 44 => Header::Field { + name: header::LAST_MODIFIED, + value: HeaderValue::from_static(""), + }, + 45 => Header::Field { + name: header::LINK, + value: HeaderValue::from_static(""), + }, + 46 => Header::Field { + name: header::LOCATION, + value: HeaderValue::from_static(""), + }, + 47 => Header::Field { + name: header::MAX_FORWARDS, + value: HeaderValue::from_static(""), + }, + 48 => Header::Field { + name: header::PROXY_AUTHENTICATE, + value: HeaderValue::from_static(""), + }, + 49 => Header::Field { + name: header::PROXY_AUTHORIZATION, + value: HeaderValue::from_static(""), + }, + 50 => Header::Field { + name: header::RANGE, + value: HeaderValue::from_static(""), + }, + 51 => Header::Field { + name: header::REFERER, + value: HeaderValue::from_static(""), + }, + 52 => Header::Field { + name: header::REFRESH, + value: HeaderValue::from_static(""), + }, + 53 => Header::Field { + name: header::RETRY_AFTER, + value: HeaderValue::from_static(""), + }, + 54 => Header::Field { + name: header::SERVER, + value: HeaderValue::from_static(""), + }, + 55 => Header::Field { + name: header::SET_COOKIE, + value: HeaderValue::from_static(""), + }, + 56 => Header::Field { + name: header::STRICT_TRANSPORT_SECURITY, + value: HeaderValue::from_static(""), + }, + 57 => Header::Field { + name: header::TRANSFER_ENCODING, + value: HeaderValue::from_static(""), + }, + 58 => Header::Field { + name: header::USER_AGENT, + value: HeaderValue::from_static(""), + }, + 59 => Header::Field { + name: header::VARY, + value: HeaderValue::from_static(""), + }, + 60 => Header::Field { + name: header::VIA, + value: HeaderValue::from_static(""), + }, + 61 => Header::Field { + name: header::WWW_AUTHENTICATE, + value: HeaderValue::from_static(""), + }, + _ => unreachable!(), + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::hpack::Header; + + #[test] + fn test_peek_u8() { + let b = 0xff; + let mut buf = Cursor::new(vec![b]); + assert_eq!(peek_u8(&mut buf), Some(b)); + assert_eq!(buf.get_u8(), b); + assert_eq!(peek_u8(&mut buf), None); + } + + #[test] + fn test_decode_string_empty() { + let mut de = Decoder::new(0); + let mut buf = BytesMut::new(); + let err = de.decode_string(&mut Cursor::new(&mut buf)).unwrap_err(); + assert_eq!(err, DecoderError::NeedMore(NeedMore::UnexpectedEndOfStream)); + } + + #[test] + fn test_decode_empty() { + let mut de = Decoder::new(0); + let mut buf = BytesMut::new(); + let empty = de.decode(&mut Cursor::new(&mut buf), |_| {}).unwrap(); + assert_eq!(empty, ()); + } + + #[test] + fn test_decode_indexed_larger_than_table() { + let mut de = Decoder::new(0); + + let mut buf = BytesMut::new(); + buf.extend(&[0b01000000, 0x80 | 2]); + buf.extend(huff_encode(b"foo")); + buf.extend(&[0x80 | 3]); + buf.extend(huff_encode(b"bar")); + + let mut res = vec![]; + let _ = de + .decode(&mut Cursor::new(&mut buf), |h| { + res.push(h); + }) + .unwrap(); + + assert_eq!(res.len(), 1); + assert_eq!(de.table.size(), 0); + + match res[0] { + Header::Field { + ref name, + ref value, + } => { + assert_eq!(name, "foo"); + assert_eq!(value, "bar"); + } + _ => panic!(), + } + } + + fn huff_encode(src: &[u8]) -> BytesMut { + let mut buf = BytesMut::new(); + huffman::encode(src, &mut buf); + buf + } + + #[test] + fn test_decode_continuation_header_with_non_huff_encoded_name() { + let mut de = Decoder::new(0); + let value = huff_encode(b"bar"); + let mut buf = BytesMut::new(); + // header name is non_huff encoded + buf.extend(&[0b01000000, 0x00 | 3]); + buf.extend(b"foo"); + // header value is partial + buf.extend(&[0x80 | 3]); + buf.extend(&value[0..1]); + + let mut res = vec![]; + let e = de + .decode(&mut Cursor::new(&mut buf), |h| { + res.push(h); + }) + .unwrap_err(); + // decode error because the header value is partial + assert_eq!(e, DecoderError::NeedMore(NeedMore::StringUnderflow)); + + // extend buf with the remaining header value + buf.extend(&value[1..]); + let _ = de + .decode(&mut Cursor::new(&mut buf), |h| { + res.push(h); + }) + .unwrap(); + + assert_eq!(res.len(), 1); + assert_eq!(de.table.size(), 0); + + match res[0] { + Header::Field { + ref name, + ref value, + } => { + assert_eq!(name, "foo"); + assert_eq!(value, "bar"); + } + _ => panic!(), + } + } +} |