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 - |
|
8 - | use aws_smithy_types::date_time::Format;
|
9 - | use aws_smithy_types::primitive::Parse;
|
10 - | use aws_smithy_types::DateTime;
|
11 - | use http_02x::header::{HeaderMap, HeaderName, HeaderValue};
|
12 - | use std::borrow::Cow;
|
13 - | use std::error::Error;
|
14 - | use std::fmt;
|
15 - | use std::str::FromStr;
|
16 - |
|
17 - | /// An error was encountered while parsing a header
|
18 - | #[derive(Debug)]
|
19 - | pub struct ParseError {
|
20 - | message: Cow<'static, str>,
|
21 - | source: Option<Box<dyn Error + Send + Sync + 'static>>,
|
22 - | }
|
23 - |
|
24 - | impl 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 - |
|
42 - | impl 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 - |
|
48 - | impl 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
|
58 - | pub 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.
|
78 - | pub 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`
|
89 - | pub fn read_many_from_str<'a, T: FromStr>(
|
90 - | values: impl Iterator<Item = &'a str>,
|
91 - | ) -> Result<Vec<T>, ParseError>
|
92 - | where
|
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`
|
103 - | pub 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
|
113 - | fn 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`
|
132 - | pub fn one_or_none<'a, T: FromStr>(
|
133 - | mut values: impl Iterator<Item = &'a str>,
|
134 - | ) -> Result<Option<T>, ParseError>
|
135 - | where
|
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.
|
153 - | pub fn set_request_header_if_absent<V>(
|
154 - | request: http_02x::request::Builder,
|
155 - | key: HeaderName,
|
156 - | value: V,
|
157 - | ) -> http_02x::request::Builder
|
158 - | where
|
159 - | HeaderValue: TryFrom<V>,
|
160 - | <HeaderValue as TryFrom<V>>::Error: Into<http_02x::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.
|
174 - | pub fn set_response_header_if_absent<V>(
|
175 - | response: http_02x::response::Builder,
|
176 - | key: HeaderName,
|
177 - | value: V,
|
178 - | ) -> http_02x::response::Builder
|
179 - | where
|
180 - | HeaderValue: TryFrom<V>,
|
181 - | <HeaderValue as TryFrom<V>>::Error: Into<http_02x::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).
|
197 - | mod 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(¤t_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
|
279 - | fn 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.
|
288 - | pub 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 [`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`.
|
307 - | pub 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 - | #[cfg(test)]
|
333 - | mod test {
|
334 - | use super::quote_header_value;
|
335 - | use crate::header::{
|
336 - | append_merge_header_maps, headers_for_prefix, many_dates, read_many_from_str,
|
337 - | read_many_primitive, set_request_header_if_absent, set_response_header_if_absent,
|
338 - | ParseError,
|
339 - | };
|
340 - | use aws_smithy_runtime_api::http::Request;
|
341 - | use aws_smithy_types::error::display::DisplayErrorContext;
|
342 - | use aws_smithy_types::{date_time::Format, DateTime};
|
343 - | use http_02x::header::{HeaderMap, HeaderName, HeaderValue};
|
344 - | use std::collections::HashMap;
|
345 - |
|
346 - | #[test]
|
347 - | fn put_on_request_if_absent() {
|
348 - | let builder = http_02x::Request::builder().header("foo", "bar");
|
349 - | let builder = set_request_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
|
350 - | let builder =
|
351 - | set_request_header_if_absent(builder, HeaderName::from_static("other"), "value");
|
352 - | let req = builder.body(()).expect("valid request");
|
353 - | assert_eq!(
|
354 - | req.headers().get_all("foo").iter().collect::<Vec<_>>(),
|
355 - | vec!["bar"]
|
356 - | );
|
357 - | assert_eq!(
|
358 - | req.headers().get_all("other").iter().collect::<Vec<_>>(),
|
359 - | vec!["value"]
|
360 - | );
|
361 - | }
|
362 - |
|
363 - | #[test]
|
364 - | fn put_on_response_if_absent() {
|
365 - | let builder = http_02x::Response::builder().header("foo", "bar");
|
366 - | let builder = set_response_header_if_absent(builder, HeaderName::from_static("foo"), "baz");
|
367 - | let builder =
|
368 - | set_response_header_if_absent(builder, HeaderName::from_static("other"), "value");
|
369 - | let response = builder.body(()).expect("valid response");
|
370 - | assert_eq!(
|
371 - | response.headers().get_all("foo").iter().collect::<Vec<_>>(),
|
372 - | vec!["bar"]
|
373 - | );
|
374 - | assert_eq!(
|
375 - | response
|
376 - | .headers()
|
377 - | .get_all("other")
|
378 - | .iter()
|
379 - | .collect::<Vec<_>>(),
|
380 - | vec!["value"]
|
381 - | );
|
382 - | }
|
383 - |
|
384 - | #[test]
|
385 - | fn parse_floats() {
|
386 - | let test_request = http_02x::Request::builder()
|
387 - | .header("X-Float-Multi", "0.0,Infinity,-Infinity,5555.5")
|
388 - | .header("X-Float-Error", "notafloat")
|
389 - | .body(())
|
390 - | .unwrap();
|
391 - | assert_eq!(
|
392 - | read_many_primitive::<f32>(
|
393 - | test_request
|
394 - | .headers()
|
395 - | .get_all("X-Float-Multi")
|
396 - | .iter()
|
397 - | .map(|v| v.to_str().unwrap())
|
398 - | )
|
399 - | .expect("valid"),
|
400 - | vec![0.0, f32::INFINITY, f32::NEG_INFINITY, 5555.5]
|
401 - | );
|
402 - | let message = format!(
|
403 - | "{}",
|
404 - | DisplayErrorContext(
|
405 - | read_many_primitive::<f32>(
|
406 - | test_request
|
407 - | .headers()
|
408 - | .get_all("X-Float-Error")
|
409 - | .iter()
|
410 - | .map(|v| v.to_str().unwrap())
|
411 - | )
|
412 - | .expect_err("invalid")
|
413 - | )
|
414 - | );
|
415 - | let expected = "output failed to parse in headers: failed reading a list of primitives: failed to parse input as f32";
|
416 - | assert!(
|
417 - | message.starts_with(expected),
|
418 - | "expected '{message}' to start with '{expected}'"
|
419 - | );
|
420 - | }
|
421 - |
|
422 - | #[test]
|
423 - | fn test_many_dates() {
|
424 - | let test_request = http_02x::Request::builder()
|
425 - | .header("Empty", "")
|
426 - | .header("SingleHttpDate", "Wed, 21 Oct 2015 07:28:00 GMT")
|
427 - | .header(
|
428 - | "MultipleHttpDates",
|
429 - | "Wed, 21 Oct 2015 07:28:00 GMT,Thu, 22 Oct 2015 07:28:00 GMT",
|
430 - | )
|
431 - | .header("SingleEpochSeconds", "1234.5678")
|
432 - | .header("MultipleEpochSeconds", "1234.5678,9012.3456")
|
433 - | .body(())
|
434 - | .unwrap();
|
435 - | let read = |name: &str, format: Format| {
|
436 - | many_dates(
|
437 - | test_request
|
438 - | .headers()
|
439 - | .get_all(name)
|
440 - | .iter()
|
441 - | .map(|v| v.to_str().unwrap()),
|
442 - | format,
|
443 - | )
|
444 - | };
|
445 - | let read_valid = |name: &str, format: Format| read(name, format).expect("valid");
|
446 - | assert_eq!(
|
447 - | read_valid("Empty", Format::DateTime),
|
448 - | Vec::<DateTime>::new()
|
449 - | );
|
450 - | assert_eq!(
|
451 - | read_valid("SingleHttpDate", Format::HttpDate),
|
452 - | vec![DateTime::from_secs_and_nanos(1445412480, 0)]
|
453 - | );
|
454 - | assert_eq!(
|
455 - | read_valid("MultipleHttpDates", Format::HttpDate),
|
456 - | vec![
|
457 - | DateTime::from_secs_and_nanos(1445412480, 0),
|
458 - | DateTime::from_secs_and_nanos(1445498880, 0)
|
459 - | ]
|
460 - | );
|
461 - | assert_eq!(
|
462 - | read_valid("SingleEpochSeconds", Format::EpochSeconds),
|
463 - | vec![DateTime::from_secs_and_nanos(1234, 567_800_000)]
|
464 - | );
|
465 - | assert_eq!(
|
466 - | read_valid("MultipleEpochSeconds", Format::EpochSeconds),
|
467 - | vec![
|
468 - | DateTime::from_secs_and_nanos(1234, 567_800_000),
|
469 - | DateTime::from_secs_and_nanos(9012, 345_600_000)
|
470 - | ]
|
471 - | );
|
472 - | }
|
473 - |
|
474 - | #[test]
|
475 - | fn read_many_strings() {
|
476 - | let test_request = http_02x::Request::builder()
|
477 - | .header("Empty", "")
|
478 - | .header("Foo", " foo")
|
479 - | .header("FooTrailing", "foo ")
|
480 - | .header("FooInQuotes", "\" foo \"")
|
481 - | .header("CommaInQuotes", "\"foo,bar\",baz")
|
482 - | .header("CommaInQuotesTrailing", "\"foo,bar\",baz ")
|
483 - | .header("QuoteInQuotes", "\"foo\\\",bar\",\"\\\"asdf\\\"\",baz")
|
484 - | .header(
|
485 - | "QuoteInQuotesWithSpaces",
|
486 - | "\"foo\\\",bar\", \"\\\"asdf\\\"\", baz",
|
487 - | )
|
488 - | .header("JunkFollowingQuotes", "\"\\\"asdf\\\"\"baz")
|
489 - | .header("EmptyQuotes", "\"\",baz")
|
490 - | .header("EscapedSlashesInQuotes", "foo, \"(foo\\\\bar)\"")
|
491 - | .body(())
|
492 - | .unwrap();
|
493 - | let read = |name: &str| {
|
494 - | read_many_from_str::<String>(
|
495 - | test_request
|
496 - | .headers()
|
497 - | .get_all(name)
|
498 - | .iter()
|
499 - | .map(|v| v.to_str().unwrap()),
|
500 - | )
|
501 - | };
|
502 - | let read_valid = |name: &str| read(name).expect("valid");
|
503 - | assert_eq!(read_valid("Empty"), Vec::<String>::new());
|
504 - | assert_eq!(read_valid("Foo"), vec!["foo"]);
|
505 - | assert_eq!(read_valid("FooTrailing"), vec!["foo"]);
|
506 - | assert_eq!(read_valid("FooInQuotes"), vec![" foo "]);
|
507 - | assert_eq!(read_valid("CommaInQuotes"), vec!["foo,bar", "baz"]);
|
508 - | assert_eq!(read_valid("CommaInQuotesTrailing"), vec!["foo,bar", "baz"]);
|
509 - | assert_eq!(
|
510 - | read_valid("QuoteInQuotes"),
|
511 - | vec!["foo\",bar", "\"asdf\"", "baz"]
|
512 - | );
|
513 - | assert_eq!(
|
514 - | read_valid("QuoteInQuotesWithSpaces"),
|
515 - | vec!["foo\",bar", "\"asdf\"", "baz"]
|
516 - | );
|
517 - | assert!(read("JunkFollowingQuotes").is_err());
|
518 - | assert_eq!(read_valid("EmptyQuotes"), vec!["", "baz"]);
|
519 - | assert_eq!(
|
520 - | read_valid("EscapedSlashesInQuotes"),
|
521 - | vec!["foo", "(foo\\bar)"]
|
522 - | );
|
523 - | }
|
524 - |
|
525 - | #[test]
|
526 - | fn read_many_bools() {
|
527 - | let test_request = http_02x::Request::builder()
|
528 - | .header("X-Bool-Multi", "true,false")
|
529 - | .header("X-Bool-Multi", "true")
|
530 - | .header("X-Bool", "true")
|
531 - | .header("X-Bool-Invalid", "truth,falsy")
|
532 - | .header("X-Bool-Single", "true,false,true,true")
|
533 - | .header("X-Bool-Quoted", "true,\"false\",true,true")
|
534 - | .body(())
|
535 - | .unwrap();
|
536 - | assert_eq!(
|
537 - | read_many_primitive::<bool>(
|
538 - | test_request
|
539 - | .headers()
|
540 - | .get_all("X-Bool-Multi")
|
541 - | .iter()
|
542 - | .map(|v| v.to_str().unwrap())
|
543 - | )
|
544 - | .expect("valid"),
|
545 - | vec![true, false, true]
|
546 - | );
|
547 - |
|
548 - | assert_eq!(
|
549 - | read_many_primitive::<bool>(
|
550 - | test_request
|
551 - | .headers()
|
552 - | .get_all("X-Bool")
|
553 - | .iter()
|
554 - | .map(|v| v.to_str().unwrap())
|
555 - | )
|
556 - | .unwrap(),
|
557 - | vec![true]
|
558 - | );
|
559 - | assert_eq!(
|
560 - | read_many_primitive::<bool>(
|
561 - | test_request
|
562 - | .headers()
|
563 - | .get_all("X-Bool-Single")
|
564 - | .iter()
|
565 - | .map(|v| v.to_str().unwrap())
|
566 - | )
|
567 - | .unwrap(),
|
568 - | vec![true, false, true, true]
|
569 - | );
|
570 - | assert_eq!(
|
571 - | read_many_primitive::<bool>(
|
572 - | test_request
|
573 - | .headers()
|
574 - | .get_all("X-Bool-Quoted")
|
575 - | .iter()
|
576 - | .map(|v| v.to_str().unwrap())
|
577 - | )
|
578 - | .unwrap(),
|
579 - | vec![true, false, true, true]
|
580 - | );
|
581 - | read_many_primitive::<bool>(
|
582 - | test_request
|
583 - | .headers()
|
584 - | .get_all("X-Bool-Invalid")
|
585 - | .iter()
|
586 - | .map(|v| v.to_str().unwrap()),
|
587 - | )
|
588 - | .expect_err("invalid");
|
589 - | }
|
590 - |
|
591 - | #[test]
|
592 - | fn check_read_many_i16() {
|
593 - | let test_request = http_02x::Request::builder()
|
594 - | .header("X-Multi", "123,456")
|
595 - | .header("X-Multi", "789")
|
596 - | .header("X-Num", "777")
|
597 - | .header("X-Num-Invalid", "12ef3")
|
598 - | .header("X-Num-Single", "1,2,3,-4,5")
|
599 - | .header("X-Num-Quoted", "1, \"2\",3,\"-4\",5")
|
600 - | .body(())
|
601 - | .unwrap();
|
602 - | assert_eq!(
|
603 - | read_many_primitive::<i16>(
|
604 - | test_request
|
605 - | .headers()
|
606 - | .get_all("X-Multi")
|
607 - | .iter()
|
608 - | .map(|v| v.to_str().unwrap())
|
609 - | )
|
610 - | .expect("valid"),
|
611 - | vec![123, 456, 789]
|
612 - | );
|
613 - |
|
614 - | assert_eq!(
|
615 - | read_many_primitive::<i16>(
|
616 - | test_request
|
617 - | .headers()
|
618 - | .get_all("X-Num")
|
619 - | .iter()
|
620 - | .map(|v| v.to_str().unwrap())
|
621 - | )
|
622 - | .unwrap(),
|
623 - | vec![777]
|
624 - | );
|
625 - | assert_eq!(
|
626 - | read_many_primitive::<i16>(
|
627 - | test_request
|
628 - | .headers()
|
629 - | .get_all("X-Num-Single")
|
630 - | .iter()
|
631 - | .map(|v| v.to_str().unwrap())
|
632 - | )
|
633 - | .unwrap(),
|
634 - | vec![1, 2, 3, -4, 5]
|
635 - | );
|
636 - | assert_eq!(
|
637 - | read_many_primitive::<i16>(
|
638 - | test_request
|
639 - | .headers()
|
640 - | .get_all("X-Num-Quoted")
|
641 - | .iter()
|
642 - | .map(|v| v.to_str().unwrap())
|
643 - | )
|
644 - | .unwrap(),
|
645 - | vec![1, 2, 3, -4, 5]
|
646 - | );
|
647 - | read_many_primitive::<i16>(
|
648 - | test_request
|
649 - | .headers()
|
650 - | .get_all("X-Num-Invalid")
|
651 - | .iter()
|
652 - | .map(|v| v.to_str().unwrap()),
|
653 - | )
|
654 - | .expect_err("invalid");
|
655 - | }
|
656 - |
|
657 - | #[test]
|
658 - | fn test_prefix_headers() {
|
659 - | let test_request = Request::try_from(
|
660 - | http_02x::Request::builder()
|
661 - | .header("X-Prefix-A", "123,456")
|
662 - | .header("X-Prefix-B", "789")
|
663 - | .header("X-Prefix-C", "777")
|
664 - | .header("X-Prefix-C", "777")
|
665 - | .body(())
|
666 - | .unwrap(),
|
667 - | )
|
668 - | .unwrap();
|
669 - | let resp: Result<HashMap<String, Vec<i16>>, ParseError> =
|
670 - | headers_for_prefix(test_request.headers().iter().map(|h| h.0), "X-Prefix-")
|
671 - | .map(|(key, header_name)| {
|
672 - | let values = test_request.headers().get_all(header_name);
|
673 - | read_many_primitive(values).map(|v| (key.to_string(), v))
|
674 - | })
|
675 - | .collect();
|
676 - | let resp = resp.expect("valid");
|
677 - | assert_eq!(resp.get("a"), Some(&vec![123_i16, 456_i16]));
|
678 - | }
|
679 - |
|
680 - | #[test]
|
681 - | fn test_quote_header_value() {
|
682 - | assert_eq!("", "e_header_value(""));
|
683 - | assert_eq!("foo", "e_header_value("foo"));
|
684 - | assert_eq!("\" foo\"", "e_header_value(" foo"));
|
685 - | assert_eq!("foo bar", "e_header_value("foo bar"));
|
686 - | assert_eq!("\"foo,bar\"", "e_header_value("foo,bar"));
|
687 - | assert_eq!("\",\"", "e_header_value(","));
|
688 - | assert_eq!("\"\\\"foo\\\"\"", "e_header_value("\"foo\""));
|
689 - | assert_eq!("\"\\\"f\\\\oo\\\"\"", "e_header_value("\"f\\oo\""));
|
690 - | assert_eq!("\"(\"", "e_header_value("("));
|
691 - | assert_eq!("\")\"", "e_header_value(")"));
|
692 - | }
|
693 - |
|
694 - | #[test]
|
695 - | fn test_append_merge_header_maps_with_shared_key() {
|
696 - | let header_name = HeaderName::from_static("some_key");
|
697 - | let left_header_value = HeaderValue::from_static("lhs value");
|
698 - | let right_header_value = HeaderValue::from_static("rhs value");
|
699 - |
|
700 - | let mut left_hand_side_headers = HeaderMap::new();
|
701 - | left_hand_side_headers.insert(header_name.clone(), left_header_value.clone());
|
702 - |
|
703 - | let mut right_hand_side_headers = HeaderMap::new();
|
704 - | right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
|
705 - |
|
706 - | let merged_header_map =
|
707 - | append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
|
708 - | let actual_merged_values: Vec<_> =
|
709 - | merged_header_map.get_all(header_name).into_iter().collect();
|
710 - |
|
711 - | let expected_merged_values = vec![left_header_value, right_header_value];
|
712 - |
|
713 - | assert_eq!(actual_merged_values, expected_merged_values);
|
714 - | }
|
715 - |
|
716 - | #[test]
|
717 - | fn test_append_merge_header_maps_with_multiple_values_in_left_hand_map() {
|
718 - | let header_name = HeaderName::from_static("some_key");
|
719 - | let left_header_value_1 = HeaderValue::from_static("lhs value 1");
|
720 - | let left_header_value_2 = HeaderValue::from_static("lhs_value 2");
|
721 - | let right_header_value = HeaderValue::from_static("rhs value");
|
722 - |
|
723 - | let mut left_hand_side_headers = HeaderMap::new();
|
724 - | left_hand_side_headers.insert(header_name.clone(), left_header_value_1.clone());
|
725 - | left_hand_side_headers.append(header_name.clone(), left_header_value_2.clone());
|
726 - |
|
727 - | let mut right_hand_side_headers = HeaderMap::new();
|
728 - | right_hand_side_headers.insert(header_name.clone(), right_header_value.clone());
|
729 - |
|
730 - | let merged_header_map =
|
731 - | append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
|
732 - | let actual_merged_values: Vec<_> =
|
733 - | merged_header_map.get_all(header_name).into_iter().collect();
|
734 - |
|
735 - | let expected_merged_values =
|
736 - | vec![left_header_value_1, left_header_value_2, right_header_value];
|
737 - |
|
738 - | assert_eq!(actual_merged_values, expected_merged_values);
|
739 - | }
|
740 - |
|
741 - | #[test]
|
742 - | fn test_append_merge_header_maps_with_empty_left_hand_map() {
|
743 - | let header_name = HeaderName::from_static("some_key");
|
744 - | let right_header_value_1 = HeaderValue::from_static("rhs value 1");
|
745 - | let right_header_value_2 = HeaderValue::from_static("rhs_value 2");
|
746 - |
|
747 - | let left_hand_side_headers = HeaderMap::new();
|
748 - |
|
749 - | let mut right_hand_side_headers = HeaderMap::new();
|
750 - | right_hand_side_headers.insert(header_name.clone(), right_header_value_1.clone());
|
751 - | right_hand_side_headers.append(header_name.clone(), right_header_value_2.clone());
|
752 - |
|
753 - | let merged_header_map =
|
754 - | append_merge_header_maps(left_hand_side_headers, right_hand_side_headers);
|
755 - | let actual_merged_values: Vec<_> =
|
756 - | merged_header_map.get_all(header_name).into_iter().collect();
|
757 - |
|
758 - | let expected_merged_values = vec![right_header_value_1, right_header_value_2];
|
759 - |
|
760 - | assert_eq!(actual_merged_values, expected_merged_values);
|
761 - | }
|
762 - | }
|