Skip to main content

simoom_h2/
headers.rs

1//! Received header blocks: one reusable byte buffer plus field offsets, validated as fields
2//! arrive (RFC 9113 §8.2, §8.3).
3
4use std::fmt;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub(crate) enum Kind {
8    Request,
9    Response,
10    Trailers,
11}
12
13const METHOD: usize = 0;
14const SCHEME: usize = 1;
15const AUTHORITY: usize = 2;
16const PATH: usize = 3;
17const STATUS: usize = 4;
18const PROTOCOL: usize = 5;
19
20/// tchar (RFC 9110 §5.6.2) without upper-case letters.
21const fn name_table() -> [bool; 256] {
22    let mut t = [false; 256];
23    let extra = b"!#$%&'*+-.^_`|~";
24    let mut i = 0;
25    while i < extra.len() {
26        t[extra[i] as usize] = true;
27        i += 1;
28    }
29    let mut c = b'0';
30    while c <= b'9' {
31        t[c as usize] = true;
32        c += 1;
33    }
34    let mut c = b'a';
35    while c <= b'z' {
36        t[c as usize] = true;
37        c += 1;
38    }
39    t
40}
41static NAME_CHARS: [bool; 256] = name_table();
42
43/// A field value is valid when it has no NUL, CR or LF and no leading or trailing whitespace
44/// (RFC 9113 §8.2.1).
45#[inline]
46pub(crate) fn valid_value(v: &[u8]) -> bool {
47    if let (Some(&f), Some(&l)) = (v.first(), v.last()) {
48        if f == b' ' || f == b'\t' || l == b' ' || l == b'\t' {
49            return false;
50        }
51    }
52    !v.iter().any(|&b| b == 0 || b == b'\r' || b == b'\n')
53}
54
55/// A regular field name is valid when it is a lower-case token.
56#[inline]
57pub(crate) fn valid_name(n: &[u8]) -> bool {
58    !n.is_empty() && n.iter().all(|&b| NAME_CHARS[b as usize])
59}
60
61/// Connection-specific fields are not allowed in HTTP/2 (RFC 9113 §8.2.2).
62#[inline]
63pub(crate) fn connection_specific(name: &[u8]) -> bool {
64    matches!(name, b"connection" | b"keep-alive" | b"proxy-connection" | b"transfer-encoding" | b"upgrade")
65}
66
67fn parse_u64(v: &[u8]) -> Option<u64> {
68    if v.is_empty() || v.len() > 19 {
69        return None;
70    }
71    let mut n = 0u64;
72    for &b in v {
73        if !b.is_ascii_digit() {
74            return None;
75        }
76        n = n * 10 + (b - b'0') as u64;
77    }
78    Some(n)
79}
80
81/// A received header block (request or response headers, or trailers).
82///
83/// Pseudo-header fields are reached through [`Headers::method`], [`Headers::path`] and so on;
84/// [`Headers::iter`] yields the regular fields in order. Names are lower case.
85#[derive(Clone)]
86pub struct Headers {
87    buf: Vec<u8>,
88    /// (start in buf, name length, value length)
89    fields: Vec<(u32, u32, u32)>,
90    /// index + 1 into `fields` for each pseudo-header, 0 when absent
91    pseudo: [u16; 6],
92    npseudo: usize,
93    host: u16,
94    content_length: Option<u64>,
95    malformed: Option<&'static str>,
96    kind: Kind,
97    /// `:protocol` is accepted (extended CONNECT, RFC 8441).
98    pub(crate) allow_protocol: bool,
99}
100
101impl Default for Headers {
102    fn default() -> Self {
103        Headers {
104            buf: Vec::new(),
105            fields: Vec::new(),
106            pseudo: [0; 6],
107            npseudo: 0,
108            host: 0,
109            content_length: None,
110            malformed: None,
111            kind: Kind::Request,
112            allow_protocol: false,
113        }
114    }
115}
116
117impl Headers {
118    pub(crate) fn reset(&mut self, kind: Kind) {
119        self.buf.clear();
120        self.fields.clear();
121        self.pseudo = [0; 6];
122        self.npseudo = 0;
123        self.host = 0;
124        self.content_length = None;
125        self.malformed = None;
126        self.kind = kind;
127    }
128
129    #[inline]
130    fn fail(&mut self, why: &'static str) {
131        if self.malformed.is_none() {
132            self.malformed = Some(why);
133        }
134    }
135
136    fn store(&mut self, name: &[u8], value: &[u8]) -> usize {
137        let start = self.buf.len() as u32;
138        self.buf.extend_from_slice(name);
139        self.buf.extend_from_slice(value);
140        self.fields.push((start, name.len() as u32, value.len() as u32));
141        self.fields.len()
142    }
143
144    /// Adds a decoded field, checking it.
145    pub(crate) fn push(&mut self, name: &[u8], value: &[u8]) {
146        if self.malformed.is_some() {
147            return;
148        }
149        if !valid_value(value) {
150            return self.fail("invalid field value");
151        }
152        if name.first() == Some(&b':') {
153            if self.kind == Kind::Trailers {
154                return self.fail("pseudo-header in trailers");
155            }
156            if self.fields.len() > self.npseudo {
157                return self.fail("pseudo-header after a regular field");
158            }
159            let slot = match (self.kind, name) {
160                (Kind::Request, b":method") => METHOD,
161                (Kind::Request, b":scheme") => SCHEME,
162                (Kind::Request, b":authority") => AUTHORITY,
163                (Kind::Request, b":path") => PATH,
164                (Kind::Request, b":protocol") if cfg!(feature = "connect-protocol") && self.allow_protocol => PROTOCOL,
165                (Kind::Response, b":status") => STATUS,
166                _ => return self.fail("unknown or misplaced pseudo-header"),
167            };
168            if self.pseudo[slot] != 0 {
169                return self.fail("duplicate pseudo-header");
170            }
171            self.pseudo[slot] = self.store(name, value) as u16;
172            self.npseudo += 1;
173            return;
174        }
175        if !valid_name(name) {
176            return self.fail("invalid field name");
177        }
178        match name {
179            _ if connection_specific(name) => return self.fail("connection-specific field"),
180            b"te" if value != b"trailers" => return self.fail("te other than trailers"),
181            b"content-length" => match parse_u64(value) {
182                Some(n) if self.content_length.is_none_or(|c| c == n) => self.content_length = Some(n),
183                _ => return self.fail("invalid content-length"),
184            },
185            _ => {}
186        }
187        let i = self.store(name, value);
188        if name == b"host" && self.host == 0 {
189            self.host = i as u16;
190        }
191    }
192
193    /// Checks the required pseudo-headers once the block is complete (RFC 9113 §8.3).
194    pub(crate) fn finish(&mut self) {
195        if self.malformed.is_some() {
196            return;
197        }
198        match self.kind {
199            Kind::Request => {
200                let Some(method) = self.method() else { return self.fail("missing :method") };
201                if cfg!(feature = "connect-protocol") && self.protocol().is_some() {
202                    // Extended CONNECT (RFC 8441 §4): CONNECT with :scheme, :path and
203                    // :authority.
204                    if method != b"CONNECT" {
205                        return self.fail(":protocol without CONNECT");
206                    }
207                    let (Some(scheme), Some(path), Some(_)) = (self.scheme(), self.path(), self.authority()) else {
208                        return self.fail("extended CONNECT without :scheme, :path or :authority");
209                    };
210                    if (scheme == b"http" || scheme == b"https") && path.first() != Some(&b'/') {
211                        return self.fail("invalid :path");
212                    }
213                } else if method == b"CONNECT" {
214                    if self.authority().is_none() || self.scheme().is_some() || self.path().is_some() {
215                        return self.fail("malformed CONNECT");
216                    }
217                } else {
218                    let (Some(scheme), Some(path)) = (self.scheme(), self.path()) else {
219                        return self.fail("missing :scheme or :path");
220                    };
221                    if path.is_empty() {
222                        return self.fail("empty :path");
223                    }
224                    let http = scheme == b"http" || scheme == b"https";
225                    if http && path[0] != b'/' && !(path == b"*" && method == b"OPTIONS") {
226                        return self.fail("invalid :path");
227                    }
228                }
229                if let (Some(a), Some(h)) = (self.authority(), self.get(b"host")) {
230                    if a != h {
231                        return self.fail(":authority and host differ");
232                    }
233                }
234            }
235            Kind::Response => match self.status() {
236                Some(s) if (100..1000).contains(&s) && s != 101 => {}
237                _ => self.fail("missing or invalid :status"),
238            },
239            Kind::Trailers => {}
240        }
241    }
242
243    pub(crate) fn malformed(&self) -> Option<&'static str> {
244        self.malformed
245    }
246
247    #[inline]
248    fn field(&self, i: usize) -> (&[u8], &[u8]) {
249        let (s, n, v) = self.fields[i];
250        let (s, n, v) = (s as usize, n as usize, v as usize);
251        (&self.buf[s..s + n], &self.buf[s + n..s + n + v])
252    }
253
254    #[inline]
255    fn pseudo(&self, slot: usize) -> Option<&[u8]> {
256        match self.pseudo[slot] {
257            0 => None,
258            i => Some(self.field(i as usize - 1).1),
259        }
260    }
261
262    /// `:method`.
263    pub fn method(&self) -> Option<&[u8]> {
264        self.pseudo(METHOD)
265    }
266    /// `:scheme`.
267    pub fn scheme(&self) -> Option<&[u8]> {
268        self.pseudo(SCHEME)
269    }
270    /// `:authority` (when absent, a `host` field may carry the authority: see [`Headers::get`]).
271    pub fn authority(&self) -> Option<&[u8]> {
272        self.pseudo(AUTHORITY)
273    }
274    /// `:path`.
275    pub fn path(&self) -> Option<&[u8]> {
276        self.pseudo(PATH)
277    }
278    /// `:protocol` of an extended CONNECT request (RFC 8441), for example `websocket`; only
279    /// accepted when [`crate::Config::enable_connect_protocol`] is set.
280    pub fn protocol(&self) -> Option<&[u8]> {
281        self.pseudo(PROTOCOL)
282    }
283    /// `:status` as a number.
284    pub fn status(&self) -> Option<u16> {
285        let s = self.pseudo(STATUS)?;
286        if s.len() != 3 {
287            return None;
288        }
289        parse_u64(s).map(|n| n as u16)
290    }
291    /// The content-length value, when present.
292    pub fn content_length(&self) -> Option<u64> {
293        self.content_length
294    }
295    /// Number of regular fields.
296    pub fn len(&self) -> usize {
297        self.fields.len() - self.npseudo
298    }
299    /// True when there are no regular fields.
300    pub fn is_empty(&self) -> bool {
301        self.len() == 0
302    }
303    /// Regular fields (name, value) in order.
304    pub fn iter(&self) -> impl Iterator<Item = (&[u8], &[u8])> + '_ {
305        (self.npseudo..self.fields.len()).map(move |i| self.field(i))
306    }
307    /// The first regular field named `name` (lower case).
308    pub fn get(&self, name: &[u8]) -> Option<&[u8]> {
309        self.iter().find(|(n, _)| *n == name).map(|(_, v)| v)
310    }
311}
312
313impl fmt::Debug for Headers {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        let mut l = f.debug_list();
316        for i in 0..self.fields.len() {
317            let (n, v) = self.field(i);
318            l.entry(&(String::from_utf8_lossy(n), String::from_utf8_lossy(v)));
319        }
320        l.finish()
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    fn req(fields: &[(&str, &str)]) -> Headers {
329        let mut h = Headers::default();
330        h.reset(Kind::Request);
331        for (n, v) in fields {
332            h.push(n.as_bytes(), v.as_bytes());
333        }
334        h.finish();
335        h
336    }
337
338    const BASE: [(&str, &str); 4] = [(":method", "GET"), (":scheme", "https"), (":path", "/x"), (":authority", "a")];
339
340    #[test]
341    fn valid_request() {
342        let h = req(&[BASE[0], BASE[1], BASE[2], BASE[3], ("accept", "*/*"), ("te", "trailers"), ("content-length", "0")]);
343        assert_eq!(h.malformed(), None);
344        assert_eq!(h.method(), Some(&b"GET"[..]));
345        assert_eq!(h.path(), Some(&b"/x"[..]));
346        assert_eq!(h.len(), 3);
347        assert_eq!(h.get(b"accept"), Some(&b"*/*"[..]));
348        assert_eq!(h.content_length(), Some(0));
349        assert!(format!("{h:?}").contains("accept"));
350        let h = req(&[(":method", "CONNECT"), (":authority", "a:443")]);
351        assert_eq!(h.malformed(), None);
352        let h = req(&[(":method", "OPTIONS"), (":scheme", "http"), (":path", "*")]);
353        assert_eq!(h.malformed(), None);
354    }
355
356    #[test]
357    fn malformed_requests() {
358        let cases: Vec<Vec<(&str, &str)>> = vec![
359            vec![BASE[1], BASE[2]],                                     // no method
360            vec![BASE[0], BASE[2]],                                     // no scheme
361            vec![BASE[0], BASE[1]],                                     // no path
362            vec![BASE[0], BASE[1], (":path", "")],                      // empty path
363            vec![BASE[0], BASE[1], (":path", "x")],                     // relative path
364            vec![BASE[0], BASE[0], BASE[1], BASE[2]],                   // duplicate
365            vec![BASE[0], BASE[1], BASE[2], (":status", "200")],        // response pseudo
366            vec![BASE[0], BASE[1], BASE[2], (":protocol", "ws")],       // extended CONNECT not enabled
367            vec![BASE[0], BASE[1], ("a", "b"), BASE[2]],                // pseudo after regular
368            vec![BASE[0], BASE[1], BASE[2], ("Accept", "x")],           // upper case
369            vec![BASE[0], BASE[1], BASE[2], ("", "x")],                 // empty name
370            vec![BASE[0], BASE[1], BASE[2], ("a b", "x")],              // space in name
371            vec![BASE[0], BASE[1], BASE[2], ("connection", "close")],
372            vec![BASE[0], BASE[1], BASE[2], ("keep-alive", "1")],
373            vec![BASE[0], BASE[1], BASE[2], ("transfer-encoding", "chunked")],
374            vec![BASE[0], BASE[1], BASE[2], ("upgrade", "h2c")],
375            vec![BASE[0], BASE[1], BASE[2], ("proxy-connection", "x")],
376            vec![BASE[0], BASE[1], BASE[2], ("te", "gzip")],
377            vec![BASE[0], BASE[1], BASE[2], ("content-length", "1x")],
378            vec![BASE[0], BASE[1], BASE[2], ("content-length", "1"), ("content-length", "2")],
379            vec![BASE[0], BASE[1], BASE[2], ("x", "a\r\nb")],
380            vec![BASE[0], BASE[1], BASE[2], ("x", " a")],
381            vec![BASE[0], BASE[1], BASE[2], ("x", "a\t")],
382            vec![BASE[0], BASE[1], BASE[2], ("x", "a\0")],
383            vec![BASE[0], BASE[1], BASE[2], BASE[3], ("host", "b")],    // host differs
384            vec![(":method", "CONNECT"), (":authority", "a"), (":path", "/")],
385        ];
386        for (i, c) in cases.iter().enumerate() {
387            assert!(req(c).malformed().is_some(), "case {i}: {c:?}");
388        }
389    }
390
391    #[cfg(feature = "connect-protocol")]
392    #[test]
393    fn extended_connect() {
394        let ext = |fields: &[(&str, &str)]| {
395            let mut h = Headers { allow_protocol: true, ..Headers::default() };
396            h.reset(Kind::Request);
397            for (n, v) in fields {
398                h.push(n.as_bytes(), v.as_bytes());
399            }
400            h.finish();
401            h
402        };
403        let ws = [(":method", "CONNECT"), (":protocol", "websocket"), (":scheme", "https"), (":path", "/chat"), (":authority", "a")];
404        let h = ext(&ws);
405        assert_eq!(h.malformed(), None);
406        assert_eq!(h.protocol(), Some(&b"websocket"[..]));
407        assert_eq!(h.path(), Some(&b"/chat"[..]));
408        // classic CONNECT still works with the setting on, and has no :protocol
409        let h = ext(&[(":method", "CONNECT"), (":authority", "a:443")]);
410        assert_eq!((h.malformed(), h.protocol()), (None, None));
411        let bad: Vec<Vec<(&str, &str)>> = vec![
412            vec![ws[0], ws[1], ws[2], ws[4]],                     // no :path
413            vec![ws[0], ws[1], ws[3], ws[4]],                     // no :scheme
414            vec![ws[0], ws[1], ws[2], ws[3]],                     // no :authority
415            vec![(":method", "GET"), ws[1], ws[2], ws[3], ws[4]], // not CONNECT
416            vec![ws[0], ws[1], ws[1], ws[2], ws[3], ws[4]],       // duplicate
417            vec![ws[0], ws[1], ws[2], (":path", "chat"), ws[4]],  // relative path
418        ];
419        for (i, c) in bad.iter().enumerate() {
420            assert!(ext(c).malformed().is_some(), "case {i}");
421        }
422        // without the setting, :protocol is refused (RFC 8441 §3)
423        assert!(req(&ws).malformed().is_some());
424    }
425
426    #[test]
427    fn responses_and_trailers() {
428        let mut h = Headers::default();
429        h.reset(Kind::Response);
430        h.push(b":status", b"204");
431        h.finish();
432        assert_eq!((h.malformed(), h.status()), (None, Some(204)));
433        for bad in [&b"101"[..], b"20", b"abc", b"2000"] {
434            h.reset(Kind::Response);
435            h.push(b":status", bad);
436            h.finish();
437            assert!(h.malformed().is_some());
438        }
439        h.reset(Kind::Response);
440        h.finish();
441        assert!(h.malformed().is_some());
442        h.reset(Kind::Trailers);
443        h.push(b"grpc-status", b"0");
444        h.finish();
445        assert_eq!(h.malformed(), None);
446        h.reset(Kind::Trailers);
447        h.push(b":status", b"200");
448        assert!(h.malformed().is_some());
449    }
450}