Skip to main content

ntex_httparse/
headers.rs

1use crate::{Error, Result, SlicePos, State, Status, iter::Bytes, simd, utils};
2
3/// Represents a parsed header.
4#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
5pub struct Header {
6    /// The name portion of a header.
7    ///
8    /// A header name must be valid ASCII-US, so it's safe to store as a `&str`.
9    pub name: SlicePos,
10    /// The value portion of a header.
11    ///
12    /// While headers **should** be ASCII-US, the specification allows for
13    /// values that may not be, and so the value is stored as bytes.
14    pub value: SlicePos,
15}
16
17/// Header parse result result.
18#[derive(Copy, Clone, Debug, PartialEq, Eq)]
19pub enum HeaderParsed {
20    Header(usize),
21    Eof(usize),
22}
23
24impl Header {
25    /// Parse a buffer of bytes as header.
26    ///
27    /// The return value, if complete and successful, includes the index of the
28    /// buffer that parsing stopped at, and a sliced reference to the parsed
29    /// headers. The length of the slice will be equal to the number of properly
30    /// parsed headers.
31    ///
32    /// # Example
33    ///
34    /// ```
35    /// use ntex_httparse::{Status, Header, HeaderParsed};
36    ///
37    /// let buf = b"Host: foo.bar\nAccept: */*\n\nblah blah";
38    /// let mut header = Header::default();
39    /// assert_eq!(header.parse(buf), Ok(Status::Complete(HeaderParsed::Header(14))));
40    /// ```
41    pub fn parse(&mut self, src: &[u8]) -> Result<HeaderParsed> {
42        let mut st = State::default();
43        let mut bytes = Bytes::new(src, &mut st);
44        parse_header_iter_uninit(&mut bytes, self)
45    }
46
47    pub fn parse_with_state(&mut self, src: &[u8], st: &mut State) -> Result<HeaderParsed> {
48        parse_header_iter_uninit(&mut Bytes::new(src, st), self)
49    }
50}
51
52fn parse_header_iter_uninit(
53    bytes: &mut Bytes<'_, '_>,
54    header: &mut Header,
55) -> Result<HeaderParsed> {
56    // header eof
57    if bytes.st.state == 0 {
58        // a newline here means the head is over!
59        let b = next!(bytes);
60        if b == b'\r' {
61            expect!(bytes.next() == b'\n' => Err(Error::NewLine));
62            return Ok(Status::Complete(HeaderParsed::Eof(
63                bytes.cursor() - bytes.start(),
64            )));
65        } else if b == b'\n' {
66            return Ok(Status::Complete(HeaderParsed::Eof(
67                bytes.cursor() - bytes.start(),
68            )));
69        } else if !utils::is_header_name_token(b) {
70            return Err(Error::HeaderName);
71        }
72        bytes.st.state = 1;
73        header.name.start = bytes.cursor() - 1;
74    }
75
76    // parse header name until colon
77    if bytes.st.state == 1 {
78        simd::match_header_name_vectored(bytes);
79        if next!(bytes) == b':' {
80            // SAFETY: previously bumped by 1 with next! -> always safe.
81            bytes.st.state = 2;
82            header.name.end = bytes.cursor() - 1;
83        } else {
84            return Err(Error::HeaderName);
85        }
86    }
87
88    let mut b;
89
90    // header value start position
91    if bytes.st.state == 2 {
92        // eat white space between colon and value
93        'whitespace_after_colon: loop {
94            b = next!(bytes);
95            if b == b' ' || b == b'\t' {
96                continue 'whitespace_after_colon;
97            }
98            if utils::is_header_value_token(b) {
99                bytes.st.state = 3;
100                header.value.start = bytes.cursor() - 1;
101                break 'whitespace_after_colon;
102            }
103
104            if b == b'\r' {
105                expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
106            } else if b != b'\n' {
107                return Err(Error::HeaderValue);
108            }
109
110            // This produces an empty slice that points to the beginning
111            // of the whitespace.
112            header.value.reset();
113            bytes.st.state = 0;
114            bytes.commit();
115            return Ok(Status::Complete(HeaderParsed::Header(bytes.cursor())));
116        }
117    }
118
119    // header value
120    if bytes.st.state == 3 {
121        // parse value till EOL
122        {
123            simd::match_header_value_vectored(bytes);
124
125            // check ctl
126            let b = next!(bytes);
127            if b == b'\r' {
128                expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
129            } else if b != b'\n' {
130                return Err(Error::HeaderValue);
131            }
132
133            // trim trailing whitespace in the header
134            let mut n = 1; // previous next() moves cursor to next item
135            while let Some(b) = bytes.peek_behind(n) {
136                if matches!(b, b' ' | b'\t' | b'\r' | b'\n') {
137                    n += 1;
138                } else {
139                    break;
140                }
141            }
142
143            header.value.end = bytes.cursor() - n + 1;
144        }
145    }
146    bytes.st.state = 0;
147    bytes.commit();
148
149    Ok(Status::Complete(HeaderParsed::Header(bytes.cursor())))
150}