1use super::error::Http1ParseError;
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub struct Http1FieldRef<'a> {
8 name: &'a [u8],
9 value: &'a [u8],
10}
11
12impl<'a> Http1FieldRef<'a> {
13 pub const fn new(name: &'a [u8], value: &'a [u8]) -> Self {
15 Self { name, value }
16 }
17
18 pub const fn name(self) -> &'a [u8] {
20 self.name
21 }
22
23 pub const fn value(self) -> &'a [u8] {
25 self.value
26 }
27}
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub struct Http1Field<'a> {
32 line: &'a [u8],
33 colon: usize,
34}
35
36impl<'a> Http1Field<'a> {
37 pub const fn raw_line(self) -> &'a [u8] {
39 self.line
40 }
41
42 pub fn name(self) -> &'a [u8] {
44 &self.line[..self.colon]
45 }
46
47 pub fn raw_value(self) -> &'a [u8] {
49 &self.line[self.colon + 1..]
50 }
51
52 pub fn value(self) -> &'a [u8] {
54 trim_ows(self.raw_value())
55 }
56
57 pub fn name_eq(self, name: &[u8]) -> bool {
59 eq_ascii_case(self.name(), name)
60 }
61}
62
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
65pub struct Http1Fields<'a> {
66 bytes: &'a [u8],
67}
68
69impl<'a> Http1Fields<'a> {
70 pub(crate) fn parse(bytes: &'a [u8], trailer: bool) -> Result<Self, Http1ParseError> {
71 let error = if trailer {
72 Http1ParseError::InvalidTrailer
73 } else {
74 Http1ParseError::InvalidField
75 };
76 let mut offset = 0;
77 while offset < bytes.len() {
78 let end = match find_line_end(bytes, offset) {
79 Ok(end) => end,
80 Err(LineEndError::Incomplete) => {
81 return Err(Http1ParseError::Incomplete {
82 required: bytes.len().saturating_add(1),
83 available: bytes.len(),
84 });
85 }
86 Err(LineEndError::Malformed) => return Err(error),
87 };
88 if end == offset {
89 return Err(error);
90 }
91 let line = &bytes[offset..end];
92 validate_field_line(line).map_err(|()| error)?;
93 if trailer && forbidden_trailer_line(line) {
94 return Err(Http1ParseError::InvalidTrailer);
95 }
96 offset = end + 2;
97 }
98 Ok(Self { bytes })
99 }
100
101 pub(crate) const fn from_validated(bytes: &'a [u8]) -> Self {
102 Self { bytes }
103 }
104
105 pub const fn as_bytes(self) -> &'a [u8] {
107 self.bytes
108 }
109
110 pub const fn iter(self) -> Http1FieldIter<'a> {
112 Http1FieldIter {
113 bytes: self.bytes,
114 offset: 0,
115 }
116 }
117}
118
119impl<'a> IntoIterator for Http1Fields<'a> {
120 type Item = Http1Field<'a>;
121 type IntoIter = Http1FieldIter<'a>;
122
123 fn into_iter(self) -> Self::IntoIter {
124 self.iter()
125 }
126}
127
128#[derive(Clone, Debug)]
130pub struct Http1FieldIter<'a> {
131 bytes: &'a [u8],
132 offset: usize,
133}
134
135impl<'a> Iterator for Http1FieldIter<'a> {
136 type Item = Http1Field<'a>;
137
138 fn next(&mut self) -> Option<Self::Item> {
139 if self.offset == self.bytes.len() {
140 return None;
141 }
142 let end = find_line_end(self.bytes, self.offset).ok()?;
143 let line = &self.bytes[self.offset..end];
144 self.offset = end + 2;
145 let colon = line
146 .iter()
147 .position(|byte| *byte == b':')
148 .expect("validated field contains a colon");
149 Some(Http1Field { line, colon })
150 }
151}
152
153#[derive(Clone, Copy, Debug, Eq, PartialEq)]
154pub(crate) enum LineEndError {
155 Incomplete,
156 Malformed,
157}
158
159pub(crate) fn find_line_end(bytes: &[u8], start: usize) -> Result<usize, LineEndError> {
160 let mut offset = start;
161 while offset < bytes.len() {
162 match bytes[offset] {
163 b'\r' => {
164 if offset + 1 == bytes.len() {
165 return Err(LineEndError::Incomplete);
166 }
167 if bytes[offset + 1] != b'\n' {
168 return Err(LineEndError::Malformed);
169 }
170 return Ok(offset);
171 }
172 b'\n' => return Err(LineEndError::Malformed),
173 _ => offset += 1,
174 }
175 }
176 Err(LineEndError::Incomplete)
177}
178
179pub(crate) fn trim_ows(mut value: &[u8]) -> &[u8] {
180 while matches!(value.first(), Some(b' ' | b'\t')) {
181 value = &value[1..];
182 }
183 while matches!(value.last(), Some(b' ' | b'\t')) {
184 value = &value[..value.len() - 1];
185 }
186 value
187}
188
189pub(crate) fn eq_ascii_case(left: &[u8], right: &[u8]) -> bool {
190 left.len() == right.len()
191 && left
192 .iter()
193 .zip(right)
194 .all(|(left, right)| left.eq_ignore_ascii_case(right))
195}
196
197pub(crate) const fn is_token(byte: u8) -> bool {
198 matches!(
199 byte,
200 b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*' | b'+' | b'-' | b'.' | b'^'
201 | b'_' | b'`' | b'|' | b'~' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z'
202 )
203}
204
205pub(crate) fn validate_field_line(line: &[u8]) -> Result<(), ()> {
206 let colon = line.iter().position(|byte| *byte == b':').ok_or(())?;
207 if colon == 0 || !line[..colon].iter().copied().all(is_token) {
208 return Err(());
209 }
210 if !valid_field_value(&line[colon + 1..]) {
211 return Err(());
212 }
213 Ok(())
214}
215
216pub(crate) fn validate_field_ref(field: Http1FieldRef<'_>) -> bool {
217 !field.name.is_empty()
218 && field.name.iter().copied().all(is_token)
219 && valid_field_value(field.value)
220}
221
222fn valid_field_value(value: &[u8]) -> bool {
223 !value
224 .iter()
225 .any(|byte| matches!(*byte, 0..=8 | 10..=31 | 127))
226}
227
228fn forbidden_trailer_line(line: &[u8]) -> bool {
229 let colon = line
230 .iter()
231 .position(|byte| *byte == b':')
232 .expect("validated field contains a colon");
233 let name = &line[..colon];
234 [
235 b"content-length" as &[u8],
236 b"transfer-encoding",
237 b"host",
238 b"trailer",
239 b"te",
240 ]
241 .iter()
242 .any(|forbidden| eq_ascii_case(name, forbidden))
243}