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