sark_core/http/codec/
header_utils.rs1use 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}