Skip to main content

sark_core/http/codec/
header_utils.rs

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