use std::io::{self, Read, Write};
use std::net::TcpStream;
use crate::error::{Error, Result};
use crate::{Request, Response};
const PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
const F_DATA: u8 = 0x0;
const F_HEADERS: u8 = 0x1;
#[allow(dead_code)]
const F_PRIORITY: u8 = 0x2;
const F_RST_STREAM: u8 = 0x3;
const F_SETTINGS: u8 = 0x4;
#[allow(dead_code)]
const F_PUSH_PROMISE: u8 = 0x5;
const F_PING: u8 = 0x6;
const F_GOAWAY: u8 = 0x7;
const F_WINDOW_UPDATE: u8 = 0x8;
const F_CONTINUATION: u8 = 0x9;
const FLAG_END_STREAM: u8 = 0x01;
const FLAG_ACK: u8 = 0x01;
const FLAG_END_HEADERS: u8 = 0x04;
const FLAG_PADDED: u8 = 0x08;
const FLAG_PRIORITY: u8 = 0x20;
#[derive(Debug, Clone, PartialEq, Eq)]
struct Frame {
typ: u8,
flags: u8,
stream_id: u32,
payload: Vec<u8>,
}
const MAX_FRAME_PAYLOAD: usize = 1 << 20;
fn read_exact<R: Read>(r: &mut R, buf: &mut [u8]) -> io::Result<()> {
r.read_exact(buf)
}
fn read_frame<R: Read>(r: &mut R) -> io::Result<Frame> {
let mut hdr = [0u8; 9];
read_exact(r, &mut hdr)?;
let length = ((hdr[0] as usize) << 16) | ((hdr[1] as usize) << 8) | (hdr[2] as usize);
let typ = hdr[3];
let flags = hdr[4];
let stream_id = (((hdr[5] & 0x7f) as u32) << 24)
| ((hdr[6] as u32) << 16)
| ((hdr[7] as u32) << 8)
| (hdr[8] as u32);
if length > MAX_FRAME_PAYLOAD {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("frame payload too large: {length}"),
));
}
let mut payload = vec![0u8; length];
if length > 0 {
read_exact(r, &mut payload)?;
}
Ok(Frame {
typ,
flags,
stream_id,
payload,
})
}
fn write_frame<W: Write>(w: &mut W, f: &Frame) -> io::Result<()> {
if f.payload.len() > MAX_FRAME_PAYLOAD {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"frame payload too large",
));
}
let len = f.payload.len();
let hdr = [
((len >> 16) & 0xff) as u8,
((len >> 8) & 0xff) as u8,
(len & 0xff) as u8,
f.typ,
f.flags,
((f.stream_id >> 24) & 0x7f) as u8, ((f.stream_id >> 16) & 0xff) as u8,
((f.stream_id >> 8) & 0xff) as u8,
(f.stream_id & 0xff) as u8,
];
w.write_all(&hdr)?;
if !f.payload.is_empty() {
w.write_all(&f.payload)?;
}
Ok(())
}
fn encode_int(value: u64, prefix_bits: u8) -> Vec<u8> {
let max_prefix: u64 = (1u64 << prefix_bits) - 1;
let mut out = Vec::new();
if value < max_prefix {
out.push(value as u8);
return out;
}
out.push(max_prefix as u8);
let mut rem = value - max_prefix;
while rem >= 128 {
out.push(((rem & 0x7f) as u8) | 0x80);
rem >>= 7;
}
out.push(rem as u8);
out
}
fn decode_int(buf: &[u8], prefix_bits: u8) -> Result<(u64, usize)> {
if buf.is_empty() {
return Err(Error::BadResponse("hpack: empty integer".into()));
}
let max_prefix: u64 = (1u64 << prefix_bits) - 1;
let mut value = (buf[0] as u64) & max_prefix;
if value < max_prefix {
return Ok((value, 1));
}
let mut i = 1usize;
let mut shift = 0u32;
loop {
if i >= buf.len() {
return Err(Error::BadResponse("hpack: truncated integer".into()));
}
let b = buf[i];
i += 1;
value = value
.checked_add(((b & 0x7f) as u64) << shift)
.ok_or_else(|| Error::BadResponse("hpack: integer overflow".into()))?;
if b & 0x80 == 0 {
return Ok((value, i));
}
shift += 7;
if shift > 63 {
return Err(Error::BadResponse("hpack: integer overflow".into()));
}
}
}
const STATIC_TABLE: &[(&str, &str)] = &[
(":authority", ""), (":method", "GET"), (":method", "POST"), (":path", "/"), (":path", "/index.html"), (":scheme", "http"), (":scheme", "https"), (":status", "200"), (":status", "204"), (":status", "206"), (":status", "304"), (":status", "400"), (":status", "404"), (":status", "500"), ("accept-charset", ""), ("accept-encoding", "gzip, deflate"), ("accept-language", ""), ("accept-ranges", ""), ("accept", ""), ("access-control-allow-origin", ""), ("age", ""), ("allow", ""), ("authorization", ""), ("cache-control", ""), ("content-disposition", ""), ("content-encoding", ""), ("content-language", ""), ("content-length", ""), ("content-location", ""), ("content-range", ""), ("content-type", ""), ("cookie", ""), ("date", ""), ("etag", ""), ("expect", ""), ("expires", ""), ("from", ""), ("host", ""), ("if-match", ""), ("if-modified-since", ""), ("if-none-match", ""), ("if-range", ""), ("if-unmodified-since", ""), ("last-modified", ""), ("link", ""), ("location", ""), ("max-forwards", ""), ("proxy-authenticate", ""), ("proxy-authorization", ""), ("range", ""), ("referer", ""), ("refresh", ""), ("retry-after", ""), ("server", ""), ("set-cookie", ""), ("strict-transport-security", ""), ("transfer-encoding", ""), ("user-agent", ""), ("vary", ""), ("via", ""), ("www-authenticate", ""), ];
fn static_full_index(name: &str, value: &str) -> Option<usize> {
STATIC_TABLE
.iter()
.position(|(n, v)| *n == name && *v == value)
.map(|i| i + 1)
}
fn static_name_index(name: &str) -> Option<usize> {
STATIC_TABLE
.iter()
.position(|(n, _)| *n == name)
.map(|i| i + 1)
}
const HUFFMAN: [(u32, u8); 257] = [
(0x1ff8, 13),
(0x7fffd8, 23),
(0xfffffe2, 28),
(0xfffffe3, 28),
(0xfffffe4, 28),
(0xfffffe5, 28),
(0xfffffe6, 28),
(0xfffffe7, 28),
(0xfffffe8, 28),
(0xffffea, 24),
(0x3ffffffc, 30),
(0xfffffe9, 28),
(0xfffffea, 28),
(0x3ffffffd, 30),
(0xfffffeb, 28),
(0xfffffec, 28),
(0xfffffed, 28),
(0xfffffee, 28),
(0xfffffef, 28),
(0xffffff0, 28),
(0xffffff1, 28),
(0xffffff2, 28),
(0x3ffffffe, 30),
(0xffffff3, 28),
(0xffffff4, 28),
(0xffffff5, 28),
(0xffffff6, 28),
(0xffffff7, 28),
(0xffffff8, 28),
(0xffffff9, 28),
(0xffffffa, 28),
(0xffffffb, 28),
(0x14, 6),
(0x3f8, 10),
(0x3f9, 10),
(0xffa, 12),
(0x1ff9, 13),
(0x15, 6),
(0xf8, 8),
(0x7fa, 11),
(0x3fa, 10),
(0x3fb, 10),
(0xf9, 8),
(0x7fb, 11),
(0xfa, 8),
(0x16, 6),
(0x17, 6),
(0x18, 6),
(0x0, 5),
(0x1, 5),
(0x2, 5),
(0x19, 6),
(0x1a, 6),
(0x1b, 6),
(0x1c, 6),
(0x1d, 6),
(0x1e, 6),
(0x1f, 6),
(0x5c, 7),
(0xfb, 8),
(0x7ffc, 15),
(0x20, 6),
(0xffb, 12),
(0x3fc, 10),
(0x1ffa, 13),
(0x21, 6),
(0x5d, 7),
(0x5e, 7),
(0x5f, 7),
(0x60, 7),
(0x61, 7),
(0x62, 7),
(0x63, 7),
(0x64, 7),
(0x65, 7),
(0x66, 7),
(0x67, 7),
(0x68, 7),
(0x69, 7),
(0x6a, 7),
(0x6b, 7),
(0x6c, 7),
(0x6d, 7),
(0x6e, 7),
(0x6f, 7),
(0x70, 7),
(0x71, 7),
(0x72, 7),
(0xfc, 8),
(0x73, 7),
(0xfd, 8),
(0x1ffb, 13),
(0x7fff0, 19),
(0x1ffc, 13),
(0x3ffc, 14),
(0x22, 6),
(0x7ffd, 15),
(0x3, 5),
(0x23, 6),
(0x4, 5),
(0x24, 6),
(0x5, 5),
(0x25, 6),
(0x26, 6),
(0x27, 6),
(0x6, 5),
(0x74, 7),
(0x75, 7),
(0x28, 6),
(0x29, 6),
(0x2a, 6),
(0x7, 5),
(0x2b, 6),
(0x76, 7),
(0x2c, 6),
(0x8, 5),
(0x9, 5),
(0x2d, 6),
(0x77, 7),
(0x78, 7),
(0x79, 7),
(0x7a, 7),
(0x7b, 7),
(0x7ffe, 15),
(0x7fc, 11),
(0x3ffd, 14),
(0x1ffd, 13),
(0xffffffc, 28),
(0xfffe6, 20),
(0x3fffd2, 22),
(0xfffe7, 20),
(0xfffe8, 20),
(0x3fffd3, 22),
(0x3fffd4, 22),
(0x3fffd5, 22),
(0x7fffd9, 23),
(0x3fffd6, 22),
(0x7fffda, 23),
(0x7fffdb, 23),
(0x7fffdc, 23),
(0x7fffdd, 23),
(0x7fffde, 23),
(0xffffeb, 24),
(0x7fffdf, 23),
(0xffffec, 24),
(0xffffed, 24),
(0x3fffd7, 22),
(0x7fffe0, 23),
(0xffffee, 24),
(0x7fffe1, 23),
(0x7fffe2, 23),
(0x7fffe3, 23),
(0x7fffe4, 23),
(0x1fffdc, 21),
(0x3fffd8, 22),
(0x7fffe5, 23),
(0x3fffd9, 22),
(0x7fffe6, 23),
(0x7fffe7, 23),
(0xffffef, 24),
(0x3fffda, 22),
(0x1fffdd, 21),
(0xfffe9, 20),
(0x3fffdb, 22),
(0x3fffdc, 22),
(0x7fffe8, 23),
(0x7fffe9, 23),
(0x1fffde, 21),
(0x7fffea, 23),
(0x3fffdd, 22),
(0x3fffde, 22),
(0xfffff0, 24),
(0x1fffdf, 21),
(0x3fffdf, 22),
(0x7fffeb, 23),
(0x7fffec, 23),
(0x1fffe0, 21),
(0x1fffe1, 21),
(0x3fffe0, 22),
(0x1fffe2, 21),
(0x7fffed, 23),
(0x3fffe1, 22),
(0x7fffee, 23),
(0x7fffef, 23),
(0xfffea, 20),
(0x3fffe2, 22),
(0x3fffe3, 22),
(0x3fffe4, 22),
(0x7ffff0, 23),
(0x3fffe5, 22),
(0x3fffe6, 22),
(0x7ffff1, 23),
(0x3ffffe0, 26),
(0x3ffffe1, 26),
(0xfffeb, 20),
(0x7fff1, 19),
(0x3fffe7, 22),
(0x7ffff2, 23),
(0x3fffe8, 22),
(0x1ffffec, 25),
(0x3ffffe2, 26),
(0x3ffffe3, 26),
(0x3ffffe4, 26),
(0x7ffffde, 27),
(0x7ffffdf, 27),
(0x3ffffe5, 26),
(0xfffff1, 24),
(0x1ffffed, 25),
(0x7fff2, 19),
(0x1fffe3, 21),
(0x3ffffe6, 26),
(0x7ffffe0, 27),
(0x7ffffe1, 27),
(0x3ffffe7, 26),
(0x7ffffe2, 27),
(0xfffff2, 24),
(0x1fffe4, 21),
(0x1fffe5, 21),
(0x3ffffe8, 26),
(0x3ffffe9, 26),
(0xffffffd, 28),
(0x7ffffe3, 27),
(0x7ffffe4, 27),
(0x7ffffe5, 27),
(0xfffec, 20),
(0xfffff3, 24),
(0xfffed, 20),
(0x1fffe6, 21),
(0x3fffe9, 22),
(0x1fffe7, 21),
(0x1fffe8, 21),
(0x7ffff3, 23),
(0x3fffea, 22),
(0x3fffeb, 22),
(0x1ffffee, 25),
(0x1ffffef, 25),
(0xfffff4, 24),
(0xfffff5, 24),
(0x3ffffea, 26),
(0x7ffff4, 23),
(0x3ffffeb, 26),
(0x7ffffe6, 27),
(0x3ffffec, 26),
(0x3ffffed, 26),
(0x7ffffe7, 27),
(0x7ffffe8, 27),
(0x7ffffe9, 27),
(0x7ffffea, 27),
(0x7ffffeb, 27),
(0xffffffe, 28),
(0x7ffffec, 27),
(0x7ffffed, 27),
(0x7ffffee, 27),
(0x7ffffef, 27),
(0x7fffff0, 27),
(0x3ffffee, 26),
(0x3fffffff, 30), ];
fn huffman_decode(input: &[u8]) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(input.len() * 2);
let mut acc: u64 = 0;
let mut acc_len: u8 = 0;
for &byte in input {
acc = (acc << 8) | (byte as u64);
acc_len += 8;
while acc_len >= 5 {
let mut matched = false;
let max_len = acc_len.min(30);
for try_len in 5..=max_len {
let code = (acc >> (acc_len - try_len)) & ((1u64 << try_len) - 1);
if let Some(sym) = lookup_huffman(code as u32, try_len) {
if sym == 256 {
return Err(Error::BadResponse(
"hpack: EOS symbol in Huffman literal".into(),
));
}
out.push(sym as u8);
acc_len -= try_len;
matched = true;
break;
}
}
if !matched {
break;
}
}
}
if acc_len >= 8 {
return Err(Error::BadResponse(
"hpack: trailing Huffman bits >= 8".into(),
));
}
if acc_len > 0 {
let pad_mask = (1u64 << acc_len) - 1;
let tail = acc & pad_mask;
if tail != pad_mask {
return Err(Error::BadResponse("hpack: bad Huffman padding".into()));
}
}
Ok(out)
}
fn lookup_huffman(code: u32, len: u8) -> Option<u16> {
for (i, (c, l)) in HUFFMAN.iter().enumerate() {
if *l == len && *c == code {
return Some(i as u16);
}
}
None
}
const DYN_TABLE_CAP: usize = 4096;
struct Decoder {
dyn_table: Vec<(String, String)>,
dyn_table_size: usize,
dyn_table_cap: usize,
}
impl Decoder {
fn new() -> Self {
Decoder {
dyn_table: Vec::new(),
dyn_table_size: 0,
dyn_table_cap: DYN_TABLE_CAP,
}
}
fn entry_size(name: &str, value: &str) -> usize {
name.len() + value.len() + 32
}
fn evict_to_fit(&mut self, incoming: usize) {
while self.dyn_table_size + incoming > self.dyn_table_cap && !self.dyn_table.is_empty() {
let (n, v) = self.dyn_table.pop().unwrap();
self.dyn_table_size = self.dyn_table_size.saturating_sub(Self::entry_size(&n, &v));
}
}
fn insert(&mut self, name: String, value: String) {
let sz = Self::entry_size(&name, &value);
if sz > self.dyn_table_cap {
self.dyn_table.clear();
self.dyn_table_size = 0;
return;
}
self.evict_to_fit(sz);
self.dyn_table.insert(0, (name, value));
self.dyn_table_size += sz;
}
fn lookup(&self, index: u64) -> Result<(String, String)> {
if index == 0 {
return Err(Error::BadResponse("hpack: index 0".into()));
}
let idx = index as usize;
if idx <= STATIC_TABLE.len() {
let (n, v) = STATIC_TABLE[idx - 1];
return Ok((n.to_string(), v.to_string()));
}
let dyn_idx = idx - STATIC_TABLE.len() - 1;
if dyn_idx >= self.dyn_table.len() {
return Err(Error::BadResponse(format!(
"hpack: index {idx} out of range"
)));
}
let (n, v) = &self.dyn_table[dyn_idx];
Ok((n.clone(), v.clone()))
}
fn lookup_name(&self, index: u64) -> Result<String> {
Ok(self.lookup(index)?.0)
}
fn read_string(&self, buf: &[u8], pos: &mut usize) -> Result<String> {
if *pos >= buf.len() {
return Err(Error::BadResponse("hpack: truncated string".into()));
}
let huffman = buf[*pos] & 0x80 != 0;
let (len, consumed) = decode_int(&buf[*pos..], 7)?;
*pos += consumed;
let end = pos
.checked_add(len as usize)
.ok_or_else(|| Error::BadResponse("hpack: string length overflow".into()))?;
if end > buf.len() {
return Err(Error::BadResponse("hpack: truncated string body".into()));
}
let raw = &buf[*pos..end];
*pos = end;
if huffman {
let bytes = huffman_decode(raw)?;
String::from_utf8(bytes)
.map_err(|_| Error::BadResponse("hpack: non-utf8 Huffman literal".into()))
} else {
String::from_utf8(raw.to_vec())
.map_err(|_| Error::BadResponse("hpack: non-utf8 literal".into()))
}
}
fn decode_block(&mut self, buf: &[u8]) -> Result<Vec<(String, String)>> {
let mut out = Vec::new();
let mut pos = 0;
while pos < buf.len() {
let b = buf[pos];
if b & 0x80 != 0 {
let (idx, n) = decode_int(&buf[pos..], 7)?;
pos += n;
out.push(self.lookup(idx)?);
} else if b & 0x40 != 0 {
let (idx, n) = decode_int(&buf[pos..], 6)?;
pos += n;
let name = if idx == 0 {
self.read_string(buf, &mut pos)?
} else {
self.lookup_name(idx)?
};
let value = self.read_string(buf, &mut pos)?;
self.insert(name.clone(), value.clone());
out.push((name, value));
} else if b & 0x20 != 0 {
let (new_size, n) = decode_int(&buf[pos..], 5)?;
pos += n;
let cap = (new_size as usize).min(DYN_TABLE_CAP);
self.dyn_table_cap = cap;
self.evict_to_fit(0);
} else {
let (idx, n) = decode_int(&buf[pos..], 4)?;
pos += n;
let name = if idx == 0 {
self.read_string(buf, &mut pos)?
} else {
self.lookup_name(idx)?
};
let value = self.read_string(buf, &mut pos)?;
out.push((name, value));
}
}
Ok(out)
}
}
fn hpack_encode_header(out: &mut Vec<u8>, name: &str, value: &str) {
if let Some(idx) = static_full_index(name, value) {
let mut bytes = encode_int(idx as u64, 7);
bytes[0] |= 0x80;
out.extend_from_slice(&bytes);
return;
}
if let Some(idx) = static_name_index(name) {
let mut bytes = encode_int(idx as u64, 4);
bytes[0] |= 0x00; out.extend_from_slice(&bytes);
encode_literal_string(out, value);
return;
}
out.push(0x00);
encode_literal_string(out, name);
encode_literal_string(out, value);
}
fn encode_literal_string(out: &mut Vec<u8>, s: &str) {
let bytes = s.as_bytes();
let mut len_bytes = encode_int(bytes.len() as u64, 7);
len_bytes[0] &= 0x7f; out.extend_from_slice(&len_bytes);
out.extend_from_slice(bytes);
}
fn tcp_connect(req: &Request) -> Result<TcpStream> {
let addr = format!("{}:{}", req.url.host, req.url.port);
let stream = match req.connect_timeout {
Some(t) => {
let first = std::net::ToSocketAddrs::to_socket_addrs(&addr)?
.next()
.ok_or_else(|| Error::InvalidUrl(req.url.host.clone()))?;
TcpStream::connect_timeout(&first, t)?
}
None => TcpStream::connect(&addr)?,
};
stream.set_read_timeout(req.read_timeout)?;
stream.set_write_timeout(req.read_timeout)?;
Ok(stream)
}
pub fn send(req: Request) -> Result<Response> {
if req.url.scheme != "https" {
return Err(Error::UnsupportedScheme(format!(
"http/2 over {} not supported",
req.url.scheme
)));
}
let tcp = tcp_connect(&req)?;
let opts = crate::http::tls_opts_from(&req, &[b"h2"])?;
let mut tls = crate::tls::connect_over_tls(tcp, &req.url.host, opts)?;
let negotiated_h2 = tls.alpn_selected().map(|p| p == b"h2").unwrap_or(false);
if !negotiated_h2 {
return Err(Error::H2NotNegotiated);
}
tls.write_all(PREFACE)?;
let our_settings = Frame {
typ: F_SETTINGS,
flags: 0,
stream_id: 0,
payload: Vec::new(),
};
write_frame(&mut tls, &our_settings)?;
tls.flush()?;
let header_block = build_header_block(&req);
let mut headers_flags = FLAG_END_HEADERS;
let has_body = !req.body.is_empty();
if !has_body {
headers_flags |= FLAG_END_STREAM;
}
if header_block.len() > 16384 {
return Err(Error::BadResponse(
"request headers exceed 16 KiB; CONTINUATION on encode not implemented".into(),
));
}
let headers_frame = Frame {
typ: F_HEADERS,
flags: headers_flags,
stream_id: 1,
payload: header_block,
};
write_frame(&mut tls, &headers_frame)?;
if has_body {
if req.body.len() > 16384 {
return Err(Error::BadResponse(
"request body exceeds 16 KiB; DATA fragmentation not implemented".into(),
));
}
let data_frame = Frame {
typ: F_DATA,
flags: FLAG_END_STREAM,
stream_id: 1,
payload: req.body.clone(),
};
write_frame(&mut tls, &data_frame)?;
}
tls.flush()?;
let mut decoder = Decoder::new();
let mut headers_buf: Vec<u8> = Vec::new();
let mut expecting_continuation = false;
let mut response_headers: Option<Vec<(String, String)>> = None;
let mut body: Vec<u8> = Vec::new();
let mut end_stream = false;
while !end_stream {
let frame = match read_frame(&mut tls) {
Ok(f) => f,
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
return Err(Error::UnexpectedEof);
}
Err(e) => return Err(Error::Io(e)),
};
if expecting_continuation && frame.typ != F_CONTINUATION {
return Err(Error::BadResponse(
"expected CONTINUATION between header fragments".into(),
));
}
match frame.typ {
F_SETTINGS
if frame.flags & FLAG_ACK == 0 => {
let ack = Frame {
typ: F_SETTINGS,
flags: FLAG_ACK,
stream_id: 0,
payload: Vec::new(),
};
write_frame(&mut tls, &ack)?;
tls.flush()?;
}
F_PING
if frame.flags & FLAG_ACK == 0 => {
let pong = Frame {
typ: F_PING,
flags: FLAG_ACK,
stream_id: 0,
payload: frame.payload.clone(),
};
write_frame(&mut tls, &pong)?;
tls.flush()?;
}
F_WINDOW_UPDATE => { }
F_GOAWAY
if response_headers.is_none() => {
return Err(Error::BadResponse(format!(
"server sent GOAWAY (payload {} bytes)",
frame.payload.len()
)));
}
F_RST_STREAM if frame.stream_id == 1 => {
let code = if frame.payload.len() >= 4 {
u32::from_be_bytes([
frame.payload[0],
frame.payload[1],
frame.payload[2],
frame.payload[3],
])
} else {
0
};
return Err(Error::BadResponse(format!(
"stream 1 reset by server, error code {code}"
)));
}
F_HEADERS if frame.stream_id == 1 => {
let mut payload = frame.payload.as_slice();
let mut pad_len = 0usize;
if frame.flags & FLAG_PADDED != 0 {
if payload.is_empty() {
return Err(Error::BadResponse(
"HEADERS PADDED with empty payload".into(),
));
}
pad_len = payload[0] as usize;
payload = &payload[1..];
}
if frame.flags & FLAG_PRIORITY != 0 {
if payload.len() < 5 {
return Err(Error::BadResponse(
"HEADERS PRIORITY with insufficient payload".into(),
));
}
payload = &payload[5..];
}
if payload.len() < pad_len {
return Err(Error::BadResponse(
"HEADERS padding overruns payload".into(),
));
}
let frag = &payload[..payload.len() - pad_len];
headers_buf.extend_from_slice(frag);
if frame.flags & FLAG_END_HEADERS != 0 {
let decoded = decoder.decode_block(&headers_buf)?;
headers_buf.clear();
response_headers = Some(decoded);
expecting_continuation = false;
} else {
expecting_continuation = true;
}
if frame.flags & FLAG_END_STREAM != 0 {
end_stream = true;
}
}
F_CONTINUATION if frame.stream_id == 1 => {
if !expecting_continuation {
return Err(Error::BadResponse("unexpected CONTINUATION frame".into()));
}
headers_buf.extend_from_slice(&frame.payload);
if frame.flags & FLAG_END_HEADERS != 0 {
let decoded = decoder.decode_block(&headers_buf)?;
headers_buf.clear();
response_headers = Some(decoded);
expecting_continuation = false;
}
}
F_DATA if frame.stream_id == 1 => {
let mut payload = frame.payload.as_slice();
if frame.flags & FLAG_PADDED != 0 {
if payload.is_empty() {
return Err(Error::BadResponse("DATA PADDED with empty payload".into()));
}
let pad_len = payload[0] as usize;
payload = &payload[1..];
if payload.len() < pad_len {
return Err(Error::BadResponse("DATA padding overruns payload".into()));
}
payload = &payload[..payload.len() - pad_len];
}
body.extend_from_slice(payload);
if frame.flags & FLAG_END_STREAM != 0 {
end_stream = true;
}
}
_ => {
}
}
}
let headers = response_headers
.ok_or_else(|| Error::BadResponse("response ended before any HEADERS frame".into()))?;
let mut status: Option<u16> = None;
let mut clean_headers: Vec<(String, String)> = Vec::with_capacity(headers.len());
for (k, v) in headers {
if k == ":status" {
status = Some(
v.parse::<u16>()
.map_err(|_| Error::BadResponse(format!("bad :status {v:?}")))?,
);
} else if k.starts_with(':') {
} else {
clean_headers.push((k, v));
}
}
let status = status.ok_or_else(|| Error::BadResponse("response missing :status".into()))?;
Ok(Response {
status,
reason: String::new(), version: "HTTP/2".to_string(),
headers: clean_headers,
body,
})
}
fn build_header_block(req: &Request) -> Vec<u8> {
let mut out = Vec::new();
hpack_encode_header(&mut out, ":method", &req.method);
hpack_encode_header(&mut out, ":scheme", &req.url.scheme);
let authority = if req.url.port == 443 && req.url.scheme == "https" {
req.url.host.clone()
} else {
format!("{}:{}", req.url.host, req.url.port)
};
hpack_encode_header(&mut out, ":authority", &authority);
hpack_encode_header(&mut out, ":path", &req.url.path);
let mut have_ua = false;
let mut have_accept = false;
let mut have_auth = false;
for (k, v) in &req.headers {
if is_connection_specific_header(k) || k.eq_ignore_ascii_case("host") {
continue;
}
let lk = k.to_ascii_lowercase();
if lk == "user-agent" {
have_ua = true;
}
if lk == "accept" {
have_accept = true;
}
if lk == "authorization" {
have_auth = true;
}
hpack_encode_header(&mut out, &lk, v);
}
if !have_auth {
if let Some(creds) = crate::http::effective_basic_auth(req) {
let value = format!("Basic {creds}");
hpack_encode_header(&mut out, "authorization", &value);
}
}
if !have_ua {
hpack_encode_header(
&mut out,
"user-agent",
concat!("rsurl/", env!("CARGO_PKG_VERSION")),
);
}
if !have_accept {
hpack_encode_header(&mut out, "accept", "*/*");
}
if !req.body.is_empty() {
let len = req.body.len().to_string();
hpack_encode_header(&mut out, "content-length", &len);
}
out
}
fn is_connection_specific_header(name: &str) -> bool {
matches!(
name.to_ascii_lowercase().as_str(),
"connection" | "proxy-connection" | "keep-alive" | "transfer-encoding" | "upgrade" | "te" )
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn int_encode_small() {
assert_eq!(encode_int(10, 5), vec![10]);
}
#[test]
fn int_encode_large() {
assert_eq!(encode_int(1337, 5), vec![0x1f, 0x9a, 0x0a]);
}
#[test]
fn int_encode_eight_bit() {
assert_eq!(encode_int(42, 8), vec![42]);
}
#[test]
fn int_decode_round_trips() {
for &(v, p) in &[
(0u64, 5),
(10, 5),
(30, 5),
(31, 5),
(1337, 5),
(1, 8),
(255, 8),
] {
let enc = encode_int(v, p);
let (dec, n) = decode_int(&enc, p).unwrap();
assert_eq!(dec, v, "value {v} with {p}-bit prefix");
assert_eq!(n, enc.len());
}
}
#[test]
fn int_decode_truncated_errors() {
assert!(decode_int(&[0x1f], 5).is_err());
assert!(decode_int(&[0x1f, 0x80], 5).is_err());
}
#[test]
fn static_table_method_get() {
assert_eq!(static_full_index(":method", "GET"), Some(2));
}
#[test]
fn static_table_method_post() {
assert_eq!(static_full_index(":method", "POST"), Some(3));
}
#[test]
fn static_table_name_only() {
assert_eq!(static_name_index(":status"), Some(8));
assert_eq!(static_name_index("user-agent"), Some(58));
assert_eq!(static_name_index("does-not-exist"), None);
}
#[test]
fn static_table_length() {
assert_eq!(STATIC_TABLE.len(), 61);
}
#[test]
fn frame_round_trip_empty_settings() {
let f = Frame {
typ: F_SETTINGS,
flags: 0,
stream_id: 0,
payload: Vec::new(),
};
let mut buf = Vec::new();
write_frame(&mut buf, &f).unwrap();
assert_eq!(buf.len(), 9);
let mut cur = Cursor::new(buf);
let g = read_frame(&mut cur).unwrap();
assert_eq!(g, f);
}
#[test]
fn frame_round_trip_headers_with_payload() {
let f = Frame {
typ: F_HEADERS,
flags: FLAG_END_STREAM | FLAG_END_HEADERS,
stream_id: 1,
payload: vec![
0x82, 0x86, 0x84, 0x41, 0x88, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab,
0x90, 0xf4, 0xff,
],
};
let mut buf = Vec::new();
write_frame(&mut buf, &f).unwrap();
let mut cur = Cursor::new(buf);
let g = read_frame(&mut cur).unwrap();
assert_eq!(g, f);
assert_eq!(g.flags, 0x05);
}
#[test]
fn frame_stream_id_high_bit_masked_on_read() {
let buf = vec![0, 0, 0, F_DATA, 0, 0x80, 0, 0, 1];
let mut cur = Cursor::new(buf);
let f = read_frame(&mut cur).unwrap();
assert_eq!(f.stream_id, 1);
}
#[test]
fn hpack_encode_indexed_method() {
let mut out = Vec::new();
hpack_encode_header(&mut out, ":method", "GET");
assert_eq!(out, vec![0x82]);
}
#[test]
fn hpack_encode_literal_with_indexed_name() {
let mut out = Vec::new();
hpack_encode_header(&mut out, ":path", "/foo");
assert_eq!(out, vec![0x04, 0x04, b'/', b'f', b'o', b'o']);
}
#[test]
fn hpack_encode_literal_full() {
let mut out = Vec::new();
hpack_encode_header(&mut out, "x-custom", "yes");
let expected = {
let mut v = vec![0x00, 0x08];
v.extend_from_slice(b"x-custom");
v.push(0x03);
v.extend_from_slice(b"yes");
v
};
assert_eq!(out, expected);
}
#[test]
fn hpack_decode_round_trip_pseudo_headers() {
let mut block = Vec::new();
hpack_encode_header(&mut block, ":method", "GET");
hpack_encode_header(&mut block, ":scheme", "https");
hpack_encode_header(&mut block, ":authority", "example.com");
hpack_encode_header(&mut block, ":path", "/");
let mut dec = Decoder::new();
let got = dec.decode_block(&block).unwrap();
assert_eq!(got.len(), 4);
assert_eq!(got[0], (":method".into(), "GET".into()));
assert_eq!(got[1], (":scheme".into(), "https".into()));
assert_eq!(got[2], (":authority".into(), "example.com".into()));
assert_eq!(got[3], (":path".into(), "/".into()));
}
#[test]
fn hpack_decode_indexed_static() {
let mut dec = Decoder::new();
let got = dec.decode_block(&[0x82]).unwrap();
assert_eq!(got, vec![(":method".into(), "GET".into())]);
}
#[test]
fn hpack_decode_literal_with_incremental_indexing() {
let buf: Vec<u8> = vec![
0x40, 0x0a, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', b'y', 0x0d, b'c',
b'u', b's', b't', b'o', b'm', b'-', b'h', b'e', b'a', b'd', b'e', b'r',
];
let mut dec = Decoder::new();
let got = dec.decode_block(&buf).unwrap();
assert_eq!(got, vec![("custom-key".into(), "custom-header".into())]);
assert_eq!(dec.dyn_table.len(), 1);
}
#[test]
fn huffman_decode_c4_1() {
let coded = [
0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
];
let out = huffman_decode(&coded).unwrap();
assert_eq!(out, b"www.example.com");
}
#[test]
fn huffman_decode_c4_2() {
let coded = [0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf];
let out = huffman_decode(&coded).unwrap();
assert_eq!(out, b"no-cache");
}
#[test]
fn huffman_decode_c4_3() {
let coded = [0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f];
let out = huffman_decode(&coded).unwrap();
assert_eq!(out, b"custom-key");
}
#[test]
fn huffman_decode_rejects_short_padding() {
assert!(huffman_decode(&[0x00]).is_err());
}
#[test]
fn hpack_decode_huffman_literal_value() {
let buf = vec![
0x44, 0x8c, 0x60, 0xd4, 0x85, 0x31, 0x68, 0xdf, 0x1c, 0x6f, 0xa2, 0xa6, 0xfd, 0x95,
0xb6, 0x88,
];
let _ = Decoder::new().decode_block(&buf);
}
#[test]
fn build_header_block_includes_pseudo() {
let req = Request::new("GET", "https://example.com/foo").unwrap();
let block = build_header_block(&req);
let mut dec = Decoder::new();
let headers = dec.decode_block(&block).unwrap();
let kv: Vec<(&str, &str)> = headers
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
assert!(kv.contains(&(":method", "GET")));
assert!(kv.contains(&(":scheme", "https")));
assert!(kv.contains(&(":authority", "example.com")));
assert!(kv.contains(&(":path", "/foo")));
assert!(kv.iter().any(|(k, _)| *k == "user-agent"));
assert!(kv.iter().any(|(k, _)| *k == "accept"));
}
#[test]
fn build_header_block_strips_banned_headers() {
let req = Request::new("GET", "https://example.com/")
.unwrap()
.header("Connection", "close")
.header("Host", "evil.example")
.header("X-Allowed", "yes");
let block = build_header_block(&req);
let mut dec = Decoder::new();
let headers = dec.decode_block(&block).unwrap();
let names: Vec<&str> = headers.iter().map(|(k, _)| k.as_str()).collect();
assert!(!names.contains(&"connection"));
assert!(!names.contains(&"host"));
assert!(names.contains(&"x-allowed"));
}
#[test]
fn build_header_block_authority_includes_nonstandard_port() {
let req = Request::new("GET", "https://example.com:8443/").unwrap();
let block = build_header_block(&req);
let mut dec = Decoder::new();
let headers = dec.decode_block(&block).unwrap();
let auth = headers.iter().find(|(k, _)| k == ":authority").unwrap();
assert_eq!(auth.1, "example.com:8443");
}
#[test]
fn decoder_dynamic_table_size_update_caps_to_4096() {
let mut dec = Decoder::new();
dec.decode_block(&[0x20]).unwrap();
assert_eq!(dec.dyn_table_cap, 0);
}
#[test]
fn decoder_rejects_oversize_index() {
let mut dec = Decoder::new();
let err = dec.decode_block(&[0xff, 0x01]).unwrap_err();
match err {
Error::BadResponse(_) => {}
other => panic!("expected BadResponse, got {other:?}"),
}
}
}