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
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
41pub enum HeaderError {
42 EmptyName,
44 NameTooLong,
46 InvalidName,
48 ValueTooLong,
50 InvalidValue,
52 ReservedRequestHeader,
54 DuplicateName,
56 TooManyHeaders,
58 AggregateTooLarge,
60 InvalidContentType,
62 OutputTooSmall,
64}
65
66impl_static_error!(HeaderError,
67 Self::EmptyName => "HTTP header name is empty",
68 Self::NameTooLong => "HTTP header name exceeds the length limit",
69 Self::InvalidName => "HTTP header name is invalid",
70 Self::ValueTooLong => "HTTP header value exceeds the length limit",
71 Self::InvalidValue => "HTTP header value is invalid",
72 Self::ReservedRequestHeader => "HTTP request header ownership is reserved",
73 Self::DuplicateName => "HTTP header name is duplicated",
74 Self::TooManyHeaders => "HTTP header count exceeds the limit",
75 Self::AggregateTooLarge => "HTTP header block exceeds the byte limit",
76 Self::InvalidContentType => "HTTP content type is invalid",
77 Self::OutputTooSmall => "HTTP header output is too small",
78);
79
80#[derive(Clone, Copy)]
82pub struct HeaderName<'a>(&'a str);
83
84impl<'a> HeaderName<'a> {
85 pub fn new(value: &'a str) -> Result<Self, HeaderError> {
87 validate_name(value)?;
88 Ok(Self(value))
89 }
90
91 #[must_use]
93 pub const fn as_str(self) -> &'a str {
94 self.0
95 }
96
97 #[must_use]
99 pub fn eq_ignore_ascii_case(self, other: &str) -> bool {
100 self.0.eq_ignore_ascii_case(other)
101 }
102}
103
104impl PartialEq for HeaderName<'_> {
105 fn eq(&self, other: &Self) -> bool {
106 self.0.eq_ignore_ascii_case(other.0)
107 }
108}
109
110impl Eq for HeaderName<'_> {}
111
112impl PartialOrd for HeaderName<'_> {
113 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
114 Some(self.cmp(other))
115 }
116}
117
118impl Ord for HeaderName<'_> {
119 fn cmp(&self, other: &Self) -> Ordering {
120 self.0
121 .bytes()
122 .map(|byte| byte.to_ascii_lowercase())
123 .cmp(other.0.bytes().map(|byte| byte.to_ascii_lowercase()))
124 }
125}
126
127impl Hash for HeaderName<'_> {
128 fn hash<H: Hasher>(&self, state: &mut H) {
129 for byte in self.0.bytes() {
130 state.write_u8(byte.to_ascii_lowercase());
131 }
132 }
133}
134
135impl fmt::Debug for HeaderName<'_> {
136 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
137 formatter.debug_tuple("HeaderName").field(&self.0).finish()
138 }
139}
140
141#[derive(Clone, Copy)]
154pub struct HeaderValue<'a>(&'a str);
155
156impl<'a> HeaderValue<'a> {
157 pub fn new(value: &'a str) -> Result<Self, HeaderError> {
159 validate_request_value(value)?;
160 Ok(Self(value))
161 }
162
163 #[must_use]
165 pub const fn as_str(self) -> &'a str {
166 self.0
167 }
168
169 const fn validated(value: &'a str) -> Self {
170 Self(value)
171 }
172}
173
174impl fmt::Debug for HeaderValue<'_> {
175 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
176 formatter.write_str("HeaderValue([redacted])")
177 }
178}
179
180fn validate_name(value: &str) -> Result<(), HeaderError> {
181 if value.is_empty() {
182 return Err(HeaderError::EmptyName);
183 }
184 if value.len() > MAX_HEADER_NAME_BYTES {
185 return Err(HeaderError::NameTooLong);
186 }
187 if !value.bytes().all(is_token_byte) {
188 return Err(HeaderError::InvalidName);
189 }
190 Ok(())
191}
192
193fn validate_request_value(value: &str) -> Result<(), HeaderError> {
194 if value.len() > MAX_HEADER_VALUE_BYTES {
195 return Err(HeaderError::ValueTooLong);
196 }
197 if !value.bytes().all(|byte| (b' '..=b'~').contains(&byte))
198 || value.starts_with(' ')
199 || value.ends_with(' ')
200 {
201 return Err(HeaderError::InvalidValue);
202 }
203 Ok(())
204}
205
206fn validate_response_value(value: &[u8]) -> Result<(), HeaderError> {
207 if value.len() > MAX_HEADER_VALUE_BYTES {
208 return Err(HeaderError::ValueTooLong);
209 }
210 if value.iter().any(|byte| *byte < b' ' || *byte == 0x7f) {
211 return Err(HeaderError::InvalidValue);
212 }
213 Ok(())
214}
215
216fn encoded_line_len(name_len: usize, value_len: usize) -> Result<usize, HeaderError> {
217 name_len
218 .checked_add(value_len)
219 .and_then(|len| len.checked_add(HEADER_LINE_OVERHEAD))
220 .ok_or(HeaderError::AggregateTooLarge)
221}
222
223fn is_reserved_request_name(name: HeaderName<'_>) -> bool {
224 const RESERVED: &[&str] = &[
225 "authorization",
226 "connection",
227 "content-length",
228 "host",
229 "keep-alive",
230 "proxy-authenticate",
231 "proxy-authorization",
232 "proxy-connection",
233 "te",
234 "trailer",
235 "transfer-encoding",
236 "upgrade",
237 ];
238 RESERVED
239 .iter()
240 .any(|reserved| name.eq_ignore_ascii_case(reserved))
241}
242
243fn is_token_byte(byte: u8) -> bool {
244 byte.is_ascii_alphanumeric()
245 || matches!(
246 byte,
247 b'!' | b'#'
248 | b'$'
249 | b'%'
250 | b'&'
251 | b'\''
252 | b'*'
253 | b'+'
254 | b'-'
255 | b'.'
256 | b'^'
257 | b'_'
258 | b'`'
259 | b'|'
260 | b'~'
261 )
262}
263
264const fn typed_accept<'a>(media_type: MediaType<'a>) -> HeaderValue<'a> {
265 HeaderValue::validated(media_type.as_str())
266}
267
268const fn typed_content_type<'a>(content_type: ContentType<'a>) -> HeaderValue<'a> {
269 HeaderValue::validated(content_type.as_str())
270}
271
272#[cfg(test)]
273mod tests;