| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module http |
| 5 | |
| 6 | // This file implements HPACK header compression (RFC 7541), used by HTTP/2. |
| 7 | // It is self-contained: it only depends on the static and Huffman tables in |
| 8 | // the sibling h2_hpack_*.v files, and does not touch the rest of net.http. |
| 9 | |
| 10 | // h2_hpack_default_table_size is the default maximum size of the HPACK |
| 11 | // dynamic table, in bytes (RFC 7541 Section 4.2, and the default value of |
| 12 | // SETTINGS_HEADER_TABLE_SIZE). |
| 13 | pub const h2_hpack_default_table_size = 4096 |
| 14 | |
| 15 | // H2HeaderField is a single HTTP/2 header field (a name/value pair). |
| 16 | // Field names are expected to be lowercase, as required by RFC 7540. |
| 17 | pub struct H2HeaderField { |
| 18 | pub: |
| 19 | name string |
| 20 | value string |
| 21 | } |
| 22 | |
| 23 | // H2DynEntry is one entry in the HPACK dynamic table. |
| 24 | struct H2DynEntry { |
| 25 | name string |
| 26 | value string |
| 27 | size int // name.len + value.len + 32 (RFC 7541 Section 4.1) |
| 28 | } |
| 29 | |
| 30 | // H2DynTable is the HPACK dynamic table: a size-bounded FIFO of header fields. |
| 31 | // entries[0] is always the most recently added entry, which corresponds to the |
| 32 | // lowest dynamic index in the HPACK index space. |
| 33 | struct H2DynTable { |
| 34 | mut: |
| 35 | entries []H2DynEntry |
| 36 | cur_size int |
| 37 | max_size int = h2_hpack_default_table_size |
| 38 | } |
| 39 | |
| 40 | // add inserts a new entry at the front of the table, evicting the oldest |
| 41 | // entries until it fits. Per RFC 7541 Section 4.4, an entry larger than the |
| 42 | // whole table empties it and is not added. |
| 43 | fn (mut t H2DynTable) add(name string, value string) { |
| 44 | sz := name.len + value.len + 32 |
| 45 | for t.entries.len > 0 && t.cur_size + sz > t.max_size { |
| 46 | t.cur_size -= t.entries.last().size |
| 47 | t.entries.delete_last() |
| 48 | } |
| 49 | if sz > t.max_size { |
| 50 | return |
| 51 | } |
| 52 | t.entries.insert(0, H2DynEntry{ |
| 53 | name: name |
| 54 | value: value |
| 55 | size: sz |
| 56 | }) |
| 57 | t.cur_size += sz |
| 58 | } |
| 59 | |
| 60 | // set_max_size updates the maximum table size, evicting oldest entries as |
| 61 | // needed to respect the new limit. |
| 62 | fn (mut t H2DynTable) set_max_size(n int) { |
| 63 | t.max_size = n |
| 64 | for t.entries.len > 0 && t.cur_size > t.max_size { |
| 65 | t.cur_size -= t.entries.last().size |
| 66 | t.entries.delete_last() |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | // get returns the dynamic-table entry at 1-based dynamic index `i` |
| 71 | // (i.e. i==1 is the newest entry), or none if out of range. |
| 72 | fn (t &H2DynTable) get(i int) ?H2HeaderField { |
| 73 | if i < 1 || i > t.entries.len { |
| 74 | return none |
| 75 | } |
| 76 | e := t.entries[i - 1] |
| 77 | return H2HeaderField{ |
| 78 | name: e.name |
| 79 | value: e.value |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | // H2HpackReader is a cursor over an HPACK header block being decoded. |
| 84 | struct H2HpackReader { |
| 85 | buf []u8 |
| 86 | mut: |
| 87 | pos int |
| 88 | } |
| 89 | |
| 90 | // read_int reads an HPACK variable-length integer with a prefix of |
| 91 | // `prefix_bits` bits at the current position, advancing past it |
| 92 | // (RFC 7541 Section 5.1). |
| 93 | fn (mut r H2HpackReader) read_int(prefix_bits int) !u64 { |
| 94 | if r.pos >= r.buf.len { |
| 95 | return error('hpack: integer truncated') |
| 96 | } |
| 97 | max_prefix := u64((1 << prefix_bits) - 1) |
| 98 | mut value := u64(r.buf[r.pos]) & max_prefix |
| 99 | r.pos++ |
| 100 | if value < max_prefix { |
| 101 | return value |
| 102 | } |
| 103 | mut m := 0 |
| 104 | for { |
| 105 | if r.pos >= r.buf.len { |
| 106 | return error('hpack: integer continuation truncated') |
| 107 | } |
| 108 | b := r.buf[r.pos] |
| 109 | r.pos++ |
| 110 | value += u64(b & 0x7f) << m |
| 111 | m += 7 |
| 112 | // A 64-bit value never needs more than 9 continuation bytes; reject |
| 113 | // pathological inputs early (RFC 7541 Section 5.1 security note). |
| 114 | if m > 63 { |
| 115 | return error('hpack: integer overflow') |
| 116 | } |
| 117 | if b & 0x80 == 0 { |
| 118 | break |
| 119 | } |
| 120 | } |
| 121 | return value |
| 122 | } |
| 123 | |
| 124 | // read_string reads an HPACK string literal at the current position, |
| 125 | // advancing past it and decoding Huffman coding when the H bit is set. |
| 126 | fn (mut r H2HpackReader) read_string() !string { |
| 127 | if r.pos >= r.buf.len { |
| 128 | return error('hpack: string truncated') |
| 129 | } |
| 130 | huffman := (r.buf[r.pos] & 0x80) != 0 |
| 131 | length := r.read_int(7)! |
| 132 | // Compare in u64 space, before narrowing to int, so an oversized length |
| 133 | // from a malicious peer cannot truncate past this bounds check. |
| 134 | if length > u64(r.buf.len - r.pos) { |
| 135 | return error('hpack: string length exceeds buffer') |
| 136 | } |
| 137 | n := int(length) |
| 138 | raw := r.buf[r.pos..r.pos + n] |
| 139 | r.pos += n |
| 140 | if huffman { |
| 141 | return h2_huffman_decode(raw)!.bytestr() |
| 142 | } |
| 143 | return raw.bytestr() |
| 144 | } |
| 145 | |
| 146 | // h2_hpack_write_int appends an HPACK variable-length integer to `out`, using |
| 147 | // a prefix of `prefix_bits` bits whose high bits are set from `high_bits`. |
| 148 | fn h2_hpack_write_int(mut out []u8, value u64, prefix_bits int, high_bits u8) { |
| 149 | max_prefix := u64((1 << prefix_bits) - 1) |
| 150 | if value < max_prefix { |
| 151 | out << high_bits | u8(value) |
| 152 | return |
| 153 | } |
| 154 | out << high_bits | u8(max_prefix) |
| 155 | mut v := value - max_prefix |
| 156 | for v >= 0x80 { |
| 157 | out << u8((v & 0x7f) | 0x80) |
| 158 | v >>= 7 |
| 159 | } |
| 160 | out << u8(v) |
| 161 | } |
| 162 | |
| 163 | // h2_encode_string appends an HPACK string literal to `out`, choosing Huffman |
| 164 | // coding when it is shorter than the raw bytes (RFC 7541 Section 5.2). |
| 165 | fn h2_encode_string(mut out []u8, s string) { |
| 166 | raw := s.bytes() |
| 167 | huff := h2_huffman_encode(raw) |
| 168 | if huff.len < raw.len { |
| 169 | h2_hpack_write_int(mut out, u64(huff.len), 7, 0x80) // H = 1 |
| 170 | out << huff |
| 171 | } else { |
| 172 | h2_hpack_write_int(mut out, u64(raw.len), 7, 0x00) // H = 0 |
| 173 | out << raw |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | // h2_is_sensitive reports whether a header should never be added to the HPACK |
| 178 | // dynamic table (encoded as "never indexed"), to avoid CRIME-style attacks. |
| 179 | fn h2_is_sensitive(name string) bool { |
| 180 | return name == 'cookie' || name == 'authorization' || name == 'proxy-authorization' |
| 181 | } |
| 182 | |
| 183 | // h2_hpack_find_static searches the static table for `name`/`value`. |
| 184 | // It returns the 1-based HPACK index and whether the value also matched. |
| 185 | // The index is 0 when the name is not present at all. |
| 186 | fn h2_hpack_find_static(name string, value string) (int, bool) { |
| 187 | mut name_idx := 0 |
| 188 | for i, f in h2_hpack_static_table { |
| 189 | if f.name == name { |
| 190 | if f.value == value { |
| 191 | return i + 1, true |
| 192 | } |
| 193 | if name_idx == 0 { |
| 194 | name_idx = i + 1 |
| 195 | } |
| 196 | } |
| 197 | } |
| 198 | return name_idx, false |
| 199 | } |
| 200 | |
| 201 | // H2HpackEncoder encodes header field lists into HPACK header blocks. |
| 202 | // This encoder never adds entries to the dynamic table: it uses indexed |
| 203 | // representations for static-table hits and literal-without-indexing (or |
| 204 | // never-indexed, for sensitive headers) otherwise. That keeps encoder and |
| 205 | // decoder state trivially in sync while remaining fully interoperable. |
| 206 | pub struct H2HpackEncoder { |
| 207 | pub mut: |
| 208 | dyn_table H2DynTable |
| 209 | } |
| 210 | |
| 211 | // encode returns the HPACK header block for `fields`. |
| 212 | pub fn (mut e H2HpackEncoder) encode(fields []H2HeaderField) []u8 { |
| 213 | mut out := []u8{} |
| 214 | for f in fields { |
| 215 | e.encode_field(mut out, f) |
| 216 | } |
| 217 | return out |
| 218 | } |
| 219 | |
| 220 | fn (mut e H2HpackEncoder) encode_field(mut out []u8, f H2HeaderField) { |
| 221 | sensitive := h2_is_sensitive(f.name) |
| 222 | idx, exact := h2_hpack_find_static(f.name, f.value) |
| 223 | if exact && !sensitive { |
| 224 | // Indexed Header Field (RFC 7541 Section 6.1). |
| 225 | h2_hpack_write_int(mut out, u64(idx), 7, 0x80) |
| 226 | return |
| 227 | } |
| 228 | // Literal Header Field without Indexing (6.2.2) or Never Indexed (6.2.3). |
| 229 | pattern := if sensitive { u8(0x10) } else { u8(0x00) } |
| 230 | if idx != 0 { |
| 231 | h2_hpack_write_int(mut out, u64(idx), 4, pattern) |
| 232 | } else { |
| 233 | h2_hpack_write_int(mut out, 0, 4, pattern) |
| 234 | h2_encode_string(mut out, f.name) |
| 235 | } |
| 236 | h2_encode_string(mut out, f.value) |
| 237 | } |
| 238 | |
| 239 | // H2HpackDecoder decodes HPACK header blocks into header field lists, |
| 240 | // maintaining the dynamic table across calls on the same connection. |
| 241 | pub struct H2HpackDecoder { |
| 242 | pub mut: |
| 243 | dyn_table H2DynTable |
| 244 | max_dynamic_size int = h2_hpack_default_table_size // upper bound we advertised to the peer |
| 245 | } |
| 246 | |
| 247 | // lookup resolves a 1-based HPACK index against the static and dynamic tables. |
| 248 | fn (d &H2HpackDecoder) lookup(idx u64) !H2HeaderField { |
| 249 | if idx == 0 { |
| 250 | return error('hpack: index 0 is not valid') |
| 251 | } |
| 252 | if idx <= u64(h2_hpack_static_len) { |
| 253 | return h2_hpack_static_table[idx - 1] |
| 254 | } |
| 255 | // Validate the dynamic index in u64 space before narrowing to int, so an |
| 256 | // out-of-range index from a malicious peer cannot truncate onto a valid |
| 257 | // dynamic entry. |
| 258 | dyn_idx := idx - u64(h2_hpack_static_len) |
| 259 | if dyn_idx > u64(d.dyn_table.entries.len) { |
| 260 | return error('hpack: index ${idx} out of range') |
| 261 | } |
| 262 | return d.dyn_table.get(int(dyn_idx)) or { error('hpack: index ${idx} out of range') } |
| 263 | } |
| 264 | |
| 265 | fn (d &H2HpackDecoder) read_literal(mut r H2HpackReader, prefix_bits int) !H2HeaderField { |
| 266 | name_index := r.read_int(prefix_bits)! |
| 267 | name := if name_index == 0 { |
| 268 | r.read_string()! |
| 269 | } else { |
| 270 | d.lookup(name_index)!.name |
| 271 | } |
| 272 | value := r.read_string()! |
| 273 | return H2HeaderField{ |
| 274 | name: name |
| 275 | value: value |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | // decode parses one HPACK header block and returns its header fields, |
| 280 | // updating the dynamic table as instructed by the block. |
| 281 | pub fn (mut d H2HpackDecoder) decode(block []u8) ![]H2HeaderField { |
| 282 | mut out := []H2HeaderField{} |
| 283 | mut r := H2HpackReader{ |
| 284 | buf: block |
| 285 | } |
| 286 | mut seen_field := false |
| 287 | for r.pos < block.len { |
| 288 | b := block[r.pos] |
| 289 | if b & 0x80 != 0 { |
| 290 | // Indexed Header Field (RFC 7541 Section 6.1). |
| 291 | idx := r.read_int(7)! |
| 292 | out << d.lookup(idx)! |
| 293 | seen_field = true |
| 294 | } else if b & 0x40 != 0 { |
| 295 | // Literal Header Field with Incremental Indexing (6.2.1). |
| 296 | f := d.read_literal(mut r, 6)! |
| 297 | d.dyn_table.add(f.name, f.value) |
| 298 | out << f |
| 299 | seen_field = true |
| 300 | } else if b & 0x20 != 0 { |
| 301 | // Dynamic Table Size Update (6.3). Must precede any header field. |
| 302 | if seen_field { |
| 303 | return error('hpack: dynamic table size update after a header field') |
| 304 | } |
| 305 | new_size := r.read_int(5)! |
| 306 | if int(new_size) > d.max_dynamic_size { |
| 307 | return error('hpack: dynamic table size update ${new_size} exceeds limit ${d.max_dynamic_size}') |
| 308 | } |
| 309 | d.dyn_table.set_max_size(int(new_size)) |
| 310 | } else { |
| 311 | // Literal without Indexing (6.2.2) or Never Indexed (6.2.3); |
| 312 | // neither updates the dynamic table. |
| 313 | f := d.read_literal(mut r, 4)! |
| 314 | out << f |
| 315 | seen_field = true |
| 316 | } |
| 317 | } |
| 318 | return out |
| 319 | } |
| 320 | |
| 321 | // set_max_dynamic_size updates the maximum dynamic-table size the decoder will |
| 322 | // accept (i.e. the SETTINGS_HEADER_TABLE_SIZE value advertised to the peer), |
| 323 | // shrinking the table immediately if needed. |
| 324 | pub fn (mut d H2HpackDecoder) set_max_dynamic_size(n int) { |
| 325 | d.max_dynamic_size = n |
| 326 | if d.dyn_table.max_size > n { |
| 327 | d.dyn_table.set_max_size(n) |
| 328 | } |
| 329 | } |
| 330 | |