aws_smithy_runtime_api/http/
headers.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Types for HTTP headers
7
8use crate::http::error::{HttpError, NonUtf8Header};
9use std::borrow::Cow;
10use std::fmt::Debug;
11use std::str::FromStr;
12
13/// An immutable view of headers
14#[derive(Clone, Default, Debug)]
15pub struct Headers {
16    pub(super) headers: http_02x::HeaderMap<HeaderValue>,
17}
18
19impl<'a> IntoIterator for &'a Headers {
20    type Item = (&'a str, &'a str);
21    type IntoIter = HeadersIter<'a>;
22
23    fn into_iter(self) -> Self::IntoIter {
24        HeadersIter {
25            inner: self.headers.iter(),
26        }
27    }
28}
29
30/// An Iterator over headers
31pub struct HeadersIter<'a> {
32    inner: http_02x::header::Iter<'a, HeaderValue>,
33}
34
35impl<'a> Iterator for HeadersIter<'a> {
36    type Item = (&'a str, &'a str);
37
38    fn next(&mut self) -> Option<Self::Item> {
39        self.inner.next().map(|(k, v)| (k.as_str(), v.as_ref()))
40    }
41}
42
43impl Headers {
44    /// Create an empty header map
45    pub fn new() -> Self {
46        Self::default()
47    }
48
49    #[cfg(feature = "http-1x")]
50    pub(crate) fn http1_headermap(self) -> http_1x::HeaderMap {
51        let mut headers = http_1x::HeaderMap::new();
52        headers.reserve(self.headers.len());
53        headers.extend(self.headers.into_iter().map(|(k, v)| {
54            (
55                k.map(|n| {
56                    http_1x::HeaderName::from_bytes(n.as_str().as_bytes()).expect("proven valid")
57                }),
58                v.into_http1x(),
59            )
60        }));
61        headers
62    }
63
64    #[cfg(feature = "http-02x")]
65    pub(crate) fn http0_headermap(self) -> http_02x::HeaderMap {
66        let mut headers = http_02x::HeaderMap::new();
67        headers.reserve(self.headers.len());
68        headers.extend(self.headers.into_iter().map(|(k, v)| (k, v.into_http02x())));
69        headers
70    }
71
72    /// Returns the value for a given key
73    ///
74    /// If multiple values are associated, the first value is returned
75    /// See [HeaderMap::get](http_02x::HeaderMap::get)
76    pub fn get(&self, key: impl AsRef<str>) -> Option<&str> {
77        self.headers.get(key.as_ref()).map(|v| v.as_ref())
78    }
79
80    /// Returns all values for a given key
81    pub fn get_all(&self, key: impl AsRef<str>) -> impl Iterator<Item = &str> {
82        self.headers
83            .get_all(key.as_ref())
84            .iter()
85            .map(|v| v.as_ref())
86    }
87
88    /// Returns an iterator over the headers
89    pub fn iter(&self) -> HeadersIter<'_> {
90        HeadersIter {
91            inner: self.headers.iter(),
92        }
93    }
94
95    /// Returns the total number of **values** stored in the map
96    pub fn len(&self) -> usize {
97        self.headers.len()
98    }
99
100    /// Returns true if there are no headers
101    pub fn is_empty(&self) -> bool {
102        self.len() == 0
103    }
104
105    /// Returns true if this header is present
106    pub fn contains_key(&self, key: impl AsRef<str>) -> bool {
107        self.headers.contains_key(key.as_ref())
108    }
109
110    /// Insert a value into the headers structure.
111    ///
112    /// This will *replace* any existing value for this key. Returns the previous associated value if any.
113    ///
114    /// # Panics
115    /// If the key is not valid ASCII, or if the value is not valid UTF-8, this function will panic.
116    pub fn insert(
117        &mut self,
118        key: impl AsHeaderComponent,
119        value: impl AsHeaderComponent,
120    ) -> Option<String> {
121        let key = header_name(key, false).unwrap();
122        let value = header_value(value.into_maybe_static().unwrap(), false).unwrap();
123        self.headers
124            .insert(key, value)
125            .map(|old_value| old_value.into())
126    }
127
128    /// Insert a value into the headers structure.
129    ///
130    /// This will *replace* any existing value for this key. Returns the previous associated value if any.
131    ///
132    /// If the key is not valid ASCII, or if the value is not valid UTF-8, this function will return an error.
133    pub fn try_insert(
134        &mut self,
135        key: impl AsHeaderComponent,
136        value: impl AsHeaderComponent,
137    ) -> Result<Option<String>, HttpError> {
138        let key = header_name(key, true)?;
139        let value = header_value(value.into_maybe_static()?, true)?;
140        Ok(self
141            .headers
142            .insert(key, value)
143            .map(|old_value| old_value.into()))
144    }
145
146    /// Appends a value to a given key
147    ///
148    /// # Panics
149    /// If the key is not valid ASCII, or if the value is not valid UTF-8, this function will panic.
150    pub fn append(&mut self, key: impl AsHeaderComponent, value: impl AsHeaderComponent) -> bool {
151        let key = header_name(key.into_maybe_static().unwrap(), false).unwrap();
152        let value = header_value(value.into_maybe_static().unwrap(), false).unwrap();
153        self.headers.append(key, value)
154    }
155
156    /// Appends a value to a given key
157    ///
158    /// If the key is not valid ASCII, or if the value is not valid UTF-8, this function will return an error.
159    pub fn try_append(
160        &mut self,
161        key: impl AsHeaderComponent,
162        value: impl AsHeaderComponent,
163    ) -> Result<bool, HttpError> {
164        let key = header_name(key.into_maybe_static()?, true)?;
165        let value = header_value(value.into_maybe_static()?, true)?;
166        Ok(self.headers.append(key, value))
167    }
168
169    /// Removes all headers with a given key
170    ///
171    /// If there are multiple entries for this key, the first entry is returned
172    pub fn remove(&mut self, key: impl AsRef<str>) -> Option<String> {
173        self.headers
174            .remove(key.as_ref())
175            .map(|h| h.as_str().to_string())
176    }
177}
178
179#[cfg(feature = "http-02x")]
180impl TryFrom<http_02x::HeaderMap> for Headers {
181    type Error = HttpError;
182
183    fn try_from(value: http_02x::HeaderMap) -> Result<Self, Self::Error> {
184        if let Some(utf8_error) = value.iter().find_map(|(k, v)| {
185            std::str::from_utf8(v.as_bytes())
186                .err()
187                .map(|err| NonUtf8Header::new(k.as_str().to_owned(), v.as_bytes().to_vec(), err))
188        }) {
189            Err(HttpError::non_utf8_header(utf8_error))
190        } else {
191            let mut string_safe_headers: http_02x::HeaderMap<HeaderValue> = Default::default();
192            string_safe_headers.extend(
193                value
194                    .into_iter()
195                    .map(|(k, v)| (k, HeaderValue::from_http02x(v).expect("validated above"))),
196            );
197            Ok(Headers {
198                headers: string_safe_headers,
199            })
200        }
201    }
202}
203
204#[cfg(feature = "http-1x")]
205impl TryFrom<http_1x::HeaderMap> for Headers {
206    type Error = HttpError;
207
208    fn try_from(value: http_1x::HeaderMap) -> Result<Self, Self::Error> {
209        if let Some(utf8_error) = value.iter().find_map(|(k, v)| {
210            std::str::from_utf8(v.as_bytes())
211                .err()
212                .map(|err| NonUtf8Header::new(k.as_str().to_owned(), v.as_bytes().to_vec(), err))
213        }) {
214            Err(HttpError::non_utf8_header(utf8_error))
215        } else {
216            let mut string_safe_headers: http_02x::HeaderMap<HeaderValue> = Default::default();
217            string_safe_headers.extend(value.into_iter().map(|(k, v)| {
218                (
219                    k.map(|v| {
220                        http_02x::HeaderName::from_bytes(v.as_str().as_bytes())
221                            .expect("known valid")
222                    }),
223                    HeaderValue::from_http1x(v).expect("validated above"),
224                )
225            }));
226            Ok(Headers {
227                headers: string_safe_headers,
228            })
229        }
230    }
231}
232
233use sealed::AsHeaderComponent;
234
235mod sealed {
236    use super::*;
237    /// Trait defining things that may be converted into a header component (name or value)
238    pub trait AsHeaderComponent {
239        /// If the component can be represented as a Cow<'static, str>, return it
240        fn into_maybe_static(self) -> Result<MaybeStatic, HttpError>;
241
242        /// Return a string reference to this header
243        fn as_str(&self) -> Result<&str, HttpError>;
244
245        /// If a component is already internally represented as a `http02x::HeaderName`, return it
246        fn repr_as_http02x_header_name(self) -> Result<http_02x::HeaderName, Self>
247        where
248            Self: Sized,
249        {
250            Err(self)
251        }
252    }
253
254    impl AsHeaderComponent for &'static str {
255        fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
256            Ok(Cow::Borrowed(self))
257        }
258
259        fn as_str(&self) -> Result<&str, HttpError> {
260            Ok(self)
261        }
262    }
263
264    impl AsHeaderComponent for String {
265        fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
266            Ok(Cow::Owned(self))
267        }
268
269        fn as_str(&self) -> Result<&str, HttpError> {
270            Ok(self)
271        }
272    }
273
274    impl AsHeaderComponent for Cow<'static, str> {
275        fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
276            Ok(self)
277        }
278
279        fn as_str(&self) -> Result<&str, HttpError> {
280            Ok(self.as_ref())
281        }
282    }
283
284    impl AsHeaderComponent for http_02x::HeaderValue {
285        fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
286            Ok(Cow::Owned(
287                std::str::from_utf8(self.as_bytes())
288                    .map_err(|err| {
289                        HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
290                            self.as_bytes().to_vec(),
291                            err,
292                        ))
293                    })?
294                    .to_string(),
295            ))
296        }
297
298        fn as_str(&self) -> Result<&str, HttpError> {
299            std::str::from_utf8(self.as_bytes()).map_err(|err| {
300                HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
301                    self.as_bytes().to_vec(),
302                    err,
303                ))
304            })
305        }
306    }
307
308    impl AsHeaderComponent for http_02x::HeaderName {
309        fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
310            Ok(self.to_string().into())
311        }
312
313        fn as_str(&self) -> Result<&str, HttpError> {
314            Ok(self.as_ref())
315        }
316
317        fn repr_as_http02x_header_name(self) -> Result<http_02x::HeaderName, Self>
318        where
319            Self: Sized,
320        {
321            Ok(self)
322        }
323    }
324}
325
326mod header_value {
327    use super::*;
328
329    /// HeaderValue type
330    ///
331    /// **Note**: Unlike `HeaderValue` in `http`, this only supports UTF-8 header values
332    #[derive(Debug, Clone)]
333    pub struct HeaderValue {
334        _private: Inner,
335    }
336
337    #[derive(Debug, Clone)]
338    enum Inner {
339        H0(http_02x::HeaderValue),
340        #[allow(dead_code)]
341        H1(http_1x::HeaderValue),
342    }
343
344    impl HeaderValue {
345        #[allow(dead_code)]
346        pub(crate) fn from_http02x(value: http_02x::HeaderValue) -> Result<Self, HttpError> {
347            let _ = std::str::from_utf8(value.as_bytes()).map_err(|err| {
348                HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
349                    value.as_bytes().to_vec(),
350                    err,
351                ))
352            })?;
353            Ok(Self {
354                _private: Inner::H0(value),
355            })
356        }
357
358        #[allow(dead_code)]
359        pub(crate) fn from_http1x(value: http_1x::HeaderValue) -> Result<Self, HttpError> {
360            let _ = std::str::from_utf8(value.as_bytes()).map_err(|err| {
361                HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
362                    value.as_bytes().to_vec(),
363                    err,
364                ))
365            })?;
366            Ok(Self {
367                _private: Inner::H1(value),
368            })
369        }
370
371        #[allow(dead_code)]
372        pub(crate) fn into_http02x(self) -> http_02x::HeaderValue {
373            match self._private {
374                Inner::H0(v) => v,
375                Inner::H1(v) => http_02x::HeaderValue::from_maybe_shared(v).expect("unreachable"),
376            }
377        }
378
379        #[allow(dead_code)]
380        pub(crate) fn into_http1x(self) -> http_1x::HeaderValue {
381            match self._private {
382                Inner::H1(v) => v,
383                Inner::H0(v) => http_1x::HeaderValue::from_maybe_shared(v).expect("unreachable"),
384            }
385        }
386    }
387
388    impl AsRef<str> for HeaderValue {
389        fn as_ref(&self) -> &str {
390            let bytes = match &self._private {
391                Inner::H0(v) => v.as_bytes(),
392                Inner::H1(v) => v.as_bytes(),
393            };
394            std::str::from_utf8(bytes).expect("unreachable—only strings may be stored")
395        }
396    }
397
398    impl From<HeaderValue> for String {
399        fn from(value: HeaderValue) -> Self {
400            value.as_ref().to_string()
401        }
402    }
403
404    impl HeaderValue {
405        /// Returns the string representation of this header value
406        pub fn as_str(&self) -> &str {
407            self.as_ref()
408        }
409    }
410
411    impl FromStr for HeaderValue {
412        type Err = HttpError;
413
414        fn from_str(s: &str) -> Result<Self, Self::Err> {
415            HeaderValue::try_from(s.to_string())
416        }
417    }
418
419    impl TryFrom<String> for HeaderValue {
420        type Error = HttpError;
421
422        fn try_from(value: String) -> Result<Self, Self::Error> {
423            Ok(HeaderValue::from_http02x(
424                http_02x::HeaderValue::try_from(value).map_err(HttpError::invalid_header_value)?,
425            )
426            .expect("input was a string"))
427        }
428    }
429}
430
431pub use header_value::HeaderValue;
432
433type MaybeStatic = Cow<'static, str>;
434
435fn header_name(
436    name: impl AsHeaderComponent,
437    panic_safe: bool,
438) -> Result<http_02x::HeaderName, HttpError> {
439    name.repr_as_http02x_header_name().or_else(|name| {
440        name.into_maybe_static().and_then(|mut cow| {
441            if cow.chars().any(|c| c.is_ascii_uppercase()) {
442                cow = Cow::Owned(cow.to_ascii_uppercase());
443            }
444            match cow {
445                Cow::Borrowed(s) if panic_safe => {
446                    http_02x::HeaderName::try_from(s).map_err(HttpError::invalid_header_name)
447                }
448                Cow::Borrowed(static_s) => Ok(http_02x::HeaderName::from_static(static_s)),
449                Cow::Owned(s) => {
450                    http_02x::HeaderName::try_from(s).map_err(HttpError::invalid_header_name)
451                }
452            }
453        })
454    })
455}
456
457fn header_value(value: MaybeStatic, panic_safe: bool) -> Result<HeaderValue, HttpError> {
458    let header = match value {
459        Cow::Borrowed(b) if panic_safe => {
460            http_02x::HeaderValue::try_from(b).map_err(HttpError::invalid_header_value)?
461        }
462        Cow::Borrowed(b) => http_02x::HeaderValue::from_static(b),
463        Cow::Owned(s) => {
464            http_02x::HeaderValue::try_from(s).map_err(HttpError::invalid_header_value)?
465        }
466    };
467    HeaderValue::from_http02x(header)
468}
469
470#[cfg(test)]
471mod tests {
472    use super::*;
473
474    #[test]
475    fn headers_can_be_any_string() {
476        let _: HeaderValue = "😹".parse().expect("can be any string");
477        let _: HeaderValue = "abcd".parse().expect("can be any string");
478        let _ = "a\nb"
479            .parse::<HeaderValue>()
480            .expect_err("cannot contain control characters");
481    }
482
483    #[test]
484    fn no_panic_insert_upper_case_header_name() {
485        let mut headers = Headers::new();
486        headers.insert("I-Have-Upper-Case", "foo");
487    }
488    #[test]
489    fn no_panic_append_upper_case_header_name() {
490        let mut headers = Headers::new();
491        headers.append("I-Have-Upper-Case", "foo");
492    }
493
494    #[test]
495    #[should_panic]
496    fn panic_insert_invalid_ascii_key() {
497        let mut headers = Headers::new();
498        headers.insert("💩", "foo");
499    }
500    #[test]
501    #[should_panic]
502    fn panic_insert_invalid_header_value() {
503        let mut headers = Headers::new();
504        headers.insert("foo", "💩");
505    }
506    #[test]
507    #[should_panic]
508    fn panic_append_invalid_ascii_key() {
509        let mut headers = Headers::new();
510        headers.append("💩", "foo");
511    }
512    #[test]
513    #[should_panic]
514    fn panic_append_invalid_header_value() {
515        let mut headers = Headers::new();
516        headers.append("foo", "💩");
517    }
518
519    #[test]
520    fn no_panic_try_insert_invalid_ascii_key() {
521        let mut headers = Headers::new();
522        assert!(headers.try_insert("💩", "foo").is_err());
523    }
524    #[test]
525    fn no_panic_try_insert_invalid_header_value() {
526        let mut headers = Headers::new();
527        assert!(headers
528            .try_insert(
529                "foo",
530                // Valid header value with invalid UTF-8
531                http_02x::HeaderValue::from_bytes(&[0xC0, 0x80]).unwrap()
532            )
533            .is_err());
534    }
535    #[test]
536    fn no_panic_try_append_invalid_ascii_key() {
537        let mut headers = Headers::new();
538        assert!(headers.try_append("💩", "foo").is_err());
539    }
540    #[test]
541    fn no_panic_try_append_invalid_header_value() {
542        let mut headers = Headers::new();
543        assert!(headers
544            .try_insert(
545                "foo",
546                // Valid header value with invalid UTF-8
547                http_02x::HeaderValue::from_bytes(&[0xC0, 0x80]).unwrap()
548            )
549            .is_err());
550    }
551
552    proptest::proptest! {
553        #[test]
554        fn insert_header_prop_test(input in ".*") {
555            let mut headers = Headers::new();
556            let _ = headers.try_insert(input.clone(), input);
557        }
558
559        #[test]
560        fn append_header_prop_test(input in ".*") {
561            let mut headers = Headers::new();
562            let _ = headers.try_append(input.clone(), input);
563        }
564    }
565}