///|
#cfg(target="native")
pub suberror HpackError {
HpackBadInteger
HpackBadString
HpackBadHuffmanPadding
HpackHuffmanEOS
HpackBadIndex(Int)
HpackBadDynamicTableSize(Int)
} derive(Debug, ToJson)
///|
#cfg(target="native")
let hpack_static_table_len : Int = 61
///|
#cfg(target="native")
priv struct HpackDecoder {
dynamic_table : Array[(String, String)]
mut dynamic_size : Int
mut max_dynamic_size : Int
}
///|
#cfg(target="native")
fn HpackDecoder::new(max_dynamic_size? : Int = 4096) -> HpackDecoder {
{ dynamic_table: [], dynamic_size: 0, max_dynamic_size }
}
///|
#cfg(target="native")
fn hpack_entry_size(name : String, value : String) -> Int {
name.length() + value.length() + 32
}
///|
#cfg(target="native")
fn HpackDecoder::evict(self : HpackDecoder) -> Unit {
while self.dynamic_size > self.max_dynamic_size &&
!self.dynamic_table.is_empty() {
let last = self.dynamic_table[self.dynamic_table.length() - 1]
let (name, value) = last
self.dynamic_size -= hpack_entry_size(name, value)
self.dynamic_table.unsafe_pop_back()
}
}
///|
#cfg(target="native")
fn HpackDecoder::set_max_dynamic_size(
self : HpackDecoder,
max_size : Int,
) -> Unit raise {
guard max_size >= 0 else { raise HpackBadDynamicTableSize(max_size) }
self.max_dynamic_size = max_size
self.evict()
}
///|
#cfg(target="native")
fn HpackDecoder::insert(
self : HpackDecoder,
name : String,
value : String,
) -> Unit {
let size = hpack_entry_size(name, value)
if size > self.max_dynamic_size {
self.dynamic_table.clear()
self.dynamic_size = 0
return
}
self.dynamic_table.push((name, value))
for i = self.dynamic_table.length() - 1; i > 0; i = i - 1 {
self.dynamic_table[i] = self.dynamic_table[i - 1]
}
self.dynamic_table[0] = (name, value)
self.dynamic_size += size
self.evict()
}
///|
#cfg(target="native")
fn HpackDecoder::get(
self : HpackDecoder,
index : Int,
) -> (String, String) raise {
guard index > 0 else { raise HpackBadIndex(index) }
if index <= hpack_static_table_len {
hpack_static_entry(index)
} else {
let dynamic_index = index - hpack_static_table_len - 1
guard dynamic_index >= 0 && dynamic_index < self.dynamic_table.length() else {
raise HpackBadIndex(index)
}
self.dynamic_table[dynamic_index]
}
}
///|
#cfg(target="native")
fn hpack_static_entry(index : Int) -> (String, String) raise {
match index {
1 => (":authority", "")
2 => (":method", "GET")
3 => (":method", "POST")
4 => (":path", "/")
5 => (":path", "/index.html")
6 => (":scheme", "http")
7 => (":scheme", "https")
8 => (":status", "200")
9 => (":status", "204")
10 => (":status", "206")
11 => (":status", "304")
12 => (":status", "400")
13 => (":status", "404")
14 => (":status", "500")
15 => ("accept-charset", "")
16 => ("accept-encoding", "gzip, deflate")
17 => ("accept-language", "")
18 => ("accept-ranges", "")
19 => ("accept", "")
20 => ("access-control-allow-origin", "")
21 => ("age", "")
22 => ("allow", "")
23 => ("authorization", "")
24 => ("cache-control", "")
25 => ("content-disposition", "")
26 => ("content-encoding", "")
27 => ("content-language", "")
28 => ("content-length", "")
29 => ("content-location", "")
30 => ("content-range", "")
31 => ("content-type", "")
32 => ("cookie", "")
33 => ("date", "")
34 => ("etag", "")
35 => ("expect", "")
36 => ("expires", "")
37 => ("from", "")
38 => ("host", "")
39 => ("if-match", "")
40 => ("if-modified-since", "")
41 => ("if-none-match", "")
42 => ("if-range", "")
43 => ("if-unmodified-since", "")
44 => ("last-modified", "")
45 => ("link", "")
46 => ("location", "")
47 => ("max-forwards", "")
48 => ("proxy-authenticate", "")
49 => ("proxy-authorization", "")
50 => ("range", "")
51 => ("referer", "")
52 => ("refresh", "")
53 => ("retry-after", "")
54 => ("server", "")
55 => ("set-cookie", "")
56 => ("strict-transport-security", "")
57 => ("transfer-encoding", "")
58 => ("user-agent", "")
59 => ("vary", "")
60 => ("via", "")
61 => ("www-authenticate", "")
_ => raise HpackBadIndex(index)
}
}
///|
#cfg(target="native")
fn hpack_static_name_index(name : String) -> Int {
match name {
":authority" => 1
":method" => 2
":path" => 4
":scheme" => 6
":status" => 8
"accept-encoding" => 16
"accept" => 19
"authorization" => 23
"cache-control" => 24
"content-encoding" => 26
"content-length" => 28
"content-type" => 31
"cookie" => 32
"host" => 38
"location" => 46
"set-cookie" => 55
"user-agent" => 58
_ => 0
}
}
///|
#cfg(target="native")
fn hpack_write_int(
out : @buffer.Buffer,
value : Int,
prefix_bits~ : Int,
first_mask~ : Int,
) -> Unit {
let max_prefix = (1 << prefix_bits) - 1
if value < max_prefix {
out.write_byte((first_mask | value).to_byte())
} else {
out.write_byte((first_mask | max_prefix).to_byte())
for remaining = value - max_prefix {
if remaining >= 128 {
out.write_byte(((remaining % 128) | 128).to_byte())
continue remaining / 128
} else {
out.write_byte(remaining.to_byte())
break
}
}
}
}
///|
#cfg(target="native")
fn hpack_read_int(
data : Bytes,
offset : Int,
prefix_bits~ : Int,
) -> (Int, Int) raise {
guard offset < data.length() else { raise HpackBadInteger }
let mask = (1 << prefix_bits) - 1
let first = data[offset].to_int() & mask
if first < mask {
(first, offset + 1)
} else {
let mut value = mask
let mut shift = 0
let mut pos = offset + 1
for ;; {
guard pos < data.length() else { raise HpackBadInteger }
let b = data[pos].to_int()
value += (b & 0x7f) << shift
pos += 1
if (b & 0x80) == 0 {
break
}
shift += 7
guard shift <= 28 else { raise HpackBadInteger }
}
(value, pos)
}
}
///|
#cfg(target="native")
fn hpack_write_string(out : @buffer.Buffer, value : String) -> Unit {
let bytes = @utf8.encode(value)
hpack_write_int(out, bytes.length(), prefix_bits=7, first_mask=0)
out.write_bytes(bytes)
}
///|
#cfg(target="native")
fn hpack_read_string(data : Bytes, offset : Int) -> (String, Int) raise {
guard offset < data.length() else { raise HpackBadString }
let is_huffman = (data[offset].to_int() & 0x80) != 0
let (len, pos) = hpack_read_int(data, offset, prefix_bits=7)
guard len >= 0 && pos + len <= data.length() else { raise HpackBadString }
let raw = @buffer.new()
raw.write_bytes(data[pos:pos + len])
let decoded = if is_huffman {
hpack_huffman_decode(raw.contents())
} else {
raw.contents()
}
(@utf8.decode_lossy(decoded), pos + len)
}
///|
#cfg(target="native")
fn hpack_write_header(
out : @buffer.Buffer,
name : String,
value : String,
) -> Unit {
let name = name.to_lower()
let index = hpack_static_name_index(name)
if index > 0 {
hpack_write_int(out, index, prefix_bits=4, first_mask=0)
} else {
out.write_byte(b'\x00')
hpack_write_string(out, name)
}
hpack_write_string(out, value)
}
///|
#cfg(target="native")
fn hpack_encode_headers(headers : Array[(String, String)]) -> Bytes {
let out = @buffer.new()
for item in headers {
let (name, value) = item
hpack_write_header(out, name, value)
}
out.contents()
}
///|
#cfg(target="native")
fn HpackDecoder::decode(
self : HpackDecoder,
block : Bytes,
) -> Array[(String, String)] raise {
let headers = []
for pos = 0; pos < block.length(); {
let first = block[pos].to_int()
if (first & 0x80) != 0 {
let (index, next) = hpack_read_int(block, pos, prefix_bits=7)
let entry = self.get(index)
headers.push(entry)
continue next
} else if (first & 0x40) != 0 {
let (name_index, pos) = hpack_read_int(block, pos, prefix_bits=6)
let (name, pos) = if name_index == 0 {
hpack_read_string(block, pos)
} else {
let (name, _) = self.get(name_index)
(name, pos)
}
let (value, pos) = hpack_read_string(block, pos)
headers.push((name, value))
self.insert(name, value)
continue pos
} else if (first & 0x20) != 0 {
let (max_size, next) = hpack_read_int(block, pos, prefix_bits=5)
self.set_max_dynamic_size(max_size)
continue next
} else {
let (name_index, pos) = hpack_read_int(block, pos, prefix_bits=4)
let (name, pos) = if name_index == 0 {
hpack_read_string(block, pos)
} else {
let (name, _) = self.get(name_index)
(name, pos)
}
let (value, pos) = hpack_read_string(block, pos)
headers.push((name, value))
continue pos
}
}
headers
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn hpack_decode_headers(block : Bytes) -> Array[(String, String)] raise {
HpackDecoder::new().decode(block)
}