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}