blob: 18533ac93d3c20d798afa59cf352f653966d2173 [file] [log] [blame]
// Copyright (C) 2019, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::octets;
use super::Error;
use super::Result;
use crate::h3::Header;
use super::INDEXED;
use super::INDEXED_WITH_POST_BASE;
use super::LITERAL;
use super::LITERAL_WITH_NAME_REF;
#[derive(Clone, Copy, Debug, PartialEq)]
enum Representation {
Indexed,
IndexedWithPostBase,
Literal,
LiteralWithNameRef,
LiteralWithPostBase,
}
impl Representation {
pub fn from_byte(b: u8) -> Representation {
if b & INDEXED == INDEXED {
return Representation::Indexed;
}
if b & LITERAL_WITH_NAME_REF == LITERAL_WITH_NAME_REF {
return Representation::LiteralWithNameRef;
}
if b & LITERAL == LITERAL {
return Representation::Literal;
}
if b & INDEXED_WITH_POST_BASE == INDEXED_WITH_POST_BASE {
return Representation::IndexedWithPostBase;
}
Representation::LiteralWithPostBase
}
}
/// A QPACK decoder.
pub struct Decoder {}
impl Default for Decoder {
fn default() -> Decoder {
Decoder {}
}
}
impl Decoder {
/// Creates a new QPACK decoder.
pub fn new() -> Decoder {
Decoder::default()
}
/// Processes control instructions from the encoder.
pub fn control(&mut self, _buf: &mut [u8]) -> Result<()> {
// TODO: process control instructions
Ok(())
}
/// Decodes a QPACK header block into a list of headers.
pub fn decode(&mut self, buf: &[u8], max_size: u64) -> Result<Vec<Header>> {
let mut b = octets::Octets::with_slice(buf);
let mut out = Vec::new();
let mut left = max_size;
let req_insert_count = decode_int(&mut b, 8)?;
let base = decode_int(&mut b, 7)?;
trace!("Header count={} base={}", req_insert_count, base);
while b.cap() > 0 {
let first = b.peek_u8()?;
match Representation::from_byte(first) {
Representation::Indexed => {
const STATIC: u8 = 0x40;
let s = first & STATIC == STATIC;
let index = decode_int(&mut b, 6)?;
trace!("Indexed index={} static={}", index, s);
if !s {
// TODO: implement dynamic table
return Err(Error::InvalidHeaderValue);
}
let (name, value) = lookup_static(index)?;
left = left
.checked_sub((name.len() + value.len()) as u64)
.ok_or(Error::HeaderListTooLarge)?;
out.push(Header::new(&name, &value));
},
Representation::IndexedWithPostBase => {
let index = decode_int(&mut b, 4)?;
trace!("Indexed With Post Base index={}", index);
// TODO: implement dynamic table
return Err(Error::InvalidHeaderValue);
},
Representation::Literal => {
let name_huff = b.as_ref()[0] & 0x08 == 0x08;
let name_len = decode_int(&mut b, 3)? as usize;
let mut name = b.get_bytes(name_len)?;
let name = if name_huff {
super::huffman::decode(&mut name)?
} else {
name.to_vec()
};
let name = String::from_utf8(name)
.map_err(|_| Error::InvalidHeaderValue)?;
let value = decode_str(&mut b)?;
trace!(
"Literal Without Name Reference name={:?} value={:?}",
name,
value
);
left = left
.checked_sub((name.len() + value.len()) as u64)
.ok_or(Error::HeaderListTooLarge)?;
out.push(Header::new(&name, &value));
},
Representation::LiteralWithNameRef => {
const STATIC: u8 = 0x10;
let s = first & STATIC == STATIC;
let name_idx = decode_int(&mut b, 4)?;
let value = decode_str(&mut b)?;
trace!(
"Literal name_idx={} static={} value={:?}",
name_idx,
s,
value
);
if !s {
// TODO: implement dynamic table
return Err(Error::InvalidHeaderValue);
}
let (name, _) = lookup_static(name_idx)?;
left = left
.checked_sub((name.len() + value.len()) as u64)
.ok_or(Error::HeaderListTooLarge)?;
out.push(Header::new(name, &value));
},
Representation::LiteralWithPostBase => {
trace!("Literal With Post Base");
// TODO: implement dynamic table
return Err(Error::InvalidHeaderValue);
},
}
}
Ok(out)
}
}
fn lookup_static(idx: u64) -> Result<(&'static str, &'static str)> {
let hdr = match idx {
0 => (":authority", ""),
1 => (":path", "/"),
2 => ("age", "0"),
3 => ("content-disposition", ""),
4 => ("content-length", "0"),
5 => ("cookie", ""),
6 => ("date", ""),
7 => ("etag", ""),
8 => ("if-modified-since", ""),
9 => ("if-none-match", ""),
10 => ("last-modified", ""),
11 => ("link", ""),
12 => ("location", ""),
13 => ("referer", ""),
14 => ("set-cookie", ""),
15 => (":method", "CONNECT"),
16 => (":method", "DELETE"),
17 => (":method", "GET"),
18 => (":method", "HEAD"),
19 => (":method", "OPTIONS"),
20 => (":method", "POST"),
21 => (":method", "PUT"),
22 => (":scheme", "http"),
23 => (":scheme", "https"),
24 => (":status", "103"),
25 => (":status", "200"),
26 => (":status", "304"),
27 => (":status", "404"),
28 => (":status", "503"),
29 => ("accept", "*/*"),
30 => ("accept", "application/dns-message"),
31 => ("accept-encoding", "gzip, deflate, br"),
32 => ("accept-ranges", "bytes"),
33 => ("access-control-allow-headers", "cache-control"),
34 => ("access-control-allow-headers", "content-type"),
35 => ("access-control-allow-origin", "*"),
36 => ("cache-control", "max-age=0"),
37 => ("cache-control", "max-age=2592000"),
38 => ("cache-control", "max-age=604800"),
39 => ("cache-control", "no-cache"),
40 => ("cache-control", "no-store"),
41 => ("cache-control", "public, max-age=31536000"),
42 => ("content-encoding", "br"),
43 => ("content-encoding", "gzip"),
44 => ("content-type", "application/dns-message"),
45 => ("content-type", "application/javascript"),
46 => ("content-type", "application/json"),
47 => ("content-type", "application/x-www-form-urlencoded"),
48 => ("content-type", "image/gif"),
49 => ("content-type", "image/jpeg"),
50 => ("content-type", "image/png"),
51 => ("content-type", "text/css"),
52 => ("content-type", "text/html; charset=utf-8"),
53 => ("content-type", "text/plain"),
54 => ("content-type", "text/plain;charset=utf-8"),
55 => ("range", "bytes=0-"),
56 => ("strict-transport-security", "max-age=31536000"),
57 => (
"strict-transport-security",
"max-age=31536000; includesubdomains",
),
58 => (
"strict-transport-security",
"max-age=31536000; includesubdomains; preload",
),
59 => ("vary", "accept-encoding"),
60 => ("vary", "origin"),
61 => ("x-content-type-options", "nosniff"),
62 => ("x-xss-protection", "1; mode=block"),
63 => (":status", "100"),
64 => (":status", "204"),
65 => (":status", "206"),
66 => (":status", "302"),
67 => (":status", "400"),
68 => (":status", "403"),
69 => (":status", "421"),
70 => (":status", "425"),
71 => (":status", "500"),
72 => ("accept-language", ""),
73 => ("access-control-allow-credentials", "FALSE"),
74 => ("access-control-allow-credentials", "TRUE"),
75 => ("access-control-allow-headers", "*"),
76 => ("access-control-allow-methods", "get"),
77 => ("access-control-allow-methods", "get, post, options"),
78 => ("access-control-allow-methods", "options"),
79 => ("access-control-expose-headers", "content-length"),
80 => ("access-control-request-headers", "content-type"),
81 => ("access-control-request-method", "get"),
82 => ("access-control-request-method", "post"),
83 => ("alt-svc", "clear"),
84 => ("authorization", ""),
85 => (
"content-security-policy",
"script-src 'none'; object-src 'none'; base-uri 'none'",
),
86 => ("early-data", "1"),
87 => ("expect-ct", ""),
88 => ("forwarded", ""),
89 => ("if-range", ""),
90 => ("origin", ""),
91 => ("purpose", "prefetch"),
92 => ("server", ""),
93 => ("timing-allow-origin", "*"),
94 => ("upgrade-insecure-requests", "1"),
95 => ("user-agent", ""),
96 => ("x-forwarded-for", ""),
97 => ("x-frame-options", "deny"),
98 => ("x-frame-options", "sameorigin"),
_ => return Err(Error::InvalidStaticTableIndex),
};
Ok(hdr)
}
fn decode_int(b: &mut octets::Octets, prefix: usize) -> Result<u64> {
let mask = 2u64.pow(prefix as u32) - 1;
let mut val = u64::from(b.get_u8()?);
val &= mask;
if val < mask {
return Ok(val);
}
let mut shift = 0;
while b.cap() > 0 {
let byte = b.get_u8()?;
let inc = u64::from(byte & 0x7f)
.checked_shl(shift)
.ok_or(Error::BufferTooShort)?;
val = val.checked_add(inc).ok_or(Error::BufferTooShort)?;
shift += 7;
if byte & 0x80 == 0 {
return Ok(val);
}
}
Err(Error::BufferTooShort)
}
fn decode_str<'a>(b: &'a mut octets::Octets) -> Result<String> {
let first = b.peek_u8()?;
let huff = first & 0x80 == 0x80;
let len = decode_int(b, 7)? as usize;
let mut val = b.get_bytes(len)?;
let val = if huff {
super::huffman::decode(&mut val)?
} else {
val.to_vec()
};
let val = String::from_utf8(val).map_err(|_| Error::InvalidHeaderValue)?;
Ok(val)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::octets;
#[test]
fn decode_int1() {
let mut encoded = [0b01010, 0x02];
let mut b = octets::Octets::with_slice(&mut encoded);
assert_eq!(decode_int(&mut b, 5), Ok(10));
}
#[test]
fn decode_int2() {
let mut encoded = [0b11111, 0b10011010, 0b00001010];
let mut b = octets::Octets::with_slice(&mut encoded);
assert_eq!(decode_int(&mut b, 5), Ok(1337));
}
#[test]
fn decode_int3() {
let mut encoded = [0b101010];
let mut b = octets::Octets::with_slice(&mut encoded);
assert_eq!(decode_int(&mut b, 8), Ok(42));
}
}