Skip to main content

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