Skip to main content

sark_http/field/head/
parse.rs

1//! Parsing and state updates for HTTP headers with protocol-level semantics.
2
3use core::num::NonZero;
4
5use crate::{
6    field::{
7        head::{Error, Flags, headers::Bounds},
8        scan::{Bytes, Outcome},
9        text::{Ascii, IntError},
10    },
11    h1::head::request,
12};
13
14pub(super) const fn is_whitespace(byte: u8) -> bool {
15    byte == b' ' || byte == b'\t'
16}
17
18pub(super) fn try_token_line_lit<const N: usize>(raw: &[u8], expected: &[u8; N]) -> Option<Bounds> {
19    let start = if raw.first() == Some(&b' ') { 1 } else { 0 };
20    let end = start + N;
21    if raw.len() < end + 2 {
22        return None;
23    }
24    if !raw[start..end].eq_ignore_ascii_case(expected) {
25        return None;
26    }
27    if raw[end] != b'\r' || raw[end + 1] != b'\n' {
28        return None;
29    }
30    Some(Bounds::new(end, start, end))
31}
32
33pub(super) fn clen_line(scan: &mut request::Fields, raw: &[u8]) -> Result<Option<Bounds>, Error> {
34    let Some((len, bounds)) = parse_clen(raw)? else {
35        return Ok(None);
36    };
37    scan.message = scan.message.try_length(len).map_err(Error::Message)?;
38    Ok(Some(bounds))
39}
40
41pub(super) fn te_line(scan: &mut request::Fields, raw: &[u8]) -> Result<Option<Bounds>, Error> {
42    let (bounds, value) = match try_token_line_lit(raw, b"chunked") {
43        Some(bounds) => (bounds, b"chunked".as_slice()),
44        None => {
45            let Some(bounds) = trim_line(raw)? else {
46                return Ok(None);
47            };
48            (bounds, bounds.value(raw))
49        }
50    };
51    scan.message = scan.message.try_coding(value).map_err(Error::Message)?;
52    Ok(Some(bounds))
53}
54
55fn scan_length(raw: &[u8]) -> Result<Option<(usize, usize, usize)>, Error> {
56    let mut idx = if raw.first() == Some(&b' ') { 1 } else { 0 };
57    while idx < raw.len() && is_whitespace(raw[idx]) {
58        idx += 1;
59    }
60    let start = idx;
61    if start == raw.len() {
62        return Ok(None);
63    }
64    // SAFETY: the OWS scan stopped within this same immutable raw.
65    let rest = unsafe { raw.get_unchecked(start..) };
66    let (value, len) = Ascii::new(rest)
67        .parse_prefix()
68        .map_err(Error::ContentLength)?;
69    let end = start + len;
70    Ok(Some((value, start, end)))
71}
72
73pub(super) fn trim_line(raw: &[u8]) -> Result<Option<Bounds>, Error> {
74    let mut idx = 0usize;
75    while idx < raw.len() && is_whitespace(raw[idx]) {
76        idx += 1;
77    }
78    let start = idx;
79    let cr = match Bytes::new(raw).value(start) {
80        Outcome::Found(cr) => cr,
81        Outcome::Partial => return Ok(None),
82        Outcome::Invalid { .. } => return Err(Error::Value),
83    };
84    // SAFETY: this scan found CRLF after start inside the same immutable raw.
85    let mut value = unsafe { raw.get_unchecked(start..cr) };
86    while matches!(value.last(), Some(b' ' | b'\t')) {
87        value = &value[..value.len() - 1];
88    }
89    let end = start + value.len();
90    Ok(Some(Bounds::new(cr, start, end)))
91}
92
93fn parse_clen(raw: &[u8]) -> Result<Option<(usize, Bounds)>, Error> {
94    let Some((value, start, end)) = scan_length(raw)? else {
95        return Ok(None);
96    };
97    // SAFETY: scan_length counted actual digits within this exact raw's suffix.
98    let rest = unsafe { raw.get_unchecked(end..) };
99    if rest.starts_with(b"\r\n") {
100        return Ok(Some((value, Bounds::new(end, start, end))));
101    }
102    let mut idx = end;
103    while idx < raw.len() && is_whitespace(raw[idx]) {
104        idx += 1;
105    }
106    if idx >= raw.len() {
107        return Ok(None);
108    }
109    if raw[idx] != b'\r' {
110        return Err(Error::ContentLength(IntError::InvalidByte));
111    }
112    if idx + 1 >= raw.len() {
113        return Ok(None);
114    }
115    if raw[idx + 1] != b'\n' {
116        return Err(Error::ContentLength(IntError::InvalidByte));
117    }
118    Ok(Some((value, Bounds::new(idx, start, end))))
119}
120
121pub(super) fn ae_line(
122    scan: &mut request::Fields,
123    flags: &mut Flags,
124    raw: &[u8],
125) -> Result<Option<Bounds>, Error> {
126    let Some(bounds) = trim_line(raw)? else {
127        return Ok(None);
128    };
129    ae_value(scan, flags, bounds.value(raw))?;
130    Ok(Some(bounds))
131}
132
133const GZIP_SEEN: u16 = 1 << 3;
134const GZIP_ALLOWED: u16 = 1 << 4;
135const GZIP_EXCLUDED: u16 = 1 << 5;
136const WILDCARD_ALLOWED: u16 = 1 << 6;
137const WILDCARD_EXCLUDED: u16 = 1 << 7;
138
139const GZIP: usize = 0;
140const BR: usize = 1;
141const IDENTITY: usize = 2;
142const WILDCARD: usize = 3;
143
144/// Coding qualities use 0..=1000; repeated explicit qualities use their minimum.
145#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146pub struct AcceptEncoding([NonZero<u16>; 4]);
147
148impl Default for AcceptEncoding {
149    fn default() -> Self {
150        Self([NonZero::<u16>::MAX; 4])
151    }
152}
153
154impl AcceptEncoding {
155    pub fn try_parse(raw: &[u8]) -> Result<Self, Error> {
156        let (_, encoding) = scan::<true>(raw)?;
157        Ok(encoding)
158    }
159
160    pub fn try_apply(
161        fields: &mut request::Fields,
162        flags: &mut Flags,
163        raw: &[u8],
164    ) -> Result<Self, Error> {
165        let (next, encoding) = scan::<true>(raw)?;
166        apply(fields, flags, next);
167        Ok(encoding)
168    }
169
170    pub fn scan_line(
171        fields: &mut request::Fields,
172        flags: &mut Flags,
173        raw: &[u8],
174    ) -> Result<Option<(Bounds, Self)>, Error> {
175        let Some(bounds) = trim_line(raw)? else {
176            return Ok(None);
177        };
178        let encoding = Self::try_apply(fields, flags, bounds.value(raw))?;
179        Ok(Some((bounds, encoding)))
180    }
181
182    pub fn append(&mut self, other: Self) {
183        for (target, value) in self.0.iter_mut().zip(other.0) {
184            *target = (*target).min(value);
185        }
186    }
187
188    pub fn gzip(self) -> u16 {
189        Self::resolve(self.0[GZIP], self.0[WILDCARD])
190    }
191
192    pub fn br(self) -> u16 {
193        Self::resolve(self.0[BR], self.0[WILDCARD])
194    }
195
196    fn resolve(value: NonZero<u16>, wildcard: NonZero<u16>) -> u16 {
197        match value {
198            value if value != NonZero::<u16>::MAX => value.get() - 1,
199            _ if wildcard != NonZero::<u16>::MAX => wildcard.get() - 1,
200            _ => 0,
201        }
202    }
203
204    pub fn identity(self) -> u16 {
205        match self.0[IDENTITY] {
206            q if q != NonZero::<u16>::MAX => q.get() - 1,
207            _ if self.0[WILDCARD] == NonZero::<u16>::MIN => 0,
208            _ => 1000,
209        }
210    }
211}
212
213/// Accumulates supported coding admission across all field lines. An explicit
214/// gzip member overrides the wildcard, and an explicit zero remains excluded.
215pub(super) fn ae_value(
216    fields: &mut request::Fields,
217    flags: &mut Flags,
218    raw: &[u8],
219) -> Result<(), Error> {
220    let (next, _) = scan::<false>(raw)?;
221    apply(fields, flags, next);
222    Ok(())
223}
224
225fn scan<const FULL: bool>(raw: &[u8]) -> Result<(Flags, AcceptEncoding), Error> {
226    let mut next = Flags::default();
227    let mut encoding = AcceptEncoding::default();
228    for member in raw.split(|&byte| byte == b',') {
229        let member = crate::field::Error::trim_ows(member);
230        if member.is_empty() {
231            continue;
232        }
233        let end = member
234            .iter()
235            .position(|&byte| !crate::field::Error::is_name_byte(byte))
236            .unwrap_or(member.len());
237        if end == 0 {
238            return Err(Error::Value);
239        }
240        let token = &member[..end];
241        let quality = quality::<FULL>(&member[end..])?;
242        let slot = if token.eq_ignore_ascii_case(b"gzip") {
243            next.set(GZIP_SEEN);
244            next.set(if quality != 0 {
245                GZIP_ALLOWED
246            } else {
247                GZIP_EXCLUDED
248            });
249            GZIP
250        } else if token == b"*" {
251            next.set(if quality != 0 {
252                WILDCARD_ALLOWED
253            } else {
254                WILDCARD_EXCLUDED
255            });
256            WILDCARD
257        } else if FULL && token.eq_ignore_ascii_case(b"br") {
258            BR
259        } else if FULL && token.eq_ignore_ascii_case(b"identity") {
260            IDENTITY
261        } else {
262            continue;
263        };
264        if FULL && let Some(value) = NonZero::new(quality + 1) {
265            match slot {
266                GZIP => encoding.0[GZIP] = encoding.0[GZIP].min(value),
267                BR => encoding.0[BR] = encoding.0[BR].min(value),
268                IDENTITY => encoding.0[IDENTITY] = encoding.0[IDENTITY].min(value),
269                _ => encoding.0[WILDCARD] = encoding.0[WILDCARD].min(value),
270            }
271        }
272    }
273    Ok((next, encoding))
274}
275
276fn apply(fields: &mut request::Fields, flags: &mut Flags, next: Flags) {
277    flags.0 |= next.0;
278    let allowed = if flags.is_set(GZIP_SEEN) {
279        flags.is_set(GZIP_ALLOWED) && !flags.is_set(GZIP_EXCLUDED)
280    } else {
281        flags.is_set(WILDCARD_ALLOWED) && !flags.is_set(WILDCARD_EXCLUDED)
282    };
283    fields.message.set_coding(if allowed {
284        request::Coding::Gzip
285    } else {
286        request::Coding::Plain
287    });
288}
289
290/// The quality grammar has only four significant digits; parsing it directly
291/// keeps malformed controls and parameters out of the admitted field value.
292fn quality<const FULL: bool>(raw: &[u8]) -> Result<u16, Error> {
293    let raw = crate::field::Error::trim_ows(raw);
294    if raw.is_empty() {
295        return Ok(if FULL { 1000 } else { 1 });
296    }
297    let Some(raw) = raw.strip_prefix(b";") else {
298        return Err(Error::Value);
299    };
300    let raw = crate::field::Error::trim_ows(raw);
301    let Some((&name, raw)) = raw.split_first() else {
302        return Err(Error::Value);
303    };
304    if !name.eq_ignore_ascii_case(&b'q') {
305        return Err(Error::Value);
306    }
307    let Some(raw) = raw.strip_prefix(b"=") else {
308        return Err(Error::Value);
309    };
310    let Some((&whole, rest)) = raw.split_first() else {
311        return Err(Error::Value);
312    };
313    if !matches!(whole, b'0' | b'1') {
314        return Err(Error::Value);
315    }
316    let fraction = if rest.is_empty() {
317        rest
318    } else {
319        rest.strip_prefix(b".").ok_or(Error::Value)?
320    };
321    if fraction.len() > 3 {
322        return Err(Error::Value);
323    }
324    let mut quality = u16::from(whole == b'1');
325    if FULL {
326        quality *= 1000;
327    }
328    let mut scale = 100;
329    for &byte in fraction {
330        if !byte.is_ascii_digit() || (whole == b'1' && byte != b'0') {
331            return Err(Error::Value);
332        }
333        if FULL {
334            quality += u16::from(byte - b'0') * scale;
335            scale /= 10;
336        } else {
337            quality |= u16::from(byte != b'0');
338        }
339    }
340    Ok(quality)
341}