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 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
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_bytes` below because we need to invoke `DateTime::read` to take
64/// advantage 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_1x::request::Builder,
215    key: HeaderName,
216    value: V,
217) -> http_1x::request::Builder
218where
219    HeaderValue: TryFrom<V>,
220    <HeaderValue as TryFrom<V>>::Error: Into<http_1x::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_1x::response::Builder,
236    key: HeaderName,
237    value: V,
238) -> http_1x::response::Builder
239where
240    HeaderValue: TryFrom<V>,
241    <HeaderValue as TryFrom<V>>::Error: Into<http_1x::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 http-02x [`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/// Given two http-1x [`HeaderMap`]s, merge them together and return the merged `HeaderMap`. If the
393/// two `HeaderMap`s share any keys, values from the right `HeaderMap` be appended to the left `HeaderMap`.
394pub fn append_merge_header_maps_http_1x(
395    mut lhs: http_1x::HeaderMap<http_1x::HeaderValue>,
396    rhs: http_1x::HeaderMap<http_1x::HeaderValue>,
397) -> http_1x::HeaderMap<http_1x::HeaderValue> {
398    let mut last_header_name_seen = None;
399    for (header_name, header_value) in rhs.into_iter() {
400        // For each yielded item that has None provided for the `HeaderName`,
401        // then the associated header name is the same as that of the previously
402        // yielded item. The first yielded item will have `HeaderName` set.
403        // https://docs.rs/http/latest/http/header/struct.HeaderMap.html#method.into_iter-2
404        match (&mut last_header_name_seen, header_name) {
405            (_, Some(header_name)) => {
406                lhs.append(header_name.clone(), header_value);
407                last_header_name_seen = Some(header_name);
408            }
409            (Some(header_name), None) => {
410                lhs.append(header_name.clone(), header_value);
411            }
412            (None, None) => unreachable!(),
413        };
414    }
415
416    lhs
417}
418
419#[cfg(test)]
420mod test {
421    use super::quote_header_value;
422    use crate::header::{
423        append_merge_header_maps, headers_for_prefix, many_dates, many_dates_bytes, one_or_none,
424        one_or_none_bytes, read_many_from_str, read_many_from_str_bytes, read_many_primitive,
425        read_many_primitive_bytes, set_request_header_if_absent, set_response_header_if_absent,
426        ParseError,
427    };
428    use aws_smithy_runtime_api::http::Request;
429    use aws_smithy_types::error::display::DisplayErrorContext;
430    use aws_smithy_types::{date_time::Format, DateTime};
431    use http_1x::header::{HeaderMap, HeaderName, HeaderValue};
432    use std::collections::HashMap;
433
434    #[test]
435    fn put_on_request_if_absent() {
436        let builder = http_1x::Request::builder().header("foo", "bar");
437        let builder = set_request_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
438        let builder =
439            set_request_header_if_absent(builder, HeaderName::from_static("other"), "value");
440        let req = builder.body(()).expect("valid request");
441        assert_eq!(
442            req.headers().get_all("foo").iter().collect::<Vec<_>>(),
443            vec!["bar"]
444        );
445        assert_eq!(
446            req.headers().get_all("other").iter().collect::<Vec<_>>(),
447            vec!["value"]
448        );
449    }
450
451    #[test]
452    fn put_on_response_if_absent() {
453        let builder = http_1x::Response::builder().header("foo", "bar");
454        let builder = set_response_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
455        let builder =
456            set_response_header_if_absent(builder, HeaderName::from_static("other"), "value");
457        let response = builder.body(()).expect("valid response");
458        assert_eq!(
459            response.headers().get_all("foo").iter().collect::<Vec<_>>(),
460            vec!["bar"]
461        );
462        assert_eq!(
463            response
464                .headers()
465                .get_all("other")
466                .iter()
467                .collect::<Vec<_>>(),
468            vec!["value"]
469        );
470    }
471
472    #[test]
473    fn parse_floats() {
474        let test_request = http_1x::Request::builder()
475            .header("X-Float-Multi", "0.0,Infinity,-Infinity,5555.5")
476            .header("X-Float-Error", "notafloat")
477            .body(())
478            .unwrap();
479        assert_eq!(
480            read_many_primitive::<f32>(
481                test_request
482                    .headers()
483                    .get_all("X-Float-Multi")
484                    .iter()
485                    .map(|v| v.to_str().unwrap())
486            )
487            .expect("valid"),
488            vec![0.0, f32::INFINITY, f32::NEG_INFINITY, 5555.5]
489        );
490        let message = format!(
491            "{}",
492            DisplayErrorContext(
493                read_many_primitive::<f32>(
494                    test_request
495                        .headers()
496                        .get_all("X-Float-Error")
497                        .iter()
498                        .map(|v| v.to_str().unwrap())
499                )
500                .expect_err("invalid")
501            )
502        );
503        let expected = "output failed to parse in headers: failed reading a list of primitives: failed to parse input as f32";
504        assert!(
505            message.starts_with(expected),
506            "expected '{message}' to start with '{expected}'"
507        );
508    }
509
510    #[test]
511    fn test_many_dates() {
512        let test_request = http_1x::Request::builder()
513            .header("Empty", "")
514            .header("SingleHttpDate", "Wed, 21 Oct 2015 07:28:00 GMT")
515            .header(
516                "MultipleHttpDates",
517                "Wed, 21 Oct 2015 07:28:00 GMT,Thu, 22 Oct 2015 07:28:00 GMT",
518            )
519            .header("SingleEpochSeconds", "1234.5678")
520            .header("MultipleEpochSeconds", "1234.5678,9012.3456")
521            .body(())
522            .unwrap();
523        let read = |name: &str, format: Format| {
524            many_dates(
525                test_request
526                    .headers()
527                    .get_all(name)
528                    .iter()
529                    .map(|v| v.to_str().unwrap()),
530                format,
531            )
532        };
533        let read_valid = |name: &str, format: Format| read(name, format).expect("valid");
534        assert_eq!(
535            read_valid("Empty", Format::DateTime),
536            Vec::<DateTime>::new()
537        );
538        assert_eq!(
539            read_valid("SingleHttpDate", Format::HttpDate),
540            vec![DateTime::from_secs_and_nanos(1445412480, 0)]
541        );
542        assert_eq!(
543            read_valid("MultipleHttpDates", Format::HttpDate),
544            vec![
545                DateTime::from_secs_and_nanos(1445412480, 0),
546                DateTime::from_secs_and_nanos(1445498880, 0)
547            ]
548        );
549        assert_eq!(
550            read_valid("SingleEpochSeconds", Format::EpochSeconds),
551            vec![DateTime::from_secs_and_nanos(1234, 567_800_000)]
552        );
553        assert_eq!(
554            read_valid("MultipleEpochSeconds", Format::EpochSeconds),
555            vec![
556                DateTime::from_secs_and_nanos(1234, 567_800_000),
557                DateTime::from_secs_and_nanos(9012, 345_600_000)
558            ]
559        );
560    }
561
562    // A lone 0xE9 is a valid HTTP header octet (obs-text per RFC 7230) but is not valid UTF-8.
563    const NON_UTF8_VALUE: &[u8] = b"value-\xe9";
564
565    #[test]
566    fn bytes_helpers_agree_with_str_helpers_on_valid_utf8() {
567        assert_eq!(
568            one_or_none::<String>(["  foo  "].into_iter()).unwrap(),
569            one_or_none_bytes::<String>([b"  foo  ".as_slice()].into_iter()).unwrap(),
570        );
571        assert_eq!(
572            read_many_from_str::<String>(["\"foo,bar\",baz"].into_iter()).unwrap(),
573            read_many_from_str_bytes::<String>([b"\"foo,bar\",baz".as_slice()].into_iter())
574                .unwrap(),
575        );
576        assert_eq!(
577            read_many_primitive::<i16>(["1,2", "3"].into_iter()).unwrap(),
578            read_many_primitive_bytes::<i16>([b"1,2".as_slice(), b"3".as_slice()].into_iter())
579                .unwrap(),
580        );
581        assert_eq!(
582            many_dates(
583                ["Mon, 16 Dec 2019 23:48:18 GMT"].into_iter(),
584                Format::HttpDate
585            )
586            .unwrap(),
587            many_dates_bytes(
588                [b"Mon, 16 Dec 2019 23:48:18 GMT".as_slice()].into_iter(),
589                Format::HttpDate
590            )
591            .unwrap(),
592        );
593    }
594
595    #[test]
596    fn bytes_helpers_reject_non_utf8() {
597        let expected = "header was not valid utf-8";
598
599        let err = one_or_none_bytes::<String>([NON_UTF8_VALUE].into_iter()).expect_err("non-utf8");
600        assert!(err.to_string().contains(expected), "{err}");
601
602        let err =
603            read_many_from_str_bytes::<String>([NON_UTF8_VALUE].into_iter()).expect_err("non-utf8");
604        assert!(err.to_string().contains(expected), "{err}");
605
606        let err =
607            read_many_primitive_bytes::<i16>([NON_UTF8_VALUE].into_iter()).expect_err("non-utf8");
608        assert!(err.to_string().contains(expected), "{err}");
609
610        let err =
611            many_dates_bytes([NON_UTF8_VALUE].into_iter(), Format::HttpDate).expect_err("non-utf8");
612        assert!(err.to_string().contains(expected), "{err}");
613    }
614
615    #[test]
616    fn one_or_none_bytes_reports_multiple_before_non_utf8() {
617        // `one_or_none` checks for multiple values before inspecting the first one; the bytes
618        // variant must keep that precedence.
619        let err = one_or_none_bytes::<String>([NON_UTF8_VALUE, b"second".as_slice()].into_iter())
620            .expect_err("multiple values");
621        assert!(
622            err.to_string()
623                .contains("expected a single value but found multiple"),
624            "{err}"
625        );
626    }
627
628    #[test]
629    fn read_many_strings() {
630        let test_request = http_1x::Request::builder()
631            .header("Empty", "")
632            .header("Foo", "  foo")
633            .header("FooTrailing", "foo   ")
634            .header("FooInQuotes", "\"  foo  \"")
635            .header("CommaInQuotes", "\"foo,bar\",baz")
636            .header("CommaInQuotesTrailing", "\"foo,bar\",baz  ")
637            .header("QuoteInQuotes", "\"foo\\\",bar\",\"\\\"asdf\\\"\",baz")
638            .header(
639                "QuoteInQuotesWithSpaces",
640                "\"foo\\\",bar\", \"\\\"asdf\\\"\", baz",
641            )
642            .header("JunkFollowingQuotes", "\"\\\"asdf\\\"\"baz")
643            .header("EmptyQuotes", "\"\",baz")
644            .header("EscapedSlashesInQuotes", "foo, \"(foo\\\\bar)\"")
645            .body(())
646            .unwrap();
647        let read = |name: &str| {
648            read_many_from_str::<String>(
649                test_request
650                    .headers()
651                    .get_all(name)
652                    .iter()
653                    .map(|v| v.to_str().unwrap()),
654            )
655        };
656        let read_valid = |name: &str| read(name).expect("valid");
657        assert_eq!(read_valid("Empty"), Vec::<String>::new());
658        assert_eq!(read_valid("Foo"), vec!["foo"]);
659        assert_eq!(read_valid("FooTrailing"), vec!["foo"]);
660        assert_eq!(read_valid("FooInQuotes"), vec!["  foo  "]);
661        assert_eq!(read_valid("CommaInQuotes"), vec!["foo,bar", "baz"]);
662        assert_eq!(read_valid("CommaInQuotesTrailing"), vec!["foo,bar", "baz"]);
663        assert_eq!(
664            read_valid("QuoteInQuotes"),
665            vec!["foo\",bar", "\"asdf\"", "baz"]
666        );
667        assert_eq!(
668            read_valid("QuoteInQuotesWithSpaces"),
669            vec!["foo\",bar", "\"asdf\"", "baz"]
670        );
671        assert!(read("JunkFollowingQuotes").is_err());
672        assert_eq!(read_valid("EmptyQuotes"), vec!["", "baz"]);
673        assert_eq!(
674            read_valid("EscapedSlashesInQuotes"),
675            vec!["foo", "(foo\\bar)"]
676        );
677    }
678
679    #[test]
680    fn read_many_bools() {
681        let test_request = http_1x::Request::builder()
682            .header("X-Bool-Multi", "true,false")
683            .header("X-Bool-Multi", "true")
684            .header("X-Bool", "true")
685            .header("X-Bool-Invalid", "truth,falsy")
686            .header("X-Bool-Single", "true,false,true,true")
687            .header("X-Bool-Quoted", "true,\"false\",true,true")
688            .body(())
689            .unwrap();
690        assert_eq!(
691            read_many_primitive::<bool>(
692                test_request
693                    .headers()
694                    .get_all("X-Bool-Multi")
695                    .iter()
696                    .map(|v| v.to_str().unwrap())
697            )
698            .expect("valid"),
699            vec![true, false, true]
700        );
701
702        assert_eq!(
703            read_many_primitive::<bool>(
704                test_request
705                    .headers()
706                    .get_all("X-Bool")
707                    .iter()
708                    .map(|v| v.to_str().unwrap())
709            )
710            .unwrap(),
711            vec![true]
712        );
713        assert_eq!(
714            read_many_primitive::<bool>(
715                test_request
716                    .headers()
717                    .get_all("X-Bool-Single")
718                    .iter()
719                    .map(|v| v.to_str().unwrap())
720            )
721            .unwrap(),
722            vec![true, false, true, true]
723        );
724        assert_eq!(
725            read_many_primitive::<bool>(
726                test_request
727                    .headers()
728                    .get_all("X-Bool-Quoted")
729                    .iter()
730                    .map(|v| v.to_str().unwrap())
731            )
732            .unwrap(),
733            vec![true, false, true, true]
734        );
735        read_many_primitive::<bool>(
736            test_request
737                .headers()
738                .get_all("X-Bool-Invalid")
739                .iter()
740                .map(|v| v.to_str().unwrap()),
741        )
742        .expect_err("invalid");
743    }
744
745    #[test]
746    fn check_read_many_i16() {
747        let test_request = http_1x::Request::builder()
748            .header("X-Multi", "123,456")
749            .header("X-Multi", "789")
750            .header("X-Num", "777")
751            .header("X-Num-Invalid", "12ef3")
752            .header("X-Num-Single", "1,2,3,-4,5")
753            .header("X-Num-Quoted", "1, \"2\",3,\"-4\",5")
754            .body(())
755            .unwrap();
756        assert_eq!(
757            read_many_primitive::<i16>(
758                test_request
759                    .headers()
760                    .get_all("X-Multi")
761                    .iter()
762                    .map(|v| v.to_str().unwrap())
763            )
764            .expect("valid"),
765            vec![123, 456, 789]
766        );
767
768        assert_eq!(
769            read_many_primitive::<i16>(
770                test_request
771                    .headers()
772                    .get_all("X-Num")
773                    .iter()
774                    .map(|v| v.to_str().unwrap())
775            )
776            .unwrap(),
777            vec![777]
778        );
779        assert_eq!(
780            read_many_primitive::<i16>(
781                test_request
782                    .headers()
783                    .get_all("X-Num-Single")
784                    .iter()
785                    .map(|v| v.to_str().unwrap())
786            )
787            .unwrap(),
788            vec![1, 2, 3, -4, 5]
789        );
790        assert_eq!(
791            read_many_primitive::<i16>(
792                test_request
793                    .headers()
794                    .get_all("X-Num-Quoted")
795                    .iter()
796                    .map(|v| v.to_str().unwrap())
797            )
798            .unwrap(),
799            vec![1, 2, 3, -4, 5]
800        );
801        read_many_primitive::<i16>(
802            test_request
803                .headers()
804                .get_all("X-Num-Invalid")
805                .iter()
806                .map(|v| v.to_str().unwrap()),
807        )
808        .expect_err("invalid");
809    }
810
811    #[test]
812    fn test_prefix_headers() {
813        let test_request = Request::try_from(
814            http_1x::Request::builder()
815                .header("X-Prefix-A", "123,456")
816                .header("X-Prefix-B", "789")
817                .header("X-Prefix-C", "777")
818                .header("X-Prefix-C", "777")
819                .body(())
820                .unwrap(),
821        )
822        .unwrap();
823        let resp: Result<HashMap<String, Vec<i16>>, ParseError> =
824            headers_for_prefix(test_request.headers().iter().map(|h| h.0), "X-Prefix-")
825                .map(|(key, header_name)| {
826                    let values = test_request.headers().get_all(header_name);
827                    read_many_primitive(values).map(|v| (key.to_string(), v))
828                })
829                .collect();
830        let resp = resp.expect("valid");
831        assert_eq!(resp.get("a"), Some(&vec![123_i16, 456_i16]));
832    }
833
834    #[test]
835    fn test_quote_header_value() {
836        assert_eq!("", &quote_header_value(""));
837        assert_eq!("foo", &quote_header_value("foo"));
838        assert_eq!("\"  foo\"", &quote_header_value("  foo"));
839        assert_eq!("foo bar", &quote_header_value("foo bar"));
840        assert_eq!("\"foo,bar\"", &quote_header_value("foo,bar"));
841        assert_eq!("\",\"", &quote_header_value(","));
842        assert_eq!("\"\\\"foo\\\"\"", &quote_header_value("\"foo\""));
843        assert_eq!("\"\\\"f\\\\oo\\\"\"", &quote_header_value("\"f\\oo\""));
844        assert_eq!("\"(\"", &quote_header_value("("));
845        assert_eq!("\")\"", &quote_header_value(")"));
846    }
847
848    #[test]
849    fn test_append_merge_header_maps_with_shared_key() {
850        let header_name = HeaderName::from_static("some_key");
851        let left_header_value = HeaderValue::from_static("lhs value");
852        let right_header_value = HeaderValue::from_static("rhs value");
853
854        let mut left_hand_side_headers = HeaderMap::new();
855        left_hand_side_headers.insert(header_name.clone(), left_header_value.clone());
856
857        let mut right_hand_side_headers = HeaderMap::new();
858        right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
859
860        let merged_header_map =
861            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
862        let actual_merged_values: Vec<_> =
863            merged_header_map.get_all(header_name).into_iter().collect();
864
865        let expected_merged_values = vec![left_header_value, right_header_value];
866
867        assert_eq!(actual_merged_values, expected_merged_values);
868    }
869
870    #[test]
871    fn test_append_merge_header_maps_with_multiple_values_in_left_hand_map() {
872        let header_name = HeaderName::from_static("some_key");
873        let left_header_value_1 = HeaderValue::from_static("lhs value 1");
874        let left_header_value_2 = HeaderValue::from_static("lhs_value 2");
875        let right_header_value = HeaderValue::from_static("rhs value");
876
877        let mut left_hand_side_headers = HeaderMap::new();
878        left_hand_side_headers.insert(header_name.clone(), left_header_value_1.clone());
879        left_hand_side_headers.append(header_name.clone(), left_header_value_2.clone());
880
881        let mut right_hand_side_headers = HeaderMap::new();
882        right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
883
884        let merged_header_map =
885            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
886        let actual_merged_values: Vec<_> =
887            merged_header_map.get_all(header_name).into_iter().collect();
888
889        let expected_merged_values =
890            vec![left_header_value_1, left_header_value_2, right_header_value];
891
892        assert_eq!(actual_merged_values, expected_merged_values);
893    }
894
895    #[test]
896    fn test_append_merge_header_maps_with_empty_left_hand_map() {
897        let header_name = HeaderName::from_static("some_key");
898        let right_header_value_1 = HeaderValue::from_static("rhs value 1");
899        let right_header_value_2 = HeaderValue::from_static("rhs_value 2");
900
901        let left_hand_side_headers = HeaderMap::new();
902
903        let mut right_hand_side_headers = HeaderMap::new();
904        right_hand_side_headers.insert(header_name.clone(), right_header_value_1.clone());
905        right_hand_side_headers.append(header_name.clone(), right_header_value_2.clone());
906
907        let merged_header_map =
908            append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
909        let actual_merged_values: Vec<_> =
910            merged_header_map.get_all(header_name).into_iter().collect();
911
912        let expected_merged_values = vec![right_header_value_1, right_header_value_2];
913
914        assert_eq!(actual_merged_values, expected_merged_values);
915    }
916}