Skip to main content

ntex_httparse/
headers.rs

1use crate::{Error, Result, SlicePos, State, Status, iter::Bytes, simd, utils};
2
3/// A parsed header.
4#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
5pub struct Header {
6    /// Header name, an ASCII token.
7    pub name: SlicePos,
8    /// Header value, without surrounding whitespace, `0..0` if empty.
9    ///
10    /// May contain HTAB, SP, visible ASCII and bytes `0x80..=0xFF`, so it is
11    /// not guaranteed to be UTF-8.
12    pub value: SlicePos,
13}
14
15/// Header parse result.
16///
17/// Both variants hold the position in `src` right after the parsed input,
18/// i.e. the number of bytes consumed when parsing started at position 0.
19#[derive(Copy, Clone, Debug, PartialEq, Eq)]
20pub enum HeaderParsed {
21    /// A header was parsed, its positions are stored in `Header`.
22    Header(usize),
23    /// The empty line ending the header section was parsed.
24    Eof(usize),
25}
26
27impl Header {
28    /// Parse a single header from a buffer of bytes.
29    ///
30    /// If complete and successful, name and value positions are stored in
31    /// `self` and the returned `HeaderParsed` holds the position in the
32    /// buffer where parsing stopped.
33    ///
34    /// Obsolete line folding is not supported: a line starting with SP or
35    /// HTAB is rejected with `Error::HeaderName`.
36    ///
37    /// # Example
38    ///
39    /// ```
40    /// use ntex_httparse::{Status, Header, HeaderParsed};
41    ///
42    /// let buf = b"Host: foo.bar \nAccept: */*\n\nblah blah";
43    /// let mut header = Header::default();
44    /// assert_eq!(header.parse(buf), Ok(Status::Complete(HeaderParsed::Header(15))));
45    /// assert_eq!(&buf[header.name.start..header.name.end], b"Host");
46    /// assert_eq!(&buf[header.value.start..header.value.end], b"foo.bar");
47    /// ```
48    pub fn parse(&mut self, src: &[u8]) -> Result<HeaderParsed> {
49        let mut st = State::default();
50        let mut bytes = Bytes::new(src, &mut st);
51        parse_header_iter_uninit(&mut bytes, self)
52    }
53
54    /// Parse a header, resuming from `st` saved by a previous `Partial` result.
55    ///
56    /// `st` must come from a previous call with the same buffer (which may have
57    /// grown since), or be `State::default()`. An invalid state returns
58    /// `Error::HeaderName`.
59    pub fn parse_with_state(&mut self, src: &[u8], st: &mut State) -> Result<HeaderParsed> {
60        if st.state > 3 || st.start > st.cursor || st.cursor > src.len() {
61            return Err(Error::HeaderName);
62        }
63        parse_header_iter_uninit(&mut Bytes::new(src, st), self)
64    }
65}
66
67fn parse_header_iter_uninit(
68    bytes: &mut Bytes<'_, '_>,
69    header: &mut Header,
70) -> Result<HeaderParsed> {
71    // header eof
72    if bytes.st.state == 0 {
73        // a newline here means the head is over!
74        let b = next!(bytes);
75        if b == b'\r' {
76            expect_lf!(bytes => Err(Error::NewLine));
77            bytes.commit();
78            return Ok(Status::Complete(HeaderParsed::Eof(bytes.cursor())));
79        } else if b == b'\n' {
80            bytes.commit();
81            return Ok(Status::Complete(HeaderParsed::Eof(bytes.cursor())));
82        } else if !utils::is_header_name_token(b) {
83            return Err(Error::HeaderName);
84        }
85        bytes.st.state = 1;
86        header.name.start = bytes.cursor() - 1;
87    }
88
89    // parse header name until colon
90    if bytes.st.state == 1 {
91        simd::match_header_name_vectored(bytes);
92        if next!(bytes) == b':' {
93            bytes.st.state = 2;
94            header.name.end = bytes.cursor() - 1;
95        } else {
96            return Err(Error::HeaderName);
97        }
98    }
99
100    let mut b;
101
102    // header value start position
103    if bytes.st.state == 2 {
104        // eat white space between colon and value
105        'whitespace_after_colon: loop {
106            b = next!(bytes);
107            if b == b' ' || b == b'\t' {
108                continue 'whitespace_after_colon;
109            }
110            if utils::is_header_value_token(b) {
111                bytes.st.state = 3;
112                header.value.start = bytes.cursor() - 1;
113                break 'whitespace_after_colon;
114            }
115
116            if b == b'\r' {
117                expect_lf!(bytes => Err(Error::HeaderValue));
118            } else if b != b'\n' {
119                return Err(Error::HeaderValue);
120            }
121
122            // This produces an empty slice that points to the beginning
123            // of the whitespace.
124            header.value.reset();
125            bytes.st.state = 0;
126            bytes.commit();
127            return Ok(Status::Complete(HeaderParsed::Header(bytes.cursor())));
128        }
129    }
130
131    // header value
132    if bytes.st.state == 3 {
133        // parse value till EOL
134        {
135            simd::match_header_value_vectored(bytes);
136
137            // check ctl
138            let b = next!(bytes);
139            if b == b'\r' {
140                expect_lf!(bytes => Err(Error::HeaderValue));
141            } else if b != b'\n' {
142                return Err(Error::HeaderValue);
143            }
144
145            // trim trailing whitespace in the header
146            let mut n = 1; // previous next() moves cursor to next item
147            while let Some(b) = bytes.peek_behind(n) {
148                if matches!(b, b' ' | b'\t' | b'\r' | b'\n') {
149                    n += 1;
150                } else {
151                    break;
152                }
153            }
154
155            header.value.end = bytes.cursor() - n + 1;
156        }
157    }
158    bytes.st.state = 0;
159    bytes.commit();
160
161    Ok(Status::Complete(HeaderParsed::Header(bytes.cursor())))
162}