Skip to main content

aws_smithy_http/
header.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Utilities for parsing information from headers
7
8use http_1x::header::{HeaderMap, HeaderName, HeaderValue};
9
10// The parsing primitives live in `aws-smithy-runtime-api` so that `aws-smithy-schema` can
11// share one implementation without taking on this crate's dependency tree. These are exact
12// re-exports, not wrappers: `ParseError` keeps a single type identity, so downstream
13// signatures, `?` conversions, `Error::source()` chains, and `downcast_ref::<ParseError>()`
14// are unaffected, and generated code that names `aws_smithy_http::header::*` keeps working.
15#[doc(inline)]
16pub use aws_smithy_runtime_api::http::header_parse::{
17    headers_for_prefix, many_dates, many_dates_bytes, one_or_none, one_or_none_bytes,
18    quote_header_value, read_many_from_str, read_many_from_str_bytes, read_many_primitive,
19    read_many_primitive_bytes,
20};
21#[doc(inline)]
22pub use aws_smithy_runtime_api::http::ParseError;
23
24/// Given an HTTP request, set a request header if that header was not already set.
25pub fn set_request_header_if_absent<V>(
26    request: http_1x::request::Builder,
27    key: HeaderName,
28    value: V,
29) -> http_1x::request::Builder
30where
31    HeaderValue: TryFrom<V>,
32    <HeaderValue as TryFrom<V>>::Error: Into<http_1x::Error>,
33{
34    if !request
35        .headers_ref()
36        .map(|map| map.contains_key(&key))
37        .unwrap_or(false)
38    {
39        request.header(key, value)
40    } else {
41        request
42    }
43}
44
45/// Given an HTTP response, set a response header if that header was not already set.
46pub fn set_response_header_if_absent<V>(
47    response: http_1x::response::Builder,
48    key: HeaderName,
49    value: V,
50) -> http_1x::response::Builder
51where
52    HeaderValue: TryFrom<V>,
53    <HeaderValue as TryFrom<V>>::Error: Into<http_1x::Error>,
54{
55    if !response
56        .headers_ref()
57        .map(|map| map.contains_key(&key))
58        .unwrap_or(false)
59    {
60        response.header(key, value)
61    } else {
62        response
63    }
64}
65
66/// Given two http-02x [`HeaderMap`]s, merge them together and return the merged `HeaderMap`. If the
67/// two `HeaderMap`s share any keys, values from the right `HeaderMap` be appended to the left `HeaderMap`.
68pub fn append_merge_header_maps(
69    mut lhs: HeaderMap<HeaderValue>,
70    rhs: HeaderMap<HeaderValue>,
71) -> HeaderMap<HeaderValue> {
72    let mut last_header_name_seen = None;
73    for (header_name, header_value) in rhs.into_iter() {
74        // For each yielded item that has None provided for the `HeaderName`,
75        // then the associated header name is the same as that of the previously
76        // yielded item. The first yielded item will have `HeaderName` set.
77        // https://docs.rs/http/latest/http/header/struct.HeaderMap.html#method.into_iter-2
78        match (&mut last_header_name_seen, header_name) {
79            (_, Some(header_name)) => {
80                lhs.append(header_name.clone(), header_value);
81                last_header_name_seen = Some(header_name);
82            }
83            (Some(header_name), None) => {
84                lhs.append(header_name.clone(), header_value);
85            }
86            (None, None) => unreachable!(),
87        };
88    }
89
90    lhs
91}
92
93/// Given two http-1x [`HeaderMap`]s, merge them together and return the merged `HeaderMap`. If the
94/// two `HeaderMap`s share any keys, values from the right `HeaderMap` be appended to the left `HeaderMap`.
95pub fn append_merge_header_maps_http_1x(
96    mut lhs: http_1x::HeaderMap<http_1x::HeaderValue>,
97    rhs: http_1x::HeaderMap<http_1x::HeaderValue>,
98) -> http_1x::HeaderMap<http_1x::HeaderValue> {
99    let mut last_header_name_seen = None;
100    for (header_name, header_value) in rhs.into_iter() {
101        // For each yielded item that has None provided for the `HeaderName`,
102        // then the associated header name is the same as that of the previously
103        // yielded item. The first yielded item will have `HeaderName` set.
104        // https://docs.rs/http/latest/http/header/struct.HeaderMap.html#method.into_iter-2
105        match (&mut last_header_name_seen, header_name) {
106            (_, Some(header_name)) => {
107                lhs.append(header_name.clone(), header_value);
108                last_header_name_seen = Some(header_name);
109            }
110            (Some(header_name), None) => {
111                lhs.append(header_name.clone(), header_value);
112            }
113            (None, None) => unreachable!(),
114        };
115    }
116
117    lhs
118}
119
120#[cfg(test)]
121mod test {
122    use super::quote_header_value;
123    use crate::header::{
124        append_merge_header_maps, headers_for_prefix, many_dates, many_dates_bytes, one_or_none,
125        one_or_none_bytes, read_many_from_str, read_many_from_str_bytes, read_many_primitive,
126        read_many_primitive_bytes, set_request_header_if_absent, set_response_header_if_absent,
127        ParseError,
128    };
129    use aws_smithy_runtime_api::http::Request;
130    use aws_smithy_types::error::display::DisplayErrorContext;
131    use aws_smithy_types::{date_time::Format, DateTime};
132    use http_1x::header::{HeaderMap, HeaderName, HeaderValue};
133    use std::collections::HashMap;
134
135    #[test]
136    fn put_on_request_if_absent() {
137        let builder = http_1x::Request::builder().header("foo", "bar");
138        let builder = set_request_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
139        let builder =
140            set_request_header_if_absent(builder, HeaderName::from_static("other"), "value");
141        let req = builder.body(()).expect("valid request");
142        assert_eq!(
143            req.headers().get_all("foo").iter().collect::<Vec<_>>(),
144            vec!["bar"]
145        );
146        assert_eq!(
147            req.headers().get_all("other").iter().collect::<Vec<_>>(),
148            vec!["value"]
149        );
150    }
151
152    #[test]
153    fn put_on_response_if_absent() {
154        let builder = http_1x::Response::builder().header("foo", "bar");
155        let builder = set_response_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
156        let builder =
157            set_response_header_if_absent(builder, HeaderName::from_static("other"), "value");
158        let response = builder.body(()).expect("valid response");
159        assert_eq!(
160            response.headers().get_all("foo").iter().collect::<Vec<_>>(),
161            vec!["bar"]
162        );
163        assert_eq!(
164            response
165                .headers()
166                .get_all("other")
167                .iter()
168                .collect::<Vec<_>>(),
169            vec!["value"]
170        );
171    }
172
173    #[test]
174    fn parse_floats() {
175        let test_request = http_1x::Request::builder()
176            .header("X-Float-Multi", "0.0,Infinity,-Infinity,5555.5")
177            .header("X-Float-Error", "notafloat")
178            .body(())
179            .unwrap();
180        assert_eq!(
181            read_many_primitive::<f32>(
182                test_request
183                    .headers()
184                    .get_all("X-Float-Multi")
185                    .iter()
186                    .map(|v| v.to_str().unwrap())
187            )
188            .expect("valid"),
189            vec![0.0, f32::INFINITY, f32::NEG_INFINITY, 5555.5]
190        );
191        let message = format!(
192            "{}",
193            DisplayErrorContext(
194                read_many_primitive::<f32>(
195                    test_request
196                        .headers()
197                        .get_all("X-Float-Error")
198                        .iter()
199                        .map(|v| v.to_str().unwrap())
200                )
201                .expect_err("invalid")
202            )
203        );
204        let expected = "output failed to parse in headers: failed reading a list of primitives: failed to parse input as f32";
205        assert!(
206            message.starts_with(expected),
207            "expected '{message}' to start with '{expected}'"
208        );
209    }
210
211    #[test]
212    fn test_many_dates() {
213        let test_request = http_1x::Request::builder()
214            .header("Empty", "")
215            .header("SingleHttpDate", "Wed, 21 Oct 2015 07:28:00 GMT")
216            .header(
217                "MultipleHttpDates",
218                "Wed, 21 Oct 2015 07:28:00 GMT,Thu, 22 Oct 2015 07:28:00 GMT",
219            )
220            .header("SingleEpochSeconds", "1234.5678")
221            .header("MultipleEpochSeconds", "1234.5678,9012.3456")
222            .body(())
223            .unwrap();
224        let read = |name: &str, format: Format| {
225            many_dates(
226                test_request
227                    .headers()
228                    .get_all(name)
229                    .iter()
230                    .map(|v| v.to_str().unwrap()),
231                format,
232            )
233        };
234        let read_valid = |name: &str, format: Format| read(name, format).expect("valid");
235        assert_eq!(
236            read_valid("Empty", Format::DateTime),
237            Vec::<DateTime>::new()
238        );
239        assert_eq!(
240            read_valid("SingleHttpDate", Format::HttpDate),
241            vec![DateTime::from_secs_and_nanos(1445412480, 0)]
242        );
243        assert_eq!(
244            read_valid("MultipleHttpDates", Format::HttpDate),
245            vec![
246                DateTime::from_secs_and_nanos(1445412480, 0),
247                DateTime::from_secs_and_nanos(1445498880, 0)
248            ]
249        );
250        assert_eq!(
251            read_valid("SingleEpochSeconds", Format::EpochSeconds),
252            vec![DateTime::from_secs_and_nanos(1234, 567_800_000)]
253        );
254        assert_eq!(
255            read_valid("MultipleEpochSeconds", Format::EpochSeconds),
256            vec![
257                DateTime::from_secs_and_nanos(1234, 567_800_000),
258                DateTime::from_secs_and_nanos(9012, 345_600_000)
259            ]
260        );
261    }
262
263    // A lone 0xE9 is a valid HTTP header octet (obs-text per RFC 7230) but is not valid UTF-8.
264    const NON_UTF8_VALUE: &[u8] = b"value-\xe9";
265
266    #[test]
267    fn bytes_helpers_agree_with_str_helpers_on_valid_utf8() {
268        assert_eq!(
269            one_or_none::<String>(["  foo  "].into_iter()).unwrap(),
270            one_or_none_bytes::<String>([b"  foo  ".as_slice()].into_iter()).unwrap(),
271        );
272        assert_eq!(
273            read_many_from_str::<String>(["\"foo,bar\",baz"].into_iter()).unwrap(),
274            read_many_from_str_bytes::<String>([b"\"foo,bar\",baz".as_slice()].into_iter())
275                .unwrap(),
276        );
277        assert_eq!(
278            read_many_primitive::<i16>(["1,2", "3"].into_iter()).unwrap(),
279            read_many_primitive_bytes::<i16>([b"1,2".as_slice(), b"3".as_slice()].into_iter())
280                .unwrap(),
281        );
282        assert_eq!(
283            many_dates(
284                ["Mon, 16 Dec 2019 23:48:18 GMT"].into_iter(),
285                Format::HttpDate
286            )
287            .unwrap(),
288            many_dates_bytes(
289                [b"Mon, 16 Dec 2019 23:48:18 GMT".as_slice()].into_iter(),
290                Format::HttpDate
291            )
292            .unwrap(),
293        );
294    }
295
296    #[test]
297    fn bytes_helpers_reject_non_utf8() {
298        let expected = "header was not valid utf-8";
299
300        let err = one_or_none_bytes::<String>([NON_UTF8_VALUE].into_iter()).expect_err("non-utf8");
301        assert!(err.to_string().contains(expected), "{err}");
302
303        let err =
304            read_many_from_str_bytes::<String>([NON_UTF8_VALUE].into_iter()).expect_err("non-utf8");
305        assert!(err.to_string().contains(expected), "{err}");
306
307        let err =
308            read_many_primitive_bytes::<i16>([NON_UTF8_VALUE].into_iter()).expect_err("non-utf8");
309        assert!(err.to_string().contains(expected), "{err}");
310
311        let err =
312            many_dates_bytes([NON_UTF8_VALUE].into_iter(), Format::HttpDate).expect_err("non-utf8");
313        assert!(err.to_string().contains(expected), "{err}");
314    }
315
316    #[test]
317    fn one_or_none_bytes_reports_multiple_before_non_utf8() {
318        // `one_or_none` checks for multiple values before inspecting the first one; the bytes
319        // variant must keep that precedence.
320        let err = one_or_none_bytes::<String>([NON_UTF8_VALUE, b"second".as_slice()].into_iter())
321            .expect_err("multiple values");
322        assert!(
323            err.to_string()
324                .contains("expected a single value but found multiple"),
325            "{err}"
326        );
327    }
328
329    #[test]
330    fn read_many_strings() {
331        let test_request = http_1x::Request::builder()
332            .header("Empty", "")
333            .header("Foo", "  foo")
334            .header("FooTrailing", "foo   ")
335            .header("FooInQuotes", "\"  foo  \"")
336            .header("CommaInQuotes", "\"foo,bar\",baz")
337            .header("CommaInQuotesTrailing", "\"foo,bar\",baz  ")
338            .header("QuoteInQuotes", "\"foo\\\",bar\",\"\\\"asdf\\\"\",baz")
339            .header(
340                "QuoteInQuotesWithSpaces",
341                "\"foo\\\",bar\", \"\\\"asdf\\\"\", baz",
342            )
343            .header("JunkFollowingQuotes", "\"\\\"asdf\\\"\"baz")
344            .header("EmptyQuotes", "\"\",baz")
345            .header("EscapedSlashesInQuotes", "foo, \"(foo\\\\bar)\"")
346            .body(())
347            .unwrap();
348        let read = |name: &str| {
349            read_many_from_str::<String>(
350                test_request
351                    .headers()
352                    .get_all(name)
353                    .iter()
354                    .map(|v| v.to_str().unwrap()),
355            )
356        };
357        let read_valid = |name: &str| read(name).expect("valid");
358        assert_eq!(read_valid("Empty"), Vec::<String>::new());
359        assert_eq!(read_valid("Foo"), vec!["foo"]);
360        assert_eq!(read_valid("FooTrailing"), vec!["foo"]);
361        assert_eq!(read_valid("FooInQuotes"), vec!["  foo  "]);
362        assert_eq!(read_valid("CommaInQuotes"), vec!["foo,bar", "baz"]);
363        assert_eq!(read_valid("CommaInQuotesTrailing"), vec!["foo,bar", "baz"]);
364        assert_eq!(
365            read_valid("QuoteInQuotes"),
366            vec!["foo\",bar", "\"asdf\"", "baz"]
367        );
368        assert_eq!(
369            read_valid("QuoteInQuotesWithSpaces"),
370            vec!["foo\",bar", "\"asdf\"", "baz"]
371        );
372        assert!(read("JunkFollowingQuotes").is_err());
373        assert_eq!(read_valid("EmptyQuotes"), vec!["", "baz"]);
374        assert_eq!(
375            read_valid("EscapedSlashesInQuotes"),
376            vec!["foo", "(foo\\bar)"]
377        );
378    }
379
380    #[test]
381    fn read_many_bools() {
382        let test_request = http_1x::Request::builder()
383            .header("X-Bool-Multi", "true,false")
384            .header("X-Bool-Multi", "true")
385            .header("X-Bool", "true")
386            .header("X-Bool-Invalid", "truth,falsy")
387            .header("X-Bool-Single", "true,false,true,true")
388            .header("X-Bool-Quoted", "true,\"false\",true,true")
389            .body(())
390            .unwrap();
391        assert_eq!(
392            read_many_primitive::<bool>(
393                test_request
394                    .headers()
395                    .get_all("X-Bool-Multi")
396                    .iter()
397                    .map(|v| v.to_str().unwrap())
398            )
399            .expect("valid"),
400            vec![true, false, true]
401        );
402
403        assert_eq!(
404            read_many_primitive::<bool>(
405                test_request
406                    .headers()
407                    .get_all("X-Bool")
408                    .iter()
409                    .map(|v| v.to_str().unwrap())
410            )
411            .unwrap(),
412            vec![true]
413        );
414        assert_eq!(
415            read_many_primitive::<bool>(
416                test_request
417                    .headers()
418                    .get_all("X-Bool-Single")
419                    .iter()
420                    .map(|v| v.to_str().unwrap())
421            )
422            .unwrap(),
423            vec![true, false, true, true]
424        );
425        assert_eq!(
426            read_many_primitive::<bool>(
427                test_request
428                    .headers()
429                    .get_all("X-Bool-Quoted")
430                    .iter()
431                    .map(|v| v.to_str().unwrap())
432            )
433            .unwrap(),
434            vec![true, false, true, true]
435        );
436        read_many_primitive::<bool>(
437            test_request
438                .headers()
439                .get_all("X-Bool-Invalid")
440                .iter()
441                .map(|v| v.to_str().unwrap()),
442        )
443        .expect_err("invalid");
444    }
445
446    #[test]
447    fn check_read_many_i16() {
448        let test_request = http_1x::Request::builder()
449            .header("X-Multi", "123,456")
450            .header("X-Multi", "789")
451            .header("X-Num", "777")
452            .header("X-Num-Invalid", "12ef3")
453            .header("X-Num-Single", "1,2,3,-4,5")
454            .header("X-Num-Quoted", "1, \"2\",3,\"-4\",5")
455            .body(())
456            .unwrap();
457        assert_eq!(
458            read_many_primitive::<i16>(
459                test_request
460                    .headers()
461                    .get_all("X-Multi")
462                    .iter()
463                    .map(|v| v.to_str().unwrap())
464            )
465            .expect("valid"),
466            vec![123, 456, 789]
467        );
468
469        assert_eq!(
470            read_many_primitive::<i16>(
471                test_request
472                    .headers()
473                    .get_all("X-Num")
474                    .iter()
475                    .map(|v| v.to_str().unwrap())
476            )
477            .unwrap(),
478            vec![777]
479        );
480        assert_eq!(
481            read_many_primitive::<i16>(
482                test_request
483                    .headers()
484                    .get_all("X-Num-Single")
485                    .iter()
486                    .map(|v| v.to_str().unwrap())
487            )
488            .unwrap(),
489            vec![1, 2, 3, -4, 5]
490        );
491        assert_eq!(
492            read_many_primitive::<i16>(
493                test_request
494                    .headers()
495                    .get_all("X-Num-Quoted")
496                    .iter()
497                    .map(|v| v.to_str().unwrap())
498            )
499            .unwrap(),
500            vec![1, 2, 3, -4, 5]
501        );
502        read_many_primitive::<i16>(
503            test_request
504                .headers()
505                .get_all("X-Num-Invalid")
506                .iter()
507                .map(|v| v.to_str().unwrap()),
508        )
509        .expect_err("invalid");
510    }
511
512    #[test]
513    fn test_prefix_headers() {
514        let test_request = Request::try_from(
515            http_1x::Request::builder()
516                .header("X-Prefix-A", "123,456")
517                .header("X-Prefix-B", "789")
518                .header("X-Prefix-C", "777")
519                .header("X-Prefix-C", "777")
520                .body(())
521                .unwrap(),
522        )
523        .unwrap();
524        let resp: Result<HashMap<String, Vec<i16>>, ParseError> =
525            headers_for_prefix(test_request.headers().iter().map(|h| h.0), "X-Prefix-")
526                .map(|(key, header_name)| {
527                    let values = test_request.headers().get_all(header_name);
528                    read_many_primitive(values).map(|v| (key.to_string(), v))
529                })
530                .collect();
531        let resp = resp.expect("valid");
532        assert_eq!(resp.get("a"), Some(&vec![123_i16, 456_i16]));
533    }
534
535    #[test]
536    fn test_quote_header_value() {
537        assert_eq!("", &quote_header_value(""));
538        assert_eq!("foo", &quote_header_value("foo"));
539        assert_eq!("\"  foo\"", &quote_header_value("  foo"));
540        assert_eq!("foo bar", &quote_header_value("foo bar"));
541        assert_eq!("\"foo,bar\"", &quote_header_value("foo,bar"));
542        assert_eq!("\",\"", &quote_header_value(","));
543        assert_eq!("\"\\\"foo\\\"\"", &quote_header_value("\"foo\""));
544        assert_eq!("\"\\\"f\\\\oo\\\"\"", &quote_header_value("\"f\\oo\""));
545        assert_eq!("\"(\"", &quote_header_value("("));
546        assert_eq!("\")\"", &quote_header_value(")"));
547    }
548
549    #[test]
550    fn test_append_merge_header_maps_with_shared_key() {
551        let header_name = HeaderName::from_static("some_key");
552        let left_header_value = HeaderValue::from_static("lhs value");
553        let right_header_value = HeaderValue::from_static("rhs value");
554
555        let mut left_hand_side_headers = HeaderMap::new();
556        left_hand_side_headers.insert(header_name.clone(), left_header_value.clone());
557
558        let mut right_hand_side_headers = HeaderMap::new();
559        right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
560
561        let merged_header_map =
562            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
563        let actual_merged_values: Vec<_> =
564            merged_header_map.get_all(header_name).into_iter().collect();
565
566        let expected_merged_values = vec![left_header_value, right_header_value];
567
568        assert_eq!(actual_merged_values, expected_merged_values);
569    }
570
571    #[test]
572    fn test_append_merge_header_maps_with_multiple_values_in_left_hand_map() {
573        let header_name = HeaderName::from_static("some_key");
574        let left_header_value_1 = HeaderValue::from_static("lhs value 1");
575        let left_header_value_2 = HeaderValue::from_static("lhs_value 2");
576        let right_header_value = HeaderValue::from_static("rhs value");
577
578        let mut left_hand_side_headers = HeaderMap::new();
579        left_hand_side_headers.insert(header_name.clone(), left_header_value_1.clone());
580        left_hand_side_headers.append(header_name.clone(), left_header_value_2.clone());
581
582        let mut right_hand_side_headers = HeaderMap::new();
583        right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
584
585        let merged_header_map =
586            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
587        let actual_merged_values: Vec<_> =
588            merged_header_map.get_all(header_name).into_iter().collect();
589
590        let expected_merged_values =
591            vec![left_header_value_1, left_header_value_2, right_header_value];
592
593        assert_eq!(actual_merged_values, expected_merged_values);
594    }
595
596    #[test]
597    fn test_append_merge_header_maps_with_empty_left_hand_map() {
598        let header_name = HeaderName::from_static("some_key");
599        let right_header_value_1 = HeaderValue::from_static("rhs value 1");
600        let right_header_value_2 = HeaderValue::from_static("rhs_value 2");
601
602        let left_hand_side_headers = HeaderMap::new();
603
604        let mut right_hand_side_headers = HeaderMap::new();
605        right_hand_side_headers.insert(header_name.clone(), right_header_value_1.clone());
606        right_hand_side_headers.append(header_name.clone(), right_header_value_2.clone());
607
608        let merged_header_map =
609            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
610        let actual_merged_values: Vec<_> =
611            merged_header_map.get_all(header_name).into_iter().collect();
612
613        let expected_merged_values = vec![right_header_value_1, right_header_value_2];
614
615        assert_eq!(actual_merged_values, expected_merged_values);
616    }
617}