cloud_sdk/transport/header/
mod.rs1mod request;
4mod response;
5
6pub use request::{RequestHeader, RequestHeaders};
7pub use response::{ResponseHeader, ResponseHeaders};
8
9use core::cmp::Ordering;
10use core::fmt;
11use core::hash::{Hash, Hasher};
12
13use super::{ContentType, MediaType};
14
15pub const MAX_HEADER_NAME_BYTES: usize = 64;
17pub const MAX_HEADER_VALUE_BYTES: usize = 1024;
19pub const MAX_REQUEST_HEADERS: usize = 32;
21pub const MAX_REQUEST_HEADER_BYTES: usize = 8192;
23pub const MAX_RESPONSE_HEADERS: usize = 32;
25pub const MAX_RESPONSE_HEADER_BYTES: usize = 8192;
27
28const HEADER_LINE_OVERHEAD: usize = 4;
29
30#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
32pub enum HeaderSensitivity {
33 Public,
35 Sensitive,
37}
38
39impl HeaderSensitivity {
40 #[must_use]
42 pub(crate) const fn is_sensitive(self) -> bool {
43 matches!(self, Self::Sensitive)
44 }
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub enum HeaderError {
50 EmptyName,
52 NameTooLong,
54 InvalidName,
56 ValueTooLong,
58 InvalidValue,
60 ReservedRequestHeader,
62 DuplicateName,
64 TooManyHeaders,
66 AggregateTooLarge,
68 InvalidContentType,
70 OutputTooSmall,
72}
73
74impl_static_error!(HeaderError,
75 Self::EmptyName => "HTTP header name is empty",
76 Self::NameTooLong => "HTTP header name exceeds the length limit",
77 Self::InvalidName => "HTTP header name is invalid",
78 Self::ValueTooLong => "HTTP header value exceeds the length limit",
79 Self::InvalidValue => "HTTP header value is invalid",
80 Self::ReservedRequestHeader => "HTTP request header ownership is reserved",
81 Self::DuplicateName => "HTTP header name is duplicated",
82 Self::TooManyHeaders => "HTTP header count exceeds the limit",
83 Self::AggregateTooLarge => "HTTP header block exceeds the byte limit",
84 Self::InvalidContentType => "HTTP content type is invalid",
85 Self::OutputTooSmall => "HTTP header output is too small",
86);
87
88#[derive(Clone, Copy)]
90pub struct HeaderName<'a>(&'a str);
91
92impl<'a> HeaderName<'a> {
93 pub fn new(value: &'a str) -> Result<Self, HeaderError> {
95 validate_name(value)?;
96 Ok(Self(value))
97 }
98
99 #[must_use]
101 pub const fn as_str(self) -> &'a str {
102 self.0
103 }
104
105 #[must_use]
107 pub fn eq_ignore_ascii_case(self, other: &str) -> bool {
108 self.0.eq_ignore_ascii_case(other)
109 }
110}
111
112impl PartialEq for HeaderName<'_> {
113 fn eq(&self, other: &Self) -> bool {
114 self.0.eq_ignore_ascii_case(other.0)
115 }
116}
117
118impl Eq for HeaderName<'_> {}
119
120impl PartialOrd for HeaderName<'_> {
121 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
122 Some(self.cmp(other))
123 }
124}
125
126impl Ord for HeaderName<'_> {
127 fn cmp(&self, other: &Self) -> Ordering {
128 self.0
129 .bytes()
130 .map(|byte| byte.to_ascii_lowercase())
131 .cmp(other.0.bytes().map(|byte| byte.to_ascii_lowercase()))
132 }
133}
134
135impl Hash for HeaderName<'_> {
136 fn hash<H: Hasher>(&self, state: &mut H) {
137 for byte in self.0.bytes() {
138 state.write_u8(byte.to_ascii_lowercase());
139 }
140 }
141}
142
143impl fmt::Debug for HeaderName<'_> {
144 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
145 formatter.debug_tuple("HeaderName").field(&self.0).finish()
146 }
147}
148
149#[derive(Clone, Copy)]
162pub struct HeaderValue<'a>(&'a str);
163
164impl<'a> HeaderValue<'a> {
165 pub fn new(value: &'a str) -> Result<Self, HeaderError> {
167 validate_request_value(value)?;
168 Ok(Self(value))
169 }
170
171 #[must_use]
173 pub const fn as_str(self) -> &'a str {
174 self.0
175 }
176
177 const fn validated(value: &'a str) -> Self {
178 Self(value)
179 }
180}
181
182impl fmt::Debug for HeaderValue<'_> {
183 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
184 formatter.write_str("HeaderValue([redacted])")
185 }
186}
187
188fn validate_name(value: &str) -> Result<(), HeaderError> {
189 if value.is_empty() {
190 return Err(HeaderError::EmptyName);
191 }
192 if value.len() > MAX_HEADER_NAME_BYTES {
193 return Err(HeaderError::NameTooLong);
194 }
195 if !value.bytes().all(is_token_byte) {
196 return Err(HeaderError::InvalidName);
197 }
198 Ok(())
199}
200
201fn validate_request_value(value: &str) -> Result<(), HeaderError> {
202 if value.len() > MAX_HEADER_VALUE_BYTES {
203 return Err(HeaderError::ValueTooLong);
204 }
205 if !value.bytes().all(|byte| (b' '..=b'~').contains(&byte))
206 || value.starts_with(' ')
207 || value.ends_with(' ')
208 {
209 return Err(HeaderError::InvalidValue);
210 }
211 Ok(())
212}
213
214fn validate_response_value(value: &[u8]) -> Result<(), HeaderError> {
215 if value.len() > MAX_HEADER_VALUE_BYTES {
216 return Err(HeaderError::ValueTooLong);
217 }
218 if value.iter().any(|byte| *byte < b' ' || *byte == 0x7f) {
219 return Err(HeaderError::InvalidValue);
220 }
221 Ok(())
222}
223
224fn encoded_line_len(name_len: usize, value_len: usize) -> Result<usize, HeaderError> {
225 name_len
226 .checked_add(value_len)
227 .and_then(|len| len.checked_add(HEADER_LINE_OVERHEAD))
228 .ok_or(HeaderError::AggregateTooLarge)
229}
230
231fn is_reserved_request_name(name: HeaderName<'_>) -> bool {
232 const RESERVED: &[&str] = &[
233 "authorization",
234 "connection",
235 "content-length",
236 "host",
237 "keep-alive",
238 "proxy-authenticate",
239 "proxy-authorization",
240 "proxy-connection",
241 "te",
242 "trailer",
243 "transfer-encoding",
244 "upgrade",
245 "user-agent",
246 ];
247 RESERVED
248 .iter()
249 .any(|reserved| name.eq_ignore_ascii_case(reserved))
250}
251
252fn is_token_byte(byte: u8) -> bool {
253 byte.is_ascii_alphanumeric()
254 || matches!(
255 byte,
256 b'!' | b'#'
257 | b'$'
258 | b'%'
259 | b'&'
260 | b'\''
261 | b'*'
262 | b'+'
263 | b'-'
264 | b'.'
265 | b'^'
266 | b'_'
267 | b'`'
268 | b'|'
269 | b'~'
270 )
271}
272
273const fn typed_accept<'a>(media_type: MediaType<'a>) -> HeaderValue<'a> {
274 HeaderValue::validated(media_type.as_str())
275}
276
277const fn typed_content_type<'a>(content_type: ContentType<'a>) -> HeaderValue<'a> {
278 HeaderValue::validated(content_type.as_str())
279}
280
281#[cfg(test)]
282mod tests;