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 aws_smithy_types::date_time::Format;
9use aws_smithy_types::primitive::Parse;
10use aws_smithy_types::DateTime;
11use http_1x::header::{HeaderMap, HeaderName, HeaderValue};
12use std::borrow::Cow;
13use std::error::Error;
14use std::fmt;
15use std::str::FromStr;
16
17/// An error was encountered while parsing a header
18#[derive(Debug)]
19pub struct ParseError {
20    message: Cow<'static, str>,
21    source: Option<Box<dyn Error + Send + Sync + 'static>>,
22}
23
24impl ParseError {
25    /// Create a new parse error with the given `message`
26    pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
27        Self {
28            message: message.into(),
29            source: None,
30        }
31    }
32
33    /// Attach a source to this error.
34    pub fn with_source(self, source: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
35        Self {
36            source: Some(source.into()),
37            ..self
38        }
39    }
40}
41
42impl fmt::Display for ParseError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        write!(f, "output failed to parse in headers: {}", self.message)
45    }
46}
47
48impl Error for ParseError {
49    fn source(&self) -> Option<&(dyn Error + 'static)> {
50        self.source.as_ref().map(|err| err.as_ref() as _)
51    }
52}
53
54/// Read all the dates from the header map at `key` according the `format`
55///
56/// This is separate from `read_many` below because we need to invoke `DateTime::read` to take advantage
57/// of comma-aware parsing
58pub fn many_dates<'a>(
59    values: impl Iterator<Item = &'a str>,
60    format: Format,
61) -> Result<Vec<DateTime>, ParseError> {
62    let mut out = vec![];
63    for header in values {
64        let mut header = header;
65        while !header.is_empty() {
66            let (v, next) = DateTime::read(header, format, ',').map_err(|err| {
67                ParseError::new(format!("header could not be parsed as date: {}", err))
68            })?;
69            out.push(v);
70            header = next;
71        }
72    }
73    Ok(out)
74}
75
76/// Returns an iterator over pairs where the first element is the unprefixed header name that
77/// starts with the input `key` prefix, and the second element is the full header name.
78pub fn headers_for_prefix<'a>(
79    header_names: impl Iterator<Item = &'a str>,
80    key: &'a str,
81) -> impl Iterator<Item = (&'a str, &'a str)> {
82    let lower_key = key.to_ascii_lowercase();
83    header_names
84        .filter(move |k| k.starts_with(&lower_key))
85        .map(move |k| (&k[key.len()..], k))
86}
87
88/// Convert a `HeaderValue` into a `Vec<T>` where `T: FromStr`
89pub fn read_many_from_str<'a, T: FromStr>(
90    values: impl Iterator<Item = &'a str>,
91) -> Result<Vec<T>, ParseError>
92where
93    T::Err: Error + Send + Sync + 'static,
94{
95    read_many(values, |v: &str| {
96        v.parse().map_err(|err| {
97            ParseError::new("failed during `FromString` conversion").with_source(err)
98        })
99    })
100}
101
102/// Convert a `HeaderValue` into a `Vec<T>` where `T: Parse`
103pub fn read_many_primitive<'a, T: Parse>(
104    values: impl Iterator<Item = &'a str>,
105) -> Result<Vec<T>, ParseError> {
106    read_many(values, |v: &str| {
107        T::parse_smithy_primitive(v)
108            .map_err(|err| ParseError::new("failed reading a list of primitives").with_source(err))
109    })
110}
111
112/// Read many comma / header delimited values from HTTP headers for `FromStr` types
113fn read_many<'a, T>(
114    values: impl Iterator<Item = &'a str>,
115    f: impl Fn(&str) -> Result<T, ParseError>,
116) -> Result<Vec<T>, ParseError> {
117    let mut out = vec![];
118    for header in values {
119        let mut header = header.as_bytes();
120        while !header.is_empty() {
121            let (v, next) = read_one(header, &f)?;
122            out.push(v);
123            header = next;
124        }
125    }
126    Ok(out)
127}
128
129/// Read exactly one or none from a headers iterator
130///
131/// This function does not perform comma splitting like `read_many`
132pub fn one_or_none<'a, T: FromStr>(
133    mut values: impl Iterator<Item = &'a str>,
134) -> Result<Option<T>, ParseError>
135where
136    T::Err: Error + Send + Sync + 'static,
137{
138    let first = match values.next() {
139        Some(v) => v,
140        None => return Ok(None),
141    };
142    match values.next() {
143        None => T::from_str(first.trim())
144            .map_err(|err| ParseError::new("failed to parse string").with_source(err))
145            .map(Some),
146        Some(_) => Err(ParseError::new(
147            "expected a single value but found multiple",
148        )),
149    }
150}
151
152/// Given an HTTP request, set a request header if that header was not already set.
153pub fn set_request_header_if_absent<V>(
154    request: http_1x::request::Builder,
155    key: HeaderName,
156    value: V,
157) -> http_1x::request::Builder
158where
159    HeaderValue: TryFrom<V>,
160    <HeaderValue as TryFrom<V>>::Error: Into<http_1x::Error>,
161{
162    if !request
163        .headers_ref()
164        .map(|map| map.contains_key(&key))
165        .unwrap_or(false)
166    {
167        request.header(key, value)
168    } else {
169        request
170    }
171}
172
173/// Given an HTTP response, set a response header if that header was not already set.
174pub fn set_response_header_if_absent<V>(
175    response: http_1x::response::Builder,
176    key: HeaderName,
177    value: V,
178) -> http_1x::response::Builder
179where
180    HeaderValue: TryFrom<V>,
181    <HeaderValue as TryFrom<V>>::Error: Into<http_1x::Error>,
182{
183    if !response
184        .headers_ref()
185        .map(|map| map.contains_key(&key))
186        .unwrap_or(false)
187    {
188        response.header(key, value)
189    } else {
190        response
191    }
192}
193
194/// Functions for parsing multiple comma-delimited header values out of a
195/// single header. This parsing adheres to
196/// [RFC-7230's specification of header values](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6).
197mod parse_multi_header {
198    use super::ParseError;
199    use std::borrow::Cow;
200
201    fn trim(s: Cow<'_, str>) -> Cow<'_, str> {
202        match s {
203            Cow::Owned(s) => Cow::Owned(s.trim().into()),
204            Cow::Borrowed(s) => Cow::Borrowed(s.trim()),
205        }
206    }
207
208    fn replace<'a>(value: Cow<'a, str>, pattern: &str, replacement: &str) -> Cow<'a, str> {
209        if value.contains(pattern) {
210            Cow::Owned(value.replace(pattern, replacement))
211        } else {
212            value
213        }
214    }
215
216    /// Reads a single value out of the given input, and returns a tuple containing
217    /// the parsed value and the remainder of the slice that can be used to parse
218    /// more values.
219    pub(crate) fn read_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
220        for (index, &byte) in input.iter().enumerate() {
221            let current_slice = &input[index..];
222            match byte {
223                b' ' | b'\t' => { /* skip whitespace */ }
224                b'"' => return read_quoted_value(&current_slice[1..]),
225                _ => {
226                    let (value, rest) = read_unquoted_value(current_slice)?;
227                    return Ok((trim(value), rest));
228                }
229            }
230        }
231
232        // We only end up here if the entire header value was whitespace or empty
233        Ok((Cow::Borrowed(""), &[]))
234    }
235
236    fn read_unquoted_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
237        let next_delim = input.iter().position(|&b| b == b',').unwrap_or(input.len());
238        let (first, next) = input.split_at(next_delim);
239        let first = std::str::from_utf8(first)
240            .map_err(|_| ParseError::new("header was not valid utf-8"))?;
241        Ok((Cow::Borrowed(first), then_comma(next).unwrap()))
242    }
243
244    /// Reads a header value that is surrounded by quotation marks and may have escaped
245    /// quotes inside of it.
246    fn read_quoted_value(input: &[u8]) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
247        for index in 0..input.len() {
248            match input[index] {
249                b'"' if index == 0 || input[index - 1] != b'\\' => {
250                    let mut inner = Cow::Borrowed(
251                        std::str::from_utf8(&input[0..index])
252                            .map_err(|_| ParseError::new("header was not valid utf-8"))?,
253                    );
254                    inner = replace(inner, "\\\"", "\"");
255                    inner = replace(inner, "\\\\", "\\");
256                    let rest = then_comma(&input[(index + 1)..])?;
257                    return Ok((inner, rest));
258                }
259                _ => {}
260            }
261        }
262        Err(ParseError::new(
263            "header value had quoted value without end quote",
264        ))
265    }
266
267    fn then_comma(s: &[u8]) -> Result<&[u8], ParseError> {
268        if s.is_empty() {
269            Ok(s)
270        } else if s.starts_with(b",") {
271            Ok(&s[1..])
272        } else {
273            Err(ParseError::new("expected delimiter `,`"))
274        }
275    }
276}
277
278/// Read one comma delimited value for `FromStr` types
279fn read_one<'a, T>(
280    s: &'a [u8],
281    f: &impl Fn(&str) -> Result<T, ParseError>,
282) -> Result<(T, &'a [u8]), ParseError> {
283    let (value, rest) = parse_multi_header::read_value(s)?;
284    Ok((f(&value)?, rest))
285}
286
287/// Conditionally quotes and escapes a header value if the header value contains a comma or quote.
288pub fn quote_header_value<'a>(value: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
289    let value = value.into();
290    if value.trim().len() != value.len()
291        || value.contains('"')
292        || value.contains(',')
293        || value.contains('(')
294        || value.contains(')')
295    {
296        Cow::Owned(format!(
297            "\"{}\"",
298            value.replace('\\', "\\\\").replace('"', "\\\"")
299        ))
300    } else {
301        value
302    }
303}
304
305/// Given two http-02x [`HeaderMap`]s, merge them together and return the merged `HeaderMap`. If the
306/// two `HeaderMap`s share any keys, values from the right `HeaderMap` be appended to the left `HeaderMap`.
307pub fn append_merge_header_maps(
308    mut lhs: HeaderMap<HeaderValue>,
309    rhs: HeaderMap<HeaderValue>,
310) -> HeaderMap<HeaderValue> {
311    let mut last_header_name_seen = None;
312    for (header_name, header_value) in rhs.into_iter() {
313        // For each yielded item that has None provided for the `HeaderName`,
314        // then the associated header name is the same as that of the previously
315        // yielded item. The first yielded item will have `HeaderName` set.
316        // https://docs.rs/http/latest/http/header/struct.HeaderMap.html#method.into_iter-2
317        match (&mut last_header_name_seen, header_name) {
318            (_, Some(header_name)) => {
319                lhs.append(header_name.clone(), header_value);
320                last_header_name_seen = Some(header_name);
321            }
322            (Some(header_name), None) => {
323                lhs.append(header_name.clone(), header_value);
324            }
325            (None, None) => unreachable!(),
326        };
327    }
328
329    lhs
330}
331
332/// Given two http-1x [`HeaderMap`]s, merge them together and return the merged `HeaderMap`. If the
333/// two `HeaderMap`s share any keys, values from the right `HeaderMap` be appended to the left `HeaderMap`.
334pub fn append_merge_header_maps_http_1x(
335    mut lhs: http_1x::HeaderMap<http_1x::HeaderValue>,
336    rhs: http_1x::HeaderMap<http_1x::HeaderValue>,
337) -> http_1x::HeaderMap<http_1x::HeaderValue> {
338    let mut last_header_name_seen = None;
339    for (header_name, header_value) in rhs.into_iter() {
340        // For each yielded item that has None provided for the `HeaderName`,
341        // then the associated header name is the same as that of the previously
342        // yielded item. The first yielded item will have `HeaderName` set.
343        // https://docs.rs/http/latest/http/header/struct.HeaderMap.html#method.into_iter-2
344        match (&mut last_header_name_seen, header_name) {
345            (_, Some(header_name)) => {
346                lhs.append(header_name.clone(), header_value);
347                last_header_name_seen = Some(header_name);
348            }
349            (Some(header_name), None) => {
350                lhs.append(header_name.clone(), header_value);
351            }
352            (None, None) => unreachable!(),
353        };
354    }
355
356    lhs
357}
358
359#[cfg(test)]
360mod test {
361    use super::quote_header_value;
362    use crate::header::{
363        append_merge_header_maps, headers_for_prefix, many_dates, read_many_from_str,
364        read_many_primitive, set_request_header_if_absent, set_response_header_if_absent,
365        ParseError,
366    };
367    use aws_smithy_runtime_api::http::Request;
368    use aws_smithy_types::error::display::DisplayErrorContext;
369    use aws_smithy_types::{date_time::Format, DateTime};
370    use http_1x::header::{HeaderMap, HeaderName, HeaderValue};
371    use std::collections::HashMap;
372
373    #[test]
374    fn put_on_request_if_absent() {
375        let builder = http_1x::Request::builder().header("foo", "bar");
376        let builder = set_request_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
377        let builder =
378            set_request_header_if_absent(builder, HeaderName::from_static("other"), "value");
379        let req = builder.body(()).expect("valid request");
380        assert_eq!(
381            req.headers().get_all("foo").iter().collect::<Vec<_>>(),
382            vec!["bar"]
383        );
384        assert_eq!(
385            req.headers().get_all("other").iter().collect::<Vec<_>>(),
386            vec!["value"]
387        );
388    }
389
390    #[test]
391    fn put_on_response_if_absent() {
392        let builder = http_1x::Response::builder().header("foo", "bar");
393        let builder = set_response_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
394        let builder =
395            set_response_header_if_absent(builder, HeaderName::from_static("other"), "value");
396        let response = builder.body(()).expect("valid response");
397        assert_eq!(
398            response.headers().get_all("foo").iter().collect::<Vec<_>>(),
399            vec!["bar"]
400        );
401        assert_eq!(
402            response
403                .headers()
404                .get_all("other")
405                .iter()
406                .collect::<Vec<_>>(),
407            vec!["value"]
408        );
409    }
410
411    #[test]
412    fn parse_floats() {
413        let test_request = http_1x::Request::builder()
414            .header("X-Float-Multi", "0.0,Infinity,-Infinity,5555.5")
415            .header("X-Float-Error", "notafloat")
416            .body(())
417            .unwrap();
418        assert_eq!(
419            read_many_primitive::<f32>(
420                test_request
421                    .headers()
422                    .get_all("X-Float-Multi")
423                    .iter()
424                    .map(|v| v.to_str().unwrap())
425            )
426            .expect("valid"),
427            vec![0.0, f32::INFINITY, f32::NEG_INFINITY, 5555.5]
428        );
429        let message = format!(
430            "{}",
431            DisplayErrorContext(
432                read_many_primitive::<f32>(
433                    test_request
434                        .headers()
435                        .get_all("X-Float-Error")
436                        .iter()
437                        .map(|v| v.to_str().unwrap())
438                )
439                .expect_err("invalid")
440            )
441        );
442        let expected = "output failed to parse in headers: failed reading a list of primitives: failed to parse input as f32";
443        assert!(
444            message.starts_with(expected),
445            "expected '{message}' to start with '{expected}'"
446        );
447    }
448
449    #[test]
450    fn test_many_dates() {
451        let test_request = http_1x::Request::builder()
452            .header("Empty", "")
453            .header("SingleHttpDate", "Wed, 21 Oct 2015 07:28:00 GMT")
454            .header(
455                "MultipleHttpDates",
456                "Wed, 21 Oct 2015 07:28:00 GMT,Thu, 22 Oct 2015 07:28:00 GMT",
457            )
458            .header("SingleEpochSeconds", "1234.5678")
459            .header("MultipleEpochSeconds", "1234.5678,9012.3456")
460            .body(())
461            .unwrap();
462        let read = |name: &str, format: Format| {
463            many_dates(
464                test_request
465                    .headers()
466                    .get_all(name)
467                    .iter()
468                    .map(|v| v.to_str().unwrap()),
469                format,
470            )
471        };
472        let read_valid = |name: &str, format: Format| read(name, format).expect("valid");
473        assert_eq!(
474            read_valid("Empty", Format::DateTime),
475            Vec::<DateTime>::new()
476        );
477        assert_eq!(
478            read_valid("SingleHttpDate", Format::HttpDate),
479            vec![DateTime::from_secs_and_nanos(1445412480, 0)]
480        );
481        assert_eq!(
482            read_valid("MultipleHttpDates", Format::HttpDate),
483            vec![
484                DateTime::from_secs_and_nanos(1445412480, 0),
485                DateTime::from_secs_and_nanos(1445498880, 0)
486            ]
487        );
488        assert_eq!(
489            read_valid("SingleEpochSeconds", Format::EpochSeconds),
490            vec![DateTime::from_secs_and_nanos(1234, 567_800_000)]
491        );
492        assert_eq!(
493            read_valid("MultipleEpochSeconds", Format::EpochSeconds),
494            vec![
495                DateTime::from_secs_and_nanos(1234, 567_800_000),
496                DateTime::from_secs_and_nanos(9012, 345_600_000)
497            ]
498        );
499    }
500
501    #[test]
502    fn read_many_strings() {
503        let test_request = http_1x::Request::builder()
504            .header("Empty", "")
505            .header("Foo", "  foo")
506            .header("FooTrailing", "foo   ")
507            .header("FooInQuotes", "\"  foo  \"")
508            .header("CommaInQuotes", "\"foo,bar\",baz")
509            .header("CommaInQuotesTrailing", "\"foo,bar\",baz  ")
510            .header("QuoteInQuotes", "\"foo\\\",bar\",\"\\\"asdf\\\"\",baz")
511            .header(
512                "QuoteInQuotesWithSpaces",
513                "\"foo\\\",bar\", \"\\\"asdf\\\"\", baz",
514            )
515            .header("JunkFollowingQuotes", "\"\\\"asdf\\\"\"baz")
516            .header("EmptyQuotes", "\"\",baz")
517            .header("EscapedSlashesInQuotes", "foo, \"(foo\\\\bar)\"")
518            .body(())
519            .unwrap();
520        let read = |name: &str| {
521            read_many_from_str::<String>(
522                test_request
523                    .headers()
524                    .get_all(name)
525                    .iter()
526                    .map(|v| v.to_str().unwrap()),
527            )
528        };
529        let read_valid = |name: &str| read(name).expect("valid");
530        assert_eq!(read_valid("Empty"), Vec::<String>::new());
531        assert_eq!(read_valid("Foo"), vec!["foo"]);
532        assert_eq!(read_valid("FooTrailing"), vec!["foo"]);
533        assert_eq!(read_valid("FooInQuotes"), vec!["  foo  "]);
534        assert_eq!(read_valid("CommaInQuotes"), vec!["foo,bar", "baz"]);
535        assert_eq!(read_valid("CommaInQuotesTrailing"), vec!["foo,bar", "baz"]);
536        assert_eq!(
537            read_valid("QuoteInQuotes"),
538            vec!["foo\",bar", "\"asdf\"", "baz"]
539        );
540        assert_eq!(
541            read_valid("QuoteInQuotesWithSpaces"),
542            vec!["foo\",bar", "\"asdf\"", "baz"]
543        );
544        assert!(read("JunkFollowingQuotes").is_err());
545        assert_eq!(read_valid("EmptyQuotes"), vec!["", "baz"]);
546        assert_eq!(
547            read_valid("EscapedSlashesInQuotes"),
548            vec!["foo", "(foo\\bar)"]
549        );
550    }
551
552    #[test]
553    fn read_many_bools() {
554        let test_request = http_1x::Request::builder()
555            .header("X-Bool-Multi", "true,false")
556            .header("X-Bool-Multi", "true")
557            .header("X-Bool", "true")
558            .header("X-Bool-Invalid", "truth,falsy")
559            .header("X-Bool-Single", "true,false,true,true")
560            .header("X-Bool-Quoted", "true,\"false\",true,true")
561            .body(())
562            .unwrap();
563        assert_eq!(
564            read_many_primitive::<bool>(
565                test_request
566                    .headers()
567                    .get_all("X-Bool-Multi")
568                    .iter()
569                    .map(|v| v.to_str().unwrap())
570            )
571            .expect("valid"),
572            vec![true, false, true]
573        );
574
575        assert_eq!(
576            read_many_primitive::<bool>(
577                test_request
578                    .headers()
579                    .get_all("X-Bool")
580                    .iter()
581                    .map(|v| v.to_str().unwrap())
582            )
583            .unwrap(),
584            vec![true]
585        );
586        assert_eq!(
587            read_many_primitive::<bool>(
588                test_request
589                    .headers()
590                    .get_all("X-Bool-Single")
591                    .iter()
592                    .map(|v| v.to_str().unwrap())
593            )
594            .unwrap(),
595            vec![true, false, true, true]
596        );
597        assert_eq!(
598            read_many_primitive::<bool>(
599                test_request
600                    .headers()
601                    .get_all("X-Bool-Quoted")
602                    .iter()
603                    .map(|v| v.to_str().unwrap())
604            )
605            .unwrap(),
606            vec![true, false, true, true]
607        );
608        read_many_primitive::<bool>(
609            test_request
610                .headers()
611                .get_all("X-Bool-Invalid")
612                .iter()
613                .map(|v| v.to_str().unwrap()),
614        )
615        .expect_err("invalid");
616    }
617
618    #[test]
619    fn check_read_many_i16() {
620        let test_request = http_1x::Request::builder()
621            .header("X-Multi", "123,456")
622            .header("X-Multi", "789")
623            .header("X-Num", "777")
624            .header("X-Num-Invalid", "12ef3")
625            .header("X-Num-Single", "1,2,3,-4,5")
626            .header("X-Num-Quoted", "1, \"2\",3,\"-4\",5")
627            .body(())
628            .unwrap();
629        assert_eq!(
630            read_many_primitive::<i16>(
631                test_request
632                    .headers()
633                    .get_all("X-Multi")
634                    .iter()
635                    .map(|v| v.to_str().unwrap())
636            )
637            .expect("valid"),
638            vec![123, 456, 789]
639        );
640
641        assert_eq!(
642            read_many_primitive::<i16>(
643                test_request
644                    .headers()
645                    .get_all("X-Num")
646                    .iter()
647                    .map(|v| v.to_str().unwrap())
648            )
649            .unwrap(),
650            vec![777]
651        );
652        assert_eq!(
653            read_many_primitive::<i16>(
654                test_request
655                    .headers()
656                    .get_all("X-Num-Single")
657                    .iter()
658                    .map(|v| v.to_str().unwrap())
659            )
660            .unwrap(),
661            vec![1, 2, 3, -4, 5]
662        );
663        assert_eq!(
664            read_many_primitive::<i16>(
665                test_request
666                    .headers()
667                    .get_all("X-Num-Quoted")
668                    .iter()
669                    .map(|v| v.to_str().unwrap())
670            )
671            .unwrap(),
672            vec![1, 2, 3, -4, 5]
673        );
674        read_many_primitive::<i16>(
675            test_request
676                .headers()
677                .get_all("X-Num-Invalid")
678                .iter()
679                .map(|v| v.to_str().unwrap()),
680        )
681        .expect_err("invalid");
682    }
683
684    #[test]
685    fn test_prefix_headers() {
686        let test_request = Request::try_from(
687            http_1x::Request::builder()
688                .header("X-Prefix-A", "123,456")
689                .header("X-Prefix-B", "789")
690                .header("X-Prefix-C", "777")
691                .header("X-Prefix-C", "777")
692                .body(())
693                .unwrap(),
694        )
695        .unwrap();
696        let resp: Result<HashMap<String, Vec<i16>>, ParseError> =
697            headers_for_prefix(test_request.headers().iter().map(|h| h.0), "X-Prefix-")
698                .map(|(key, header_name)| {
699                    let values = test_request.headers().get_all(header_name);
700                    read_many_primitive(values).map(|v| (key.to_string(), v))
701                })
702                .collect();
703        let resp = resp.expect("valid");
704        assert_eq!(resp.get("a"), Some(&vec![123_i16, 456_i16]));
705    }
706
707    #[test]
708    fn test_quote_header_value() {
709        assert_eq!("", &quote_header_value(""));
710        assert_eq!("foo", &quote_header_value("foo"));
711        assert_eq!("\"  foo\"", &quote_header_value("  foo"));
712        assert_eq!("foo bar", &quote_header_value("foo bar"));
713        assert_eq!("\"foo,bar\"", &quote_header_value("foo,bar"));
714        assert_eq!("\",\"", &quote_header_value(","));
715        assert_eq!("\"\\\"foo\\\"\"", &quote_header_value("\"foo\""));
716        assert_eq!("\"\\\"f\\\\oo\\\"\"", &quote_header_value("\"f\\oo\""));
717        assert_eq!("\"(\"", &quote_header_value("("));
718        assert_eq!("\")\"", &quote_header_value(")"));
719    }
720
721    #[test]
722    fn test_append_merge_header_maps_with_shared_key() {
723        let header_name = HeaderName::from_static("some_key");
724        let left_header_value = HeaderValue::from_static("lhs value");
725        let right_header_value = HeaderValue::from_static("rhs value");
726
727        let mut left_hand_side_headers = HeaderMap::new();
728        left_hand_side_headers.insert(header_name.clone(), left_header_value.clone());
729
730        let mut right_hand_side_headers = HeaderMap::new();
731        right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
732
733        let merged_header_map =
734            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
735        let actual_merged_values: Vec<_> =
736            merged_header_map.get_all(header_name).into_iter().collect();
737
738        let expected_merged_values = vec![left_header_value, right_header_value];
739
740        assert_eq!(actual_merged_values, expected_merged_values);
741    }
742
743    #[test]
744    fn test_append_merge_header_maps_with_multiple_values_in_left_hand_map() {
745        let header_name = HeaderName::from_static("some_key");
746        let left_header_value_1 = HeaderValue::from_static("lhs value 1");
747        let left_header_value_2 = HeaderValue::from_static("lhs_value 2");
748        let right_header_value = HeaderValue::from_static("rhs value");
749
750        let mut left_hand_side_headers = HeaderMap::new();
751        left_hand_side_headers.insert(header_name.clone(), left_header_value_1.clone());
752        left_hand_side_headers.append(header_name.clone(), left_header_value_2.clone());
753
754        let mut right_hand_side_headers = HeaderMap::new();
755        right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
756
757        let merged_header_map =
758            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
759        let actual_merged_values: Vec<_> =
760            merged_header_map.get_all(header_name).into_iter().collect();
761
762        let expected_merged_values =
763            vec![left_header_value_1, left_header_value_2, right_header_value];
764
765        assert_eq!(actual_merged_values, expected_merged_values);
766    }
767
768    #[test]
769    fn test_append_merge_header_maps_with_empty_left_hand_map() {
770        let header_name = HeaderName::from_static("some_key");
771        let right_header_value_1 = HeaderValue::from_static("rhs value 1");
772        let right_header_value_2 = HeaderValue::from_static("rhs_value 2");
773
774        let left_hand_side_headers = HeaderMap::new();
775
776        let mut right_hand_side_headers = HeaderMap::new();
777        right_hand_side_headers.insert(header_name.clone(), right_header_value_1.clone());
778        right_hand_side_headers.append(header_name.clone(), right_header_value_2.clone());
779
780        let merged_header_map =
781            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
782        let actual_merged_values: Vec<_> =
783            merged_header_map.get_all(header_name).into_iter().collect();
784
785        let expected_merged_values = vec![right_header_value_1, right_header_value_2];
786
787        assert_eq!(actual_merged_values, expected_merged_values);
788    }
789}