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    #[cfg(feature = "http-1x")]
326    impl AsHeaderComponent for http_1x::HeaderName {
327        fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
328            Ok(self.to_string().into())
329        }
330
331        fn as_str(&self) -> Result<&str, HttpError> {
332            Ok(self.as_ref())
333        }
334    }
335
336    #[cfg(feature = "http-1x")]
337    impl AsHeaderComponent for http_1x::HeaderValue {
338        fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
339            Ok(Cow::Owned(
340                std::str::from_utf8(self.as_bytes())
341                    .map_err(|err| {
342                        HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
343                            self.as_bytes().to_vec(),
344                            err,
345                        ))
346                    })?
347                    .to_string(),
348            ))
349        }
350
351        fn as_str(&self) -> Result<&str, HttpError> {
352            std::str::from_utf8(self.as_bytes()).map_err(|err| {
353                HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
354                    self.as_bytes().to_vec(),
355                    err,
356                ))
357            })
358        }
359    }
360}
361
362mod header_value {
363    use super::*;
364
365    /// HeaderValue type
366    ///
367    /// **Note**: Unlike `HeaderValue` in `http`, this only supports UTF-8 header values
368    #[derive(Debug, Clone)]
369    pub struct HeaderValue {
370        _private: Inner,
371    }
372
373    #[derive(Debug, Clone)]
374    enum Inner {
375        H0(http_02x::HeaderValue),
376        #[allow(dead_code)]
377        H1(http_1x::HeaderValue),
378    }
379
380    impl HeaderValue {
381        #[allow(dead_code)]
382        pub(crate) fn from_http02x(value: http_02x::HeaderValue) -> Result<Self, HttpError> {
383            let _ = std::str::from_utf8(value.as_bytes()).map_err(|err| {
384                HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
385                    value.as_bytes().to_vec(),
386                    err,
387                ))
388            })?;
389            Ok(Self {
390                _private: Inner::H0(value),
391            })
392        }
393
394        #[allow(dead_code)]
395        pub(crate) fn from_http1x(value: http_1x::HeaderValue) -> Result<Self, HttpError> {
396            let _ = std::str::from_utf8(value.as_bytes()).map_err(|err| {
397                HttpError::non_utf8_header(NonUtf8Header::new_missing_name(
398                    value.as_bytes().to_vec(),
399                    err,
400                ))
401            })?;
402            Ok(Self {
403                _private: Inner::H1(value),
404            })
405        }
406
407        #[allow(dead_code)]
408        pub(crate) fn into_http02x(self) -> http_02x::HeaderValue {
409            match self._private {
410                Inner::H0(v) => v,
411                Inner::H1(v) => http_02x::HeaderValue::from_maybe_shared(v).expect("unreachable"),
412            }
413        }
414
415        #[allow(dead_code)]
416        pub(crate) fn into_http1x(self) -> http_1x::HeaderValue {
417            match self._private {
418                Inner::H1(v) => v,
419                Inner::H0(v) => http_1x::HeaderValue::from_maybe_shared(v).expect("unreachable"),
420            }
421        }
422    }
423
424    impl AsRef<str> for HeaderValue {
425        fn as_ref(&self) -> &str {
426            let bytes = match &self._private {
427                Inner::H0(v) => v.as_bytes(),
428                Inner::H1(v) => v.as_bytes(),
429            };
430            std::str::from_utf8(bytes).expect("unreachable—only strings may be stored")
431        }
432    }
433
434    impl From<HeaderValue> for String {
435        fn from(value: HeaderValue) -> Self {
436            value.as_ref().to_string()
437        }
438    }
439
440    impl HeaderValue {
441        /// Returns the string representation of this header value
442        pub fn as_str(&self) -> &str {
443            self.as_ref()
444        }
445    }
446
447    impl FromStr for HeaderValue {
448        type Err = HttpError;
449
450        fn from_str(s: &str) -> Result<Self, Self::Err> {
451            HeaderValue::try_from(s.to_string())
452        }
453    }
454
455    impl TryFrom<String> for HeaderValue {
456        type Error = HttpError;
457
458        fn try_from(value: String) -> Result<Self, Self::Error> {
459            Ok(HeaderValue::from_http02x(
460                http_02x::HeaderValue::try_from(value).map_err(HttpError::invalid_header_value)?,
461            )
462            .expect("input was a string"))
463        }
464    }
465}
466
467pub use header_value::HeaderValue;
468
469type MaybeStatic = Cow<'static, str>;
470
471fn header_name(
472    name: impl AsHeaderComponent,
473    panic_safe: bool,
474) -> Result<http_02x::HeaderName, HttpError> {
475    name.repr_as_http02x_header_name().or_else(|name| {
476        name.into_maybe_static().and_then(|mut cow| {
477            if cow.chars().any(|c| c.is_ascii_uppercase()) {
478                cow = Cow::Owned(cow.to_ascii_uppercase());
479            }
480            match cow {
481                Cow::Borrowed(s) if panic_safe => {
482                    http_02x::HeaderName::try_from(s).map_err(HttpError::invalid_header_name)
483                }
484                Cow::Borrowed(static_s) => Ok(http_02x::HeaderName::from_static(static_s)),
485                Cow::Owned(s) => {
486                    http_02x::HeaderName::try_from(s).map_err(HttpError::invalid_header_name)
487                }
488            }
489        })
490    })
491}
492
493fn header_value(value: MaybeStatic, panic_safe: bool) -> Result<HeaderValue, HttpError> {
494    let header = match value {
495        Cow::Borrowed(b) if panic_safe => {
496            http_02x::HeaderValue::try_from(b).map_err(HttpError::invalid_header_value)?
497        }
498        Cow::Borrowed(b) => http_02x::HeaderValue::from_static(b),
499        Cow::Owned(s) => {
500            http_02x::HeaderValue::try_from(s).map_err(HttpError::invalid_header_value)?
501        }
502    };
503    HeaderValue::from_http02x(header)
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509
510    #[test]
511    fn headers_can_be_any_string() {
512        let _: HeaderValue = "😹".parse().expect("can be any string");
513        let _: HeaderValue = "abcd".parse().expect("can be any string");
514        let _ = "a\nb"
515            .parse::<HeaderValue>()
516            .expect_err("cannot contain control characters");
517    }
518
519    #[test]
520    fn no_panic_insert_upper_case_header_name() {
521        let mut headers = Headers::new();
522        headers.insert("I-Have-Upper-Case", "foo");
523    }
524    #[test]
525    fn no_panic_append_upper_case_header_name() {
526        let mut headers = Headers::new();
527        headers.append("I-Have-Upper-Case", "foo");
528    }
529
530    #[test]
531    #[should_panic]
532    fn panic_insert_invalid_ascii_key() {
533        let mut headers = Headers::new();
534        headers.insert("💩", "foo");
535    }
536    #[test]
537    #[should_panic]
538    fn panic_insert_invalid_header_value() {
539        let mut headers = Headers::new();
540        headers.insert("foo", "💩");
541    }
542    #[test]
543    #[should_panic]
544    fn panic_append_invalid_ascii_key() {
545        let mut headers = Headers::new();
546        headers.append("💩", "foo");
547    }
548    #[test]
549    #[should_panic]
550    fn panic_append_invalid_header_value() {
551        let mut headers = Headers::new();
552        headers.append("foo", "💩");
553    }
554
555    #[test]
556    fn no_panic_try_insert_invalid_ascii_key() {
557        let mut headers = Headers::new();
558        assert!(headers.try_insert("💩", "foo").is_err());
559    }
560    #[test]
561    fn no_panic_try_insert_invalid_header_value() {
562        let mut headers = Headers::new();
563        assert!(headers
564            .try_insert(
565                "foo",
566                // Valid header value with invalid UTF-8
567                http_02x::HeaderValue::from_bytes(&[0xC0, 0x80]).unwrap()
568            )
569            .is_err());
570    }
571    #[test]
572    fn no_panic_try_append_invalid_ascii_key() {
573        let mut headers = Headers::new();
574        assert!(headers.try_append("💩", "foo").is_err());
575    }
576    #[test]
577    fn no_panic_try_append_invalid_header_value() {
578        let mut headers = Headers::new();
579        assert!(headers
580            .try_insert(
581                "foo",
582                // Valid header value with invalid UTF-8
583                http_02x::HeaderValue::from_bytes(&[0xC0, 0x80]).unwrap()
584            )
585            .is_err());
586    }
587
588    proptest::proptest! {
589        #[test]
590        fn insert_header_prop_test(input in ".*") {
591            let mut headers = Headers::new();
592            let _ = headers.try_insert(input.clone(), input);
593        }
594
595        #[test]
596        fn append_header_prop_test(input in ".*") {
597            let mut headers = Headers::new();
598            let _ = headers.try_append(input.clone(), input);
599        }
600    }
601}