Skip to main content

sark_http/h1/field/
mod.rs

1//! HTTP/1 field syntax and borrow-free field state.
2
3use core::mem;
4
5use crate::field::{
6    self,
7    scan::Match,
8    text::{Ascii, IntError},
9};
10
11mod cursor;
12mod entry;
13pub mod expect;
14pub mod scan;
15pub mod syntax;
16mod transfer;
17pub mod value;
18
19pub(in crate::h1) use cursor::{Cursor, End};
20pub use entry::Entry;
21pub use transfer::Transfer;
22
23pub(in crate::h1) enum Progress<B, E = ()> {
24    Break(B),
25    /// A sink asked for the next field and carried its own continuation value.
26    Emitted(E),
27    More,
28    End(End),
29}
30
31const _: () = assert!(core::mem::size_of::<Progress<(), ()>>() == 12);
32
33#[derive(Clone, Copy, PartialEq, Eq)]
34pub(in crate::h1) struct Limit {
35    count: u32,
36    line_bytes: u32,
37}
38
39impl Limit {
40    pub(in crate::h1) const fn new(count: usize, line: usize) -> Self {
41        Self {
42            count: Self::narrow(count),
43            line_bytes: Self::narrow(line),
44        }
45    }
46
47    const fn narrow(value: usize) -> u32 {
48        if value > u32::MAX as usize {
49            u32::MAX
50        } else {
51            value as u32
52        }
53    }
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum Error {
58    DuplicateContentLength,
59    ContentLengthWithTransferEncoding,
60    InvalidContentLength(IntError),
61    InvalidTransferEncoding(value::Error),
62}
63
64/// Accumulated connection-option semantics shared by request and response
65/// scanners. Unknown valid tokens are intentionally ignored; malformed token
66/// bytes fail admission.
67#[repr(transparent)]
68#[derive(Clone, Copy, Default, PartialEq, Eq)]
69pub struct Connection(u8);
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub struct TokenError {
73    pub index: usize,
74    pub byte: u8,
75}
76
77impl Connection {
78    pub(crate) const CLOSE: Self = Self(1 << 0);
79    pub(crate) const KEEP_ALIVE: Self = Self(1 << 1);
80    const UPGRADE: u8 = 1 << 2;
81
82    pub fn try_push(mut self, value: &[u8]) -> Result<Self, TokenError> {
83        let mut rest = value;
84        while !rest.is_empty() {
85            let base = value.len() - rest.len();
86            let raw = match Match::find_by(rest, |byte| byte == b',') {
87                Some(comma) => {
88                    rest = comma.suffix();
89                    comma.prefix()
90                }
91                None => mem::take(&mut rest),
92            };
93            let mut token = raw;
94            while let [b' ' | b'\t', rest @ ..] = token {
95                token = rest;
96            }
97            let start = raw.len() - token.len();
98            while let [rest @ .., b' ' | b'\t'] = token {
99                token = rest;
100            }
101            if token.is_empty() {
102                continue;
103            }
104            if let Some((index, byte)) = token
105                .iter()
106                .copied()
107                .enumerate()
108                .find(|(_, byte)| !field::Error::is_name_byte(*byte))
109            {
110                return Err(TokenError {
111                    index: base + start + index,
112                    byte,
113                });
114            }
115            if token.eq_ignore_ascii_case(b"close") {
116                self.0 |= Self::CLOSE.0;
117            } else if token.eq_ignore_ascii_case(b"keep-alive") {
118                self.0 |= Self::KEEP_ALIVE.0;
119            } else if token.eq_ignore_ascii_case(b"upgrade") {
120                self.0 |= Self::UPGRADE;
121            }
122        }
123        Ok(self)
124    }
125
126    /// Merges connection options that were parsed by another scanner branch.
127    #[doc(hidden)]
128    pub fn append(&mut self, fields: Self) {
129        self.0 |= fields.0;
130    }
131
132    pub const fn is_close(self) -> bool {
133        self.0 & Self::CLOSE.0 != 0
134    }
135
136    pub const fn is_keep(self) -> bool {
137        self.0 & Self::KEEP_ALIVE.0 != 0
138    }
139
140    pub const fn is_upgrade(self) -> bool {
141        self.0 & Self::UPGRADE != 0
142    }
143}
144
145/// Advisory persistence parameters accumulated across every `Keep-Alive`
146/// field. Malformed and unknown parameters are ignored because they cannot
147/// change message framing; the shortest valid timeout wins deterministically.
148#[derive(Clone, Copy, Default, PartialEq, Eq)]
149pub struct KeepAlive {
150    timeout: Option<u64>,
151}
152
153impl KeepAlive {
154    pub(crate) fn parse(mut prior: Option<u64>, value: &[u8]) -> Option<u64> {
155        for raw in value.split(|byte| *byte == b',') {
156            let param = field::Error::trim_ows(raw);
157            let Some(split) = param.iter().position(|byte| *byte == b'=') else {
158                continue;
159            };
160            let name = field::Error::trim_ows(&param[..split]);
161            if !name.eq_ignore_ascii_case(b"timeout") {
162                continue;
163            }
164            let Ok(timeout) = Ascii::new(field::Error::trim_ows(&param[split + 1..])).parse_u64()
165            else {
166                continue;
167            };
168            prior = Some(prior.map_or(timeout, |current| current.min(timeout)));
169        }
170        prior
171    }
172
173    pub(crate) const fn new(timeout: Option<u64>) -> Self {
174        Self { timeout }
175    }
176
177    pub const fn timeout_secs(self) -> Option<u64> {
178        self.timeout
179    }
180}
181
182const _: () = assert!(core::mem::size_of::<Connection>() == 1);