Skip to main content

simoom_qpack/
prim.rs

1//! Static table (RFC 9204 Appendix A), prefixed integers and string literals.
2
3use crate::huffman;
4
5/// The QPACK static table, 99 entries (RFC 9204 Appendix A).
6pub static STATIC_TABLE: [(&str, &str); 99] = [
7    (":authority", ""),
8    (":path", "/"),
9    ("age", "0"),
10    ("content-disposition", ""),
11    ("content-length", "0"),
12    ("cookie", ""),
13    ("date", ""),
14    ("etag", ""),
15    ("if-modified-since", ""),
16    ("if-none-match", ""),
17    ("last-modified", ""),
18    ("link", ""),
19    ("location", ""),
20    ("referer", ""),
21    ("set-cookie", ""),
22    (":method", "CONNECT"),
23    (":method", "DELETE"),
24    (":method", "GET"),
25    (":method", "HEAD"),
26    (":method", "OPTIONS"),
27    (":method", "POST"),
28    (":method", "PUT"),
29    (":scheme", "http"),
30    (":scheme", "https"),
31    (":status", "103"),
32    (":status", "200"),
33    (":status", "304"),
34    (":status", "404"),
35    (":status", "503"),
36    ("accept", "*/*"),
37    ("accept", "application/dns-message"),
38    ("accept-encoding", "gzip, deflate, br"),
39    ("accept-ranges", "bytes"),
40    ("access-control-allow-headers", "cache-control"),
41    ("access-control-allow-headers", "content-type"),
42    ("access-control-allow-origin", "*"),
43    ("cache-control", "max-age=0"),
44    ("cache-control", "max-age=2592000"),
45    ("cache-control", "max-age=604800"),
46    ("cache-control", "no-cache"),
47    ("cache-control", "no-store"),
48    ("cache-control", "public, max-age=31536000"),
49    ("content-encoding", "br"),
50    ("content-encoding", "gzip"),
51    ("content-type", "application/dns-message"),
52    ("content-type", "application/javascript"),
53    ("content-type", "application/json"),
54    ("content-type", "application/x-www-form-urlencoded"),
55    ("content-type", "image/gif"),
56    ("content-type", "image/jpeg"),
57    ("content-type", "image/png"),
58    ("content-type", "text/css"),
59    ("content-type", "text/html; charset=utf-8"),
60    ("content-type", "text/plain"),
61    ("content-type", "text/plain;charset=utf-8"),
62    ("range", "bytes=0-"),
63    ("strict-transport-security", "max-age=31536000"),
64    ("strict-transport-security", "max-age=31536000; includesubdomains"),
65    ("strict-transport-security", "max-age=31536000; includesubdomains; preload"),
66    ("vary", "accept-encoding"),
67    ("vary", "origin"),
68    ("x-content-type-options", "nosniff"),
69    ("x-xss-protection", "1; mode=block"),
70    (":status", "100"),
71    (":status", "204"),
72    (":status", "206"),
73    (":status", "302"),
74    (":status", "400"),
75    (":status", "403"),
76    (":status", "421"),
77    (":status", "425"),
78    (":status", "500"),
79    ("accept-language", ""),
80    ("access-control-allow-credentials", "FALSE"),
81    ("access-control-allow-credentials", "TRUE"),
82    ("access-control-allow-headers", "*"),
83    ("access-control-allow-methods", "get"),
84    ("access-control-allow-methods", "get, post, options"),
85    ("access-control-allow-methods", "options"),
86    ("access-control-expose-headers", "content-length"),
87    ("access-control-request-headers", "content-type"),
88    ("access-control-request-method", "get"),
89    ("access-control-request-method", "post"),
90    ("alt-svc", "clear"),
91    ("authorization", ""),
92    ("content-security-policy", "script-src 'none'; object-src 'none'; base-uri 'none'"),
93    ("early-data", "1"),
94    ("expect-ct", ""),
95    ("forwarded", ""),
96    ("if-range", ""),
97    ("origin", ""),
98    ("purpose", "prefetch"),
99    ("server", ""),
100    ("timing-allow-origin", "*"),
101    ("upgrade-insecure-requests", "1"),
102    ("user-agent", ""),
103    ("x-forwarded-for", ""),
104    ("x-frame-options", "deny"),
105    ("x-frame-options", "sameorigin"),
106];
107
108/// Finds `(exact match, name match)` indexes in the static table.
109pub(crate) fn static_find(name: &[u8], value: &[u8]) -> (Option<u8>, Option<u8>) {
110    let mut name_idx = None;
111    for (i, (n, v)) in STATIC_TABLE.iter().enumerate() {
112        if n.len() == name.len() && n.as_bytes() == name {
113            if v.as_bytes() == value {
114                return (Some(i as u8), Some(i as u8));
115            }
116            name_idx.get_or_insert(i as u8);
117        }
118    }
119    (None, name_idx)
120}
121
122/// Parse failure: more bytes needed, or the input is invalid.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub(crate) enum Short {
125    Incomplete,
126    Invalid,
127}
128
129/// Largest integer QPACK accepts (62 bits, the QUIC varint range).
130pub const MAX_INT: u64 = (1 << 62) - 1;
131
132/// Decodes an `n`-bit prefixed integer (RFC 7541 5.1) at `*pos`, rejecting values over 62 bits.
133pub(crate) fn get_int(buf: &[u8], pos: &mut usize, n: u8) -> Result<u64, Short> {
134    let b = *buf.get(*pos).ok_or(Short::Incomplete)?;
135    *pos += 1;
136    let max = (1u64 << n) - 1;
137    let mut v = b as u64 & max;
138    if v < max {
139        return Ok(v);
140    }
141    let mut shift = 0u32;
142    loop {
143        let b = *buf.get(*pos).ok_or(Short::Incomplete)?;
144        *pos += 1;
145        if shift > 56 {
146            return Err(Short::Invalid);
147        }
148        v += ((b & 0x7f) as u64) << shift;
149        if v > MAX_INT {
150            return Err(Short::Invalid);
151        }
152        if b & 0x80 == 0 {
153            return Ok(v);
154        }
155        shift += 7;
156    }
157}
158
159/// Appends an `n`-bit prefixed integer whose first byte carries `flags` in the high bits.
160pub fn put_int(out: &mut Vec<u8>, flags: u8, n: u8, mut v: u64) {
161    let max = (1u64 << n) - 1;
162    if v < max {
163        out.push(flags | v as u8);
164        return;
165    }
166    out.push(flags | max as u8);
167    v -= max;
168    while v >= 0x80 {
169        out.push((v as u8 & 0x7f) | 0x80);
170        v >>= 7;
171    }
172    out.push(v as u8);
173}
174
175/// A string literal located in a buffer: Huffman flag and byte span.
176#[derive(Clone, Copy)]
177pub(crate) struct Span {
178    pub huff: bool,
179    pub start: usize,
180    pub end: usize,
181}
182
183/// Reads a string literal header whose length has an `n`-bit prefix and whose Huffman flag is
184/// the bit just above it, and checks that the bytes are all present.
185pub(crate) fn get_span(buf: &[u8], pos: &mut usize, n: u8) -> Result<Span, Short> {
186    let huff = *buf.get(*pos).ok_or(Short::Incomplete)? & (1 << n) != 0;
187    let len = get_int(buf, pos, n)?;
188    let start = *pos;
189    let avail = (buf.len() - start) as u64;
190    if len > avail {
191        return Err(Short::Incomplete);
192    }
193    *pos = start + len as usize;
194    Ok(Span { huff, start, end: *pos })
195}
196
197/// Decodes a located string literal into an owned vector.
198pub(crate) fn span_bytes(buf: &[u8], s: Span) -> Result<Vec<u8>, Short> {
199    let raw = &buf[s.start..s.end];
200    if s.huff {
201        let mut out = Vec::new();
202        huffman::decode(raw, &mut out).map_err(|_| Short::Invalid)?;
203        Ok(out)
204    } else {
205        Ok(raw.to_vec())
206    }
207}
208
209/// Appends a string literal with an `n`-bit length prefix, the Huffman flag just above it and
210/// `flags` above that; Huffman is used when it is shorter.
211pub(crate) fn put_str(out: &mut Vec<u8>, flags: u8, n: u8, s: &[u8]) {
212    let hlen = huffman::encoded_len(s);
213    if hlen < s.len() {
214        put_int(out, flags | (1 << n), n, hlen as u64);
215        huffman::encode(s, out);
216    } else {
217        put_int(out, flags, n, s.len() as u64);
218        out.extend_from_slice(s);
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn static_table_lookup() {
228        assert_eq!(STATIC_TABLE.len(), 99);
229        assert_eq!(static_find(b":path", b"/"), (Some(1), Some(1)));
230        assert_eq!(static_find(b":path", b"/x"), (None, Some(1)));
231        assert_eq!(static_find(b":status", b"500"), (Some(71), Some(71)));
232        assert_eq!(static_find(b"x-frame-options", b"sameorigin"), (Some(98), Some(98)));
233        assert_eq!(static_find(b"x-unknown", b""), (None, None));
234    }
235
236    #[test]
237    fn integers() {
238        // RFC 7541 C.1
239        let mut out = Vec::new();
240        put_int(&mut out, 0, 5, 10);
241        assert_eq!(out, [10]);
242        out.clear();
243        put_int(&mut out, 0, 5, 1337);
244        assert_eq!(out, [31, 154, 10]);
245        let mut p = 0;
246        assert_eq!(get_int(&out, &mut p, 5), Ok(1337));
247        out.clear();
248        put_int(&mut out, 0, 8, 42);
249        assert_eq!(out, [42]);
250        for &v in &[0u64, 1, 30, 31, 127, 128, 255, 16383, 1 << 40, MAX_INT] {
251            for n in 1..=8u8 {
252                let mut o = vec![];
253                put_int(&mut o, 0, n, v);
254                let mut p = 0;
255                assert_eq!(get_int(&o, &mut p, n), Ok(v));
256                assert_eq!(p, o.len());
257                let mut p = 0;
258                if o.len() > 1 {
259                    assert_eq!(get_int(&o[..o.len() - 1], &mut p, n), Err(Short::Incomplete));
260                }
261            }
262        }
263        // 2^62 overflows
264        let mut o = vec![];
265        put_int(&mut o, 0, 6, MAX_INT + 1);
266        let mut p = 0;
267        assert_eq!(get_int(&o, &mut p, 6), Err(Short::Invalid));
268        // endless continuation bytes
269        let o = [0x3f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01];
270        let mut p = 0;
271        assert_eq!(get_int(&o, &mut p, 6), Err(Short::Invalid));
272    }
273}