Skip to main content

sark_core/http/codec/
header_utils.rs

1use std::ops::ControlFlow;
2
3use http::header::{HeaderName, HeaderValue};
4
5use crate::{
6    error::{Error, Result},
7    http::HeaderList,
8};
9
10#[derive(Clone, Copy)]
11pub(super) enum CsvScanMode {
12    Lenient,
13    Strict,
14}
15
16pub struct Header;
17
18impl Header {
19    pub(super) fn is_ascii_ws(b: u8) -> bool {
20        b == b' ' || b == b'\t'
21    }
22
23    pub(super) fn trim_ascii_ws(value: &[u8]) -> &[u8] {
24        let mut start = 0usize;
25        let mut end = value.len();
26        while start < end && Self::is_ascii_ws(value[start]) {
27            start += 1;
28        }
29        while end > start && Self::is_ascii_ws(value[end - 1]) {
30            end -= 1;
31        }
32        &value[start..end]
33    }
34
35    pub(super) fn scan_csv_tokens(
36        value: &[u8],
37        mode: CsvScanMode,
38        mut visit: impl FnMut(&[u8]) -> ControlFlow<()>,
39    ) -> Result<()> {
40        match mode {
41            CsvScanMode::Strict => {
42                let mut start = 0usize;
43                let mut saw_token = false;
44                let mut i = 0usize;
45                while i <= value.len() {
46                    if i == value.len() || value[i] == b',' {
47                        let token = Self::trim_ascii_ws(&value[start..i]);
48                        if token.is_empty() {
49                            return Err(Error::BadRequest("Invalid Transfer-Encoding".into()));
50                        }
51                        saw_token = true;
52                        if visit(token).is_break() {
53                            return Ok(());
54                        }
55                        start = i.saturating_add(1);
56                    }
57                    i += 1;
58                }
59                if !saw_token {
60                    return Err(Error::BadRequest("Invalid Transfer-Encoding".into()));
61                }
62                Ok(())
63            }
64            CsvScanMode::Lenient => {
65                let mut i = 0usize;
66                while i < value.len() {
67                    while i < value.len() && (Self::is_ascii_ws(value[i]) || value[i] == b',') {
68                        i += 1;
69                    }
70                    if i >= value.len() {
71                        break;
72                    }
73                    let start = i;
74                    while i < value.len() && value[i] != b',' {
75                        i += 1;
76                    }
77                    let token = Self::trim_ascii_ws(&value[start..i]);
78                    if token.is_empty() {
79                        continue;
80                    }
81                    if visit(token).is_break() {
82                        break;
83                    }
84                }
85                Ok(())
86            }
87        }
88    }
89
90    pub fn has_token(value: &[u8], token: &[u8]) -> bool {
91        if token.is_empty() {
92            return false;
93        }
94        let mut found = false;
95        let _ = Self::scan_csv_tokens(value, CsvScanMode::Lenient, |part| {
96            if part.eq_ignore_ascii_case(token) {
97                found = true;
98                ControlFlow::Break(())
99            } else {
100                ControlFlow::Continue(())
101            }
102        });
103        found
104    }
105
106    pub(super) fn trimmed_eq_ascii_case(value: &[u8], expected: &[u8]) -> bool {
107        Self::trim_ascii_ws(value).eq_ignore_ascii_case(expected)
108    }
109
110    pub fn content_length(value: &[u8]) -> Result<usize> {
111        if value.is_empty() {
112            return Err(Error::BadRequest("Invalid Content-Length".into()));
113        }
114        let mut len: usize = 0;
115        for &b in value {
116            if !b.is_ascii_digit() {
117                return Err(Error::BadRequest("Invalid Content-Length".into()));
118            }
119            len = len
120                .checked_mul(10)
121                .and_then(|n| n.checked_add((b - b'0') as usize))
122                .ok_or_else(|| Error::BadRequest("Invalid Content-Length".into()))?;
123        }
124        Ok(len)
125    }
126
127    pub fn has_name(headers: &[httparse::Header<'_>], name: &str) -> bool {
128        headers.iter().any(|h| h.name.eq_ignore_ascii_case(name))
129    }
130}
131
132pub trait HeaderLookup: private::SealedHeaderLookup {
133    fn header_value(&self, name: HeaderName) -> Option<&HeaderValue>;
134
135    fn has_token(&self, name: HeaderName, token: &str) -> bool {
136        match self.header_value(name) {
137            Some(v) => Header::has_token(v.as_bytes(), token.as_bytes()),
138            None => false,
139        }
140    }
141
142    fn value_eq_ascii_case(&self, name: HeaderName, expected: &str) -> bool {
143        self.header_value(name)
144            .is_some_and(|value| value.as_bytes().eq_ignore_ascii_case(expected.as_bytes()))
145    }
146}
147
148impl HeaderLookup for http::HeaderMap {
149    fn header_value(&self, name: HeaderName) -> Option<&HeaderValue> {
150        self.get(name)
151    }
152}
153
154impl HeaderLookup for HeaderList {
155    fn header_value(&self, name: HeaderName) -> Option<&HeaderValue> {
156        self.get(name)
157    }
158}
159
160mod private {
161    use crate::http::HeaderList;
162
163    pub trait SealedHeaderLookup {}
164
165    impl SealedHeaderLookup for http::HeaderMap {}
166    impl SealedHeaderLookup for HeaderList {}
167}