1mod utilities;
3
4use crate::error::{Error, ErrorKind, ResultExt};
5use std::{fmt::Debug, str::FromStr};
6pub use utilities::*;
7
8pub trait AsHeaders {
10 type Iter: Iterator<Item = (HeaderName, HeaderValue)>;
11 fn as_headers(&self) -> Self::Iter;
12}
13
14impl<T> AsHeaders for T
15where
16 T: Header,
17{
18 type Iter = std::vec::IntoIter<(HeaderName, HeaderValue)>;
19
20 fn as_headers(&self) -> Self::Iter {
21 vec![(self.name(), self.value())].into_iter()
22 }
23}
24
25impl<T> AsHeaders for Option<T>
26where
27 T: AsHeaders<Iter = std::vec::IntoIter<(HeaderName, HeaderValue)>>,
28{
29 type Iter = T::Iter;
30
31 fn as_headers(&self) -> Self::Iter {
32 match self {
33 Some(h) => h.as_headers(),
34 None => vec![].into_iter(),
35 }
36 }
37}
38
39pub trait Header {
49 fn name(&self) -> HeaderName;
50 fn value(&self) -> HeaderValue;
51}
52
53#[derive(Clone, PartialEq, Eq, Default)]
55pub struct Headers(std::collections::HashMap<HeaderName, HeaderValue>);
56
57impl Headers {
58 pub fn new() -> Self {
59 Self::default()
60 }
61
62 pub fn get_optional_string(&self, key: &HeaderName) -> Option<String> {
64 self.get_as(key).ok()
65 }
66
67 pub fn get_str(&self, key: &HeaderName) -> crate::Result<&str> {
69 self.get_with(key, |s| crate::Result::Ok(s.as_str()))
70 }
71
72 pub fn get_optional_str(&self, key: &HeaderName) -> Option<&str> {
74 self.get_str(key).ok()
75 }
76
77 pub fn get_as<V, E>(&self, key: &HeaderName) -> crate::Result<V>
79 where
80 V: FromStr<Err = E>,
81 E: std::error::Error + Send + Sync + 'static,
82 {
83 self.get_with(key, |s| s.as_str().parse())
84 }
85
86 pub fn get_optional_as<V, E>(&self, key: &HeaderName) -> crate::Result<Option<V>>
88 where
89 V: FromStr<Err = E>,
90 E: std::error::Error + Send + Sync + 'static,
91 {
92 self.get_optional_with(key, |s| s.as_str().parse())
93 }
94
95 pub fn get_with<'a, V, F, E>(&'a self, key: &HeaderName, parser: F) -> crate::Result<V>
97 where
98 F: FnOnce(&'a HeaderValue) -> Result<V, E>,
99 E: std::error::Error + Send + Sync + 'static,
100 {
101 self.get_optional_with(key, parser)?.ok_or_else(|| {
102 Error::with_message(ErrorKind::DataConversion, || {
103 format!("header not found {}", key.as_str())
104 })
105 })
106 }
107
108 pub fn get_optional_with<'a, V, F, E>(
110 &'a self,
111 key: &HeaderName,
112 parser: F,
113 ) -> crate::Result<Option<V>>
114 where
115 F: FnOnce(&'a HeaderValue) -> Result<V, E>,
116 E: std::error::Error + Send + Sync + 'static,
117 {
118 self.0
119 .get(key)
120 .map(|v: &HeaderValue| {
121 parser(v).with_context(ErrorKind::DataConversion, || {
122 let ty = std::any::type_name::<V>();
123 format!("unable to parse header '{key:?}: {v:?}' into {ty}",)
124 })
125 })
126 .transpose()
127 }
128
129 pub fn insert<K, V>(&mut self, key: K, value: V)
131 where
132 K: Into<HeaderName>,
133 V: Into<HeaderValue>,
134 {
135 self.0.insert(key.into(), value.into());
136 }
137
138 pub fn add<H>(&mut self, header: H)
140 where
141 H: AsHeaders,
142 {
143 for (key, value) in header.as_headers() {
144 self.insert(key, value);
145 }
146 }
147
148 pub fn iter(&self) -> impl Iterator<Item = (&HeaderName, &HeaderValue)> {
150 self.0.iter()
151 }
152}
153
154fn matching_ignore_ascii_case(a: &str, b: &str) -> bool {
155 if a.len() != b.len() {
156 return false;
157 }
158 a.chars()
159 .zip(b.chars())
160 .all(|(a_c, b_c)| a_c.to_ascii_lowercase() == b_c.to_ascii_lowercase())
161}
162
163impl Debug for Headers {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 write!(f, "Headers(")?;
166 let redacted = HeaderValue::from_static("[redacted]");
167 f.debug_map()
168 .entries(self.0.iter().map(|(name, value)| {
169 if matching_ignore_ascii_case(name.as_str(), AUTHORIZATION.as_str()) {
170 (name, value)
171 } else {
172 (name, &redacted)
173 }
174 }))
175 .finish()?;
176 write!(f, ")")
177 }
178}
179
180impl IntoIterator for Headers {
181 type Item = (HeaderName, HeaderValue);
182
183 type IntoIter = std::collections::hash_map::IntoIter<HeaderName, HeaderValue>;
184
185 fn into_iter(self) -> Self::IntoIter {
186 self.0.into_iter()
187 }
188}
189
190impl From<std::collections::HashMap<HeaderName, HeaderValue>> for Headers {
191 fn from(c: std::collections::HashMap<HeaderName, HeaderValue>) -> Self {
192 Self(c)
193 }
194}
195
196#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
198pub struct HeaderName(std::borrow::Cow<'static, str>);
199
200impl HeaderName {
201 pub const fn from_static(s: &'static str) -> Self {
202 ensure_no_uppercase(s);
203 Self(std::borrow::Cow::Borrowed(s))
204 }
205
206 fn from_cow<C>(c: C) -> Self
207 where
208 C: Into<std::borrow::Cow<'static, str>>,
209 {
210 let c = c.into();
211 assert!(
212 c.chars().all(|c| c.is_lowercase() || !c.is_alphabetic()),
213 "header names must be lowercase: {c}"
214 );
215 Self(c)
216 }
217
218 pub fn as_str(&self) -> &str {
219 self.0.as_ref()
220 }
221}
222
223const fn ensure_no_uppercase(s: &str) {
225 let bytes = s.as_bytes();
226 let mut i = 0;
227 while i < bytes.len() {
228 let byte = bytes[i];
229 assert!(
230 !(byte >= 65u8 && byte <= 90u8),
231 "header names must not contain uppercase letters"
232 );
233 i += 1;
234 }
235}
236
237impl From<&'static str> for HeaderName {
238 fn from(s: &'static str) -> Self {
239 Self::from_cow(s)
240 }
241}
242
243impl From<String> for HeaderName {
244 fn from(s: String) -> Self {
245 Self::from_cow(s.to_lowercase())
246 }
247}
248
249#[derive(Clone, Debug, PartialEq, Eq)]
251pub struct HeaderValue(std::borrow::Cow<'static, str>);
252
253impl HeaderValue {
254 pub const fn from_static(s: &'static str) -> Self {
255 Self(std::borrow::Cow::Borrowed(s))
256 }
257
258 pub fn from_cow<C>(c: C) -> Self
259 where
260 C: Into<std::borrow::Cow<'static, str>>,
261 {
262 Self(c.into())
263 }
264
265 pub fn as_str(&self) -> &str {
266 self.0.as_ref()
267 }
268}
269
270impl From<&'static str> for HeaderValue {
271 fn from(s: &'static str) -> Self {
272 Self::from_cow(s)
273 }
274}
275
276impl From<String> for HeaderValue {
277 fn from(s: String) -> Self {
278 Self::from_cow(s)
279 }
280}
281
282impl From<&String> for HeaderValue {
283 fn from(s: &String) -> Self {
284 s.clone().into()
285 }
286}
287
288pub const ACCEPT: HeaderName = HeaderName::from_static("accept");
293pub const ACCEPT_ENCODING: HeaderName = HeaderName::from_static("accept-encoding");
294pub const ACL: HeaderName = HeaderName::from_static("x-ms-acl");
295pub const ACCOUNT_KIND: HeaderName = HeaderName::from_static("x-ms-account-kind");
296pub const ACTIVITY_ID: HeaderName = HeaderName::from_static("x-ms-activity-id");
297pub const APP: HeaderName = HeaderName::from_static("x-ms-app");
298pub const AUTHORIZATION: HeaderName = HeaderName::from_static("authorization");
299pub const APPEND_POSITION: HeaderName = HeaderName::from_static("x-ms-blob-condition-appendpos");
300pub const BLOB_ACCESS_TIER: HeaderName = HeaderName::from_static("x-ms-access-tier");
301pub const BLOB_CONTENT_LENGTH: HeaderName = HeaderName::from_static("x-ms-blob-content-length");
302pub const BLOB_PUBLIC_ACCESS: HeaderName = HeaderName::from_static("x-ms-blob-public-access");
303pub const BLOB_SEQUENCE_NUMBER: HeaderName = HeaderName::from_static("x-ms-blob-sequence-number");
304pub const BLOB_TYPE: HeaderName = HeaderName::from_static("x-ms-blob-type");
305pub const BLOB_CACHE_CONTROL: HeaderName = HeaderName::from_static("x-ms-blob-cache-control");
306pub const CACHE_CONTROL: HeaderName = HeaderName::from_static("cache-control");
307pub const CLIENT_REQUEST_ID: HeaderName = HeaderName::from_static("x-ms-client-request-id");
308pub const CLIENT_VERSION: HeaderName = HeaderName::from_static("x-ms-client-version");
309pub const CONTENT_DISPOSITION: HeaderName =
310 HeaderName::from_static("x-ms-blob-content-disposition");
311pub const CONTENT_ENCODING: HeaderName = HeaderName::from_static("content-encoding");
312pub const CONTENT_LANGUAGE: HeaderName = HeaderName::from_static("content-language");
313pub const CONTENT_LENGTH: HeaderName = HeaderName::from_static("content-length");
314pub const CONTENT_LOCATION: HeaderName = HeaderName::from_static("content-location");
315pub const CONTENT_MD5: HeaderName = HeaderName::from_static("content-md5");
316pub const CONTENT_RANGE: HeaderName = HeaderName::from_static("content-range");
317pub const CONTENT_SECURITY_POLICY: HeaderName = HeaderName::from_static("content-security-policy");
318pub const CONTENT_TYPE: HeaderName = HeaderName::from_static("content-type");
319pub const CONTINUATION: HeaderName = HeaderName::from_static("x-ms-continuation");
320pub const COPY_COMPLETION_TIME: HeaderName = HeaderName::from_static("x-ms-copy-completion-time");
321pub const COPY_PROGRESS: HeaderName = HeaderName::from_static("x-ms-copy-progress");
322pub const COPY_SOURCE: HeaderName = HeaderName::from_static("x-ms-copy-source");
323pub const COPY_STATUS: HeaderName = HeaderName::from_static("x-ms-copy-status");
324pub const COPY_STATUS_DESCRIPTION: HeaderName =
325 HeaderName::from_static("x-ms-copy-status-description");
326pub const CREATION_TIME: HeaderName = HeaderName::from_static("x-ms-creation-time");
327pub const DATE: HeaderName = HeaderName::from_static("date");
328pub const DELETE_SNAPSHOTS: HeaderName = HeaderName::from_static("x-ms-delete-snapshots");
329pub const DELETE_TYPE_PERMANENT: HeaderName = HeaderName::from_static("x-ms-delete-type-permanent");
330pub const ETAG: HeaderName = HeaderName::from_static("etag");
331pub const ERROR_CODE: HeaderName = HeaderName::from_static("x-ms-error-code");
332pub const HAS_IMMUTABILITY_POLICY: HeaderName =
333 HeaderName::from_static("x-ms-has-immutability-policy");
334pub const HAS_LEGAL_HOLD: HeaderName = HeaderName::from_static("x-ms-has-legal-hold");
335pub const IF_MATCH: HeaderName = HeaderName::from_static("if-match");
336pub const IF_MODIFIED_SINCE: HeaderName = HeaderName::from_static("if-modified-since");
337pub const IF_NONE_MATCH: HeaderName = HeaderName::from_static("if-none-match");
338pub const IF_RANGE: HeaderName = HeaderName::from_static("if-range");
339pub const IF_UNMODIFIED_SINCE: HeaderName = HeaderName::from_static("if-unmodified-since");
340pub const IF_SEQUENCE_NUMBER_EQ: HeaderName = HeaderName::from_static("x-ms-if-sequence-number-eq");
341pub const IF_SEQUENCE_NUMBER_LE: HeaderName = HeaderName::from_static("x-ms-if-sequence-number-le");
342pub const IF_SEQUENCE_NUMBER_LT: HeaderName = HeaderName::from_static("x-ms-if-sequence-number-lt");
343pub const IF_TAGS: HeaderName = HeaderName::from_static("x-ms-if-tags");
344pub const ITEM_COUNT: HeaderName = HeaderName::from_static("x-ms-item-count");
345pub const ITEM_TYPE: HeaderName = HeaderName::from_static("x-ms-item-type");
346pub const KEEP_ALIVE: HeaderName = HeaderName::from_static("keep-alive");
347pub const LAST_MODIFIED: HeaderName = HeaderName::from_static("last-modified");
348pub const LEASE_ACTION: HeaderName = HeaderName::from_static("x-ms-lease-action");
349pub const LEASE_BREAK_PERIOD: HeaderName = HeaderName::from_static("x-ms-lease-break-period");
350pub const LEASE_DURATION: HeaderName = HeaderName::from_static("x-ms-lease-duration");
351pub const LEASE_ID: HeaderName = HeaderName::from_static("x-ms-lease-id");
352pub const LEASE_STATE: HeaderName = HeaderName::from_static("x-ms-lease-state");
353pub const LEASE_STATUS: HeaderName = HeaderName::from_static("x-ms-lease-status");
354pub const LEASE_TIME: HeaderName = HeaderName::from_static("x-ms-lease-time");
355pub const LINK: HeaderName = HeaderName::from_static("link");
356pub const LOCATION: HeaderName = HeaderName::from_static("location");
357pub const MAX_ITEM_COUNT: HeaderName = HeaderName::from_static("x-ms-max-item-count");
358pub const META_PREFIX: HeaderName = HeaderName::from_static("x-ms-meta-");
359pub const MS_DATE: HeaderName = HeaderName::from_static("x-ms-date");
360pub const MS_RANGE: HeaderName = HeaderName::from_static("x-ms-range");
361pub const NAMESPACE_ENABLED: HeaderName = HeaderName::from_static("x-ms-namespace-enabled");
362pub const PAGE_WRITE: HeaderName = HeaderName::from_static("x-ms-page-write");
363pub const PROPERTIES: HeaderName = HeaderName::from_static("x-ms-properties");
364pub const PREFER: HeaderName = HeaderName::from_static("prefer");
365pub const PROPOSED_LEASE_ID: HeaderName = HeaderName::from_static("x-ms-proposed-lease-id");
366pub const RANGE: HeaderName = HeaderName::from_static("range");
367pub const RANGE_GET_CONTENT_CRC64: HeaderName =
368 HeaderName::from_static("x-ms-range-get-content-crc64");
369pub const RANGE_GET_CONTENT_MD5: HeaderName = HeaderName::from_static("x-ms-range-get-content-md5");
370pub const REQUEST_ID: HeaderName = HeaderName::from_static("x-ms-request-id");
371pub const REQUEST_SERVER_ENCRYPTED: HeaderName =
372 HeaderName::from_static("x-ms-request-server-encrypted");
373pub const REQUIRES_SYNC: HeaderName = HeaderName::from_static("x-ms-requires-sync");
374pub const RETRY_AFTER: HeaderName = HeaderName::from_static("retry-after");
375pub const RETRY_AFTER_MS: HeaderName = HeaderName::from_static("retry-after-ms");
376pub const X_MS_RETRY_AFTER_MS: HeaderName = HeaderName::from_static("x-ms-retry-after-ms");
377pub const SERVER: HeaderName = HeaderName::from_static("server");
378pub const SERVER_ENCRYPTED: HeaderName = HeaderName::from_static("x-ms-server-encrypted");
379pub const SESSION_TOKEN: HeaderName = HeaderName::from_static("x-ms-session-token");
380pub const SKU_NAME: HeaderName = HeaderName::from_static("x-ms-sku-name");
381pub const SOURCE_IF_MATCH: HeaderName = HeaderName::from_static("x-ms-source-if-match");
382pub const SOURCE_IF_MODIFIED_SINCE: HeaderName =
383 HeaderName::from_static("x-ms-source-if-modified-since");
384pub const SOURCE_IF_NONE_MATCH: HeaderName = HeaderName::from_static("x-ms-source-if-none-match");
385pub const SOURCE_IF_UNMODIFIED_SINCE: HeaderName =
386 HeaderName::from_static("x-ms-source-if-unmodified-since");
387pub const SOURCE_LEASE_ID: HeaderName = HeaderName::from_static("x-ms-source-lease-id");
388pub const TAGS: HeaderName = HeaderName::from_static("x-ms-tags");
389pub const USER: HeaderName = HeaderName::from_static("x-ms-user");
390pub const USER_AGENT: HeaderName = HeaderName::from_static("user-agent");
391pub const VERSION: HeaderName = HeaderName::from_static("x-ms-version");
392pub const WWW_AUTHENTICATE: HeaderName = HeaderName::from_static("www-authenticate");
393pub const ENCRYPTION_ALGORITHM: HeaderName = HeaderName::from_static("x-ms-encryption-algorithm");
394pub const ENCRYPTION_KEY: HeaderName = HeaderName::from_static("x-ms-encryption-key");
395pub const ENCRYPTION_KEY_SHA256: HeaderName = HeaderName::from_static("x-ms-encryption-key-sha256");
396pub const BLOB_COMMITTED_BLOCK_COUNT: HeaderName =
397 HeaderName::from_static("x-ms-blob-committed-block-count");
398pub const AZURE_ASYNCOPERATION: HeaderName = HeaderName::from_static("azure-asyncoperation");
399pub const OPERATION_LOCATION: HeaderName = HeaderName::from_static("operation-location");
400pub const SOURCE_RANGE: HeaderName = HeaderName::from_static("x-ms-source-range");