Skip to main content

net_wire/http1/
fields.rs

1//! HTTP/1 field views.
2
3use super::error::Http1ParseError;
4
5/// A caller-supplied HTTP/1 field.
6#[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    /// Creates a field reference.
14    pub const fn new(name: &'a [u8], value: &'a [u8]) -> Self {
15        Self { name, value }
16    }
17
18    /// Returns the field name without a colon.
19    pub const fn name(self) -> &'a [u8] {
20        self.name
21    }
22
23    /// Returns the field value.
24    pub const fn value(self) -> &'a [u8] {
25        self.value
26    }
27}
28
29/// A validated HTTP/1 field line.
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub struct Http1Field<'a> {
32    line: &'a [u8],
33    colon: usize,
34}
35
36impl<'a> Http1Field<'a> {
37    /// Returns the field line without its terminating CRLF.
38    pub const fn raw_line(self) -> &'a [u8] {
39        self.line
40    }
41
42    /// Returns the original field-name spelling.
43    pub fn name(self) -> &'a [u8] {
44        &self.line[..self.colon]
45    }
46
47    /// Returns the raw value immediately after the colon.
48    pub fn raw_value(self) -> &'a [u8] {
49        &self.line[self.colon + 1..]
50    }
51
52    /// Returns the field value with leading and trailing optional whitespace removed.
53    pub fn value(self) -> &'a [u8] {
54        trim_ows(self.raw_value())
55    }
56
57    /// Compares the field name using ASCII case-insensitive matching.
58    pub fn name_eq(self, name: &[u8]) -> bool {
59        eq_ascii_case(self.name(), name)
60    }
61}
62
63/// Ordered, validated HTTP/1 fields.
64#[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    /// Returns encoded field lines without the terminating blank line.
106    pub const fn as_bytes(self) -> &'a [u8] {
107        self.bytes
108    }
109
110    /// Iterates fields in wire order without combining duplicates.
111    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/// Iterator over validated HTTP/1 fields.
129#[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}