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