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