1use {
11 crate::{
12 auth::{SigV4Authenticator, SigV4AuthenticatorBuilder},
13 chronoutil::ParseISO8601,
14 crypto::{sha256, sha256_hex, SHA256_OUTPUT_LEN},
15 SignatureError, SignatureOptions,
16 },
17 bytes::Bytes,
18 chrono::{offset::FixedOffset, DateTime, Utc},
19 encoding_rs::UTF_8,
20 http::{
21 header::{HeaderMap, HeaderValue},
22 request::Parts,
23 uri::Uri,
24 },
25 lazy_static::lazy_static,
26 log::trace,
27 qualifier_attr::qualifiers,
28 regex::Regex,
29 std::{
30 borrow::Cow,
31 collections::HashMap,
32 fmt::{Debug, Formatter, Result as FmtResult},
33 str::from_utf8,
34 },
35};
36
37const APPLICATION_X_WWW_FORM_URLENCODED: &str = "application/x-www-form-urlencoded";
39
40const AUTHORIZATION: &str = "authorization";
42
43const AWS4_HMAC_SHA256: &str = "AWS4-HMAC-SHA256";
45
46const AWS4_HMAC_SHA256_BYTES: &[u8] = b"AWS4-HMAC-SHA256";
48
49const CHARSET: &str = "charset";
51
52const CONTENT_TYPE: &str = "content-type";
54
55const CREDENTIAL: &[u8] = b"Credential";
57
58const DATE: &str = "date";
60
61const HEX_DIGITS_UPPER: [u8; 16] =
63 [b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E', b'F'];
64
65const MSG_AUTH_HEADER_REQ_CREDENTIAL: &str = "Authorization header requires 'Credential' parameter.";
67
68const MSG_AUTH_HEADER_REQ_DATE: &str =
70 "Authorization header requires existence of either a 'X-Amz-Date' or a 'Date' header.";
71
72const MSG_AUTH_HEADER_REQ_SIGNATURE: &str = "Authorization header requires 'Signature' parameter.";
74
75const MSG_AUTH_HEADER_REQ_SIGNED_HEADERS: &str = "Authorization header requires 'SignedHeaders' parameter.";
77
78const MSG_HOST_AUTHORITY_MUST_BE_SIGNED: &str =
80 "'Host' or ':authority' must be a 'SignedHeader' in the AWS Authorization.";
81
82const MSG_ILLEGAL_HEX_CHAR: &str = "Illegal hex character in escape % pattern: %";
84
85const MSG_INCOMPLETE_TRAILING_ESCAPE: &str = "Incomplete trailing escape % sequence";
87
88const MSG_QUERY_STRING_MUST_INCLUDE_CREDENTIAL: &str = "AWS query-string parameters must include 'X-Amz-Credential'.";
90
91const MSG_QUERY_STRING_MUST_INCLUDE_SIGNATURE: &str = "AWS query-string parameters must include 'X-Amz-Signature'.";
93
94const MSG_QUERY_STRING_MUST_INCLUDE_SIGNED_HEADERS: &str =
96 "AWS query-string parameters must include 'X-Amz-SignedHeaders'.";
97
98const MSG_QUERY_STRING_MUST_INCLUDE_DATE: &str = "AWS query-string parameters must include 'X-Amz-Date'.";
100
101const MSG_REEXAMINE_QUERY_STRING_PARAMS: &str = "Re-examine the query-string parameters.";
103
104const MSG_REQUEST_MISSING_AUTH_TOKEN: &str = "Request is missing Authentication Token";
106
107const MSG_UNSUPPORTED_ALGORITHM: &str = "Unsupported AWS 'algorithm': ";
109
110const SIGNATURE: &[u8] = b"Signature";
112
113const SIGNED_HEADERS: &[u8] = b"SignedHeaders";
115
116const X_AMZ_ALGORITHM: &str = "X-Amz-Algorithm";
118
119const X_AMZ_CREDENTIAL: &str = "X-Amz-Credential";
121
122const X_AMZ_DATE: &str = "X-Amz-Date";
124
125const X_AMZ_DATE_LOWER: &str = "x-amz-date";
127
128const X_AMZ_SECURITY_TOKEN: &str = "X-Amz-Security-Token";
130
131const X_AMZ_SECURITY_TOKEN_LOWER: &str = "x-amz-security-token";
133
134const X_AMZ_SIGNATURE: &str = "X-Amz-Signature";
136
137const X_AMZ_SIGNED_HEADERS: &str = "X-Amz-SignedHeaders";
139
140lazy_static! {
141 static ref MULTISLASH: Regex = Regex::new("//+").unwrap();
143
144 static ref MULTISPACE: Regex = Regex::new(" +").unwrap();
146
147 static ref AWS4_HMAC_SHA256_RE: Regex = Regex::new(r"\s*AWS4-HMAC-SHA256(?:\s+|$)").unwrap();
149}
150
151#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
153#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
154#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
155#[derive(Debug)]
156struct AuthParams {
157 pub builder: SigV4AuthenticatorBuilder,
159
160 pub signed_headers: Vec<String>,
162
163 pub timestamp_str: String,
165}
166
167#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
175#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
176#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
177#[derive(Clone)]
178struct CanonicalRequest {
179 request_method: String,
181
182 canonical_path: String,
184
185 query_parameters: HashMap<String, Vec<String>>,
190
191 headers: HashMap<String, Vec<Vec<u8>>>,
195
196 body_sha256: String,
198}
199
200impl CanonicalRequest {
201 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
203 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
204 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
205 fn from_request_parts(
206 mut parts: Parts,
207 mut body: Bytes,
208 options: SignatureOptions,
209 ) -> Result<(Self, Parts, Bytes), SignatureError> {
210 let canonical_path = canonicalize_uri_path(parts.uri.path(), options.s3)?;
211 let content_type = get_content_type_and_charset(&parts.headers);
212 let mut query_parameters = query_string_to_normalized_map(parts.uri.query().unwrap_or(""))?;
213
214 if options.url_encode_form {
215 if let Some(content_type) = content_type {
217 if content_type.content_type == APPLICATION_X_WWW_FORM_URLENCODED {
218 trace!("Body is application/x-www-form-urlencoded; converting to query parameters");
219
220 let encoding = match &content_type.charset {
221 Some(charset) => match encoding_rs::Encoding::for_label(charset.as_bytes()) {
222 Some(encoding) => encoding,
223 None => {
224 return Err(SignatureError::InvalidBodyEncoding(format!(
225 "application/x-www-form-urlencoded body uses unsupported charset '{}'",
226 charset
227 )))
228 }
229 },
230 None => {
231 trace!("Falling back to UTF-8 for application/x-www-form-urlencoded body");
232 UTF_8
233 }
234 };
235
236 let (body_query, _, has_errors) = encoding.decode(&body);
237 if has_errors {
238 return Err(SignatureError::InvalidBodyEncoding(format!(
239 "Invalid body data encountered parsing application/x-www-form-urlencoded with charset '{}'",
240 encoding.name()
241 )));
242 }
243
244 query_parameters.extend(query_string_to_normalized_map(&body_query)?);
245 let qs = canonicalize_query_to_string(&query_parameters);
247 trace!("Rebuilding URI with new query string: {}", qs);
248
249 let mut pq = canonical_path.clone();
250 if !qs.is_empty() {
251 pq.push('?');
252 pq.push_str(&qs);
253 }
254
255 parts.uri =
256 Uri::builder().path_and_query(pq).build().expect("failed to rebuild URI with new query string");
257 body = Bytes::from("");
258 }
259 }
260 }
261
262 let headers = normalize_headers(&parts.headers);
263 let body_sha256 = sha256_hex(body.as_ref());
264
265 Ok((
266 CanonicalRequest {
267 request_method: parts.method.to_string(),
268 canonical_path,
269 query_parameters,
270 headers,
271 body_sha256,
272 },
273 parts,
274 body,
275 ))
276 }
277
278 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
280 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
281 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
282 #[inline(always)]
283 fn request_method(&self) -> &str {
284 &self.request_method
285 }
286
287 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
289 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
290 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
291 #[inline(always)]
292 fn canonical_path(&self) -> &str {
293 &self.canonical_path
294 }
295
296 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
300 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
301 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
302 #[inline(always)]
303 fn query_parameters(&self) -> &HashMap<String, Vec<String>> {
304 &self.query_parameters
305 }
306
307 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
309 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
310 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
311 #[inline(always)]
312 fn headers(&self) -> &HashMap<String, Vec<Vec<u8>>> {
313 &self.headers
314 }
315
316 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
318 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
319 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
320 #[inline(always)]
321 fn body_sha256(&self) -> &str {
322 &self.body_sha256
323 }
324
325 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
327 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
328 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
329 fn canonical_query_string(&self) -> String {
330 canonicalize_query_to_string(&self.query_parameters)
331 }
332
333 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
336 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
337 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
338 fn canonical_request(&self, signed_headers: &Vec<String>) -> Vec<u8> {
339 let mut result = Vec::with_capacity(1024);
340 result.extend(self.request_method().as_bytes());
341 result.push(b'\n');
342 result.extend(self.canonical_path().as_bytes());
343 result.push(b'\n');
344 result.extend(self.canonical_query_string().as_bytes());
345 result.push(b'\n');
346
347 for header in signed_headers {
348 let values = self.headers.get(header);
349 if let Some(values) = values {
350 for (i, value) in values.iter().enumerate() {
351 if i == 0 {
352 result.extend(header.as_bytes());
353 result.push(b':');
354 } else {
355 result.push(b',');
356 }
357 result.extend(value);
358 }
359 result.push(b'\n')
360 }
361 }
362
363 result.push(b'\n');
364 result.extend(signed_headers.join(";").as_bytes());
365 result.push(b'\n');
366 result.extend(self.body_sha256().as_bytes());
367
368 trace!("Canonical request:\n{}", String::from_utf8_lossy(&result));
369
370 result
371 }
372
373 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
375 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
376 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
377 fn canonical_request_sha256(&self, signed_headers: &Vec<String>) -> [u8; SHA256_OUTPUT_LEN] {
378 let canonical_request = self.canonical_request(signed_headers);
379 let result_digest = sha256(&canonical_request);
380 let result_slice = result_digest.as_ref();
381 assert!(result_slice.len() == SHA256_OUTPUT_LEN);
382 let mut result: [u8; SHA256_OUTPUT_LEN] = [0; SHA256_OUTPUT_LEN];
383 result.as_mut_slice().clone_from_slice(result_slice);
384 result
385 }
386
387 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
390 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
391 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
392 fn get_authenticator<S>(&self, signed_header_requirements: &S) -> Result<SigV4Authenticator, SignatureError>
393 where
394 S: SignedHeaderRequirements,
395 {
396 let auth_params = self.get_auth_parameters(signed_header_requirements)?;
397 self.get_authenticator_from_auth_parameters(auth_params)
398 }
399
400 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
402 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
403 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
404 fn get_authenticator_from_auth_parameters(
405 &self,
406 auth_params: AuthParams,
407 ) -> Result<SigV4Authenticator, SignatureError> {
408 let timestamp_str = auth_params.timestamp_str.as_str();
410 let timestamp = DateTime::<FixedOffset>::parse_from_iso8601(timestamp_str)
411 .map_err(|_| {
412 SignatureError::IncompleteSignature(format!(
413 "Date must be in ISO-8601 'basic format'. Got '{}'. See http://en.wikipedia.org/wiki/ISO_8601",
414 auth_params.timestamp_str
415 ))
416 })?
417 .with_timezone(&Utc);
418 let mut builder = auth_params.builder;
419 builder.request_timestamp(timestamp);
420
421 let signed_headers = auth_params.signed_headers;
422
423 builder.canonical_request_sha256(self.canonical_request_sha256(&signed_headers));
425
426 Ok(builder.build().expect("all fields should be set"))
427 }
428
429 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
432 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
433 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
434 fn get_auth_parameters<S>(&self, signed_header_requirements: &S) -> Result<AuthParams, SignatureError>
435 where
436 S: SignedHeaderRequirements,
437 {
438 let auth_header = self.headers().get(AUTHORIZATION);
439 let sig_algs = self.query_parameters().get(X_AMZ_ALGORITHM);
440
441 let params = match (auth_header, sig_algs) {
443 (Some(auth_header), None) => self.get_auth_parameters_from_auth_header(&auth_header[0])?,
445 (None, Some(sig_algs)) => self.get_auth_parameters_from_query_parameters(&sig_algs[0])?,
447 (Some(_), Some(_)) => return Err(SignatureError::SignatureDoesNotMatch(None)),
448 (None, None) => {
449 return Err(SignatureError::MissingAuthenticationToken(MSG_REQUEST_MISSING_AUTH_TOKEN.to_string()))
450 }
451 };
452
453 let mut found_host = false;
455 for header in ¶ms.signed_headers {
456 if header == "host" || header == ":authority" {
457 found_host = true;
458 break;
459 }
460 }
461 if !found_host {
462 return Err(SignatureError::SignatureDoesNotMatch(Some(MSG_HOST_AUTHORITY_MUST_BE_SIGNED.to_string())));
463 }
464
465 for header in signed_header_requirements.always_present() {
466 let header_lower = header.to_lowercase();
467 if !params.signed_headers.contains(&header_lower) {
468 return Err(SignatureError::SignatureDoesNotMatch(Some(format!(
469 "'{}' must be a 'SignedHeader' in the AWS Authorization.",
470 header
471 ))));
472 }
473 }
474
475 for header in signed_header_requirements.if_in_request() {
476 let header_lower = header.to_lowercase();
477 if self.headers.contains_key(&header_lower) && !params.signed_headers.contains(&header_lower) {
478 return Err(SignatureError::SignatureDoesNotMatch(Some(format!(
479 "'{}' must be a 'SignedHeader' in the AWS Authorization.",
480 header
481 ))));
482 }
483 }
484
485 for header in signed_header_requirements.prefixes() {
486 let header_lower = header.to_lowercase();
487 for http_header in self.headers.keys() {
488 if http_header.starts_with(&header_lower) && !params.signed_headers.contains(http_header) {
489 return Err(SignatureError::SignatureDoesNotMatch(Some(format!(
490 "'{}' must be a 'SignedHeader' in the AWS Authorization.",
491 http_header
492 ))));
493 }
494 }
495 }
496
497 Ok(params)
498 }
499
500 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
503 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
504 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
505 fn get_auth_parameters_from_auth_header<'a>(&'a self, auth_header: &'a [u8]) -> Result<AuthParams, SignatureError> {
506 let auth_header = trim_ascii(auth_header);
508
509 let parts = auth_header.splitn(2, |c| *c == b' ').collect::<Vec<&'a [u8]>>();
511 let algorithm = parts[0];
512 if algorithm != AWS4_HMAC_SHA256_BYTES {
513 return Err(SignatureError::IncompleteSignature(format!(
514 "{}'{}'.",
515 MSG_UNSUPPORTED_ALGORITHM,
516 String::from_utf8_lossy(algorithm)
517 )));
518 }
519
520 let parameters = if parts.len() > 1 {
521 parts[1]
522 } else {
523 b""
524 };
525
526 let mut parameter_map = HashMap::new();
528 for parameter_untrimmed in parameters.split(|c| *c == b',') {
529 let parameter = trim_ascii(parameter_untrimmed);
530
531 if parameter.is_empty() {
533 continue;
534 }
535
536 let parts = parameter.splitn(2, |c| *c == b'=').collect::<Vec<&'a [u8]>>();
537
538 if parts.len() != 2 {
540 return Err(SignatureError::IncompleteSignature(format!(
541 "'{}' not a valid key=value pair (missing equal-sign) in Authorization header: '{}'",
542 latin1_to_string(parameter),
543 latin1_to_string(auth_header)
544 )));
545 }
546
547 parameter_map.insert(parts[0], parts[1]);
549 }
550
551 let mut missing_messages = Vec::new();
553 let mut builder = SigV4Authenticator::builder();
554
555 if let Some(credential) = parameter_map.get(CREDENTIAL) {
556 builder.credential(latin1_to_string(credential));
557 } else {
558 missing_messages.push(MSG_AUTH_HEADER_REQ_CREDENTIAL);
559 }
560
561 if let Some(signature) = parameter_map.get(SIGNATURE) {
562 builder.signature(latin1_to_string(signature));
563 } else {
564 missing_messages.push(MSG_AUTH_HEADER_REQ_SIGNATURE);
565 }
566
567 let mut signed_headers = if let Some(signed_headers) = parameter_map.get(SIGNED_HEADERS) {
568 signed_headers.split(|c| *c == b';').map(latin1_to_string).collect()
569 } else {
570 missing_messages.push(MSG_AUTH_HEADER_REQ_SIGNED_HEADERS);
571 Vec::new()
572 };
573 signed_headers.sort();
574
575 let mut timestamp_str = None;
576
577 if let Some(date) = self.headers.get(X_AMZ_DATE_LOWER) {
578 timestamp_str = Some(latin1_to_string(&date[0]));
580 } else if let Some(date) = self.headers.get(DATE) {
581 timestamp_str = Some(latin1_to_string(&date[0]));
583 } else {
584 missing_messages.push(MSG_AUTH_HEADER_REQ_DATE);
585 }
586
587 if !missing_messages.is_empty() {
588 return Err(SignatureError::IncompleteSignature(format!(
589 "{} Authorization={}",
590 missing_messages.join(" "),
591 latin1_to_string(algorithm)
592 )));
593 }
594
595 if let Some(token) = self.headers.get(X_AMZ_SECURITY_TOKEN_LOWER) {
597 builder.session_token(latin1_to_string(&token[0]));
598 }
599
600 let timestamp_str = timestamp_str.expect("date_str should be set");
602 Ok(AuthParams {
603 builder,
604 signed_headers,
605 timestamp_str,
606 })
607 }
608
609 #[cfg_attr(doc, doc(cfg(feature = "unstable")))]
612 #[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
613 #[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
614 fn get_auth_parameters_from_query_parameters(&self, query_alg: &str) -> Result<AuthParams, SignatureError> {
615 if query_alg != AWS4_HMAC_SHA256 {
617 return Err(SignatureError::MissingAuthenticationToken(MSG_REQUEST_MISSING_AUTH_TOKEN.to_string()));
618 }
619
620 let mut missing_messages = Vec::new();
621 let mut builder = SigV4Authenticator::builder();
622
623 if let Some(credential) = self.query_parameters.get(X_AMZ_CREDENTIAL) {
625 builder.credential(credential[0].clone());
626 } else {
627 missing_messages.push(MSG_QUERY_STRING_MUST_INCLUDE_CREDENTIAL);
628 }
629
630 if let Some(signature) = self.query_parameters.get(X_AMZ_SIGNATURE) {
631 builder.signature(signature[0].clone());
632 } else {
633 missing_messages.push(MSG_QUERY_STRING_MUST_INCLUDE_SIGNATURE);
634 }
635
636 let mut signed_headers = if let Some(signed_headers) = self.query_parameters.get(X_AMZ_SIGNED_HEADERS) {
637 let unescaped_signed_headers = unescape_uri_encoding(&signed_headers[0]);
638 unescaped_signed_headers.split(';').map(|s| s.to_string()).collect::<Vec<String>>()
639 } else {
640 missing_messages.push(MSG_QUERY_STRING_MUST_INCLUDE_SIGNED_HEADERS);
641 Vec::new()
642 };
643 signed_headers.sort();
644
645 let timestamp_str = self.query_parameters.get(X_AMZ_DATE);
646 if timestamp_str.is_none() {
647 missing_messages.push(MSG_QUERY_STRING_MUST_INCLUDE_DATE);
648 }
649
650 if !missing_messages.is_empty() {
651 return Err(SignatureError::IncompleteSignature(format!(
652 "{} {}",
653 missing_messages.join(" "),
654 MSG_REEXAMINE_QUERY_STRING_PARAMS
655 )));
656 }
657
658 if let Some(token) = self.query_parameters.get(X_AMZ_SECURITY_TOKEN) {
660 builder.session_token(token[0].clone());
661 }
662
663 let timestamp_str = timestamp_str.expect("date_str should be set")[0].clone();
664 Ok(AuthParams {
665 builder,
666 signed_headers,
667 timestamp_str,
668 })
669 }
670}
671
672impl Debug for CanonicalRequest {
673 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
674 let headers = debug_headers(&self.headers);
675
676 f.debug_struct("CanonicalRequest")
677 .field("request_method", &self.request_method)
678 .field("canonical_path", &self.canonical_path)
679 .field("query_parameters", &self.query_parameters)
680 .field("headers", &headers)
681 .field("body_sha256", &self.body_sha256)
682 .finish()
683 }
684}
685
686#[derive(Debug, Clone, PartialEq, Eq)]
688#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
689#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
690#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
691struct ContentTypeCharset {
692 pub content_type: String,
694
695 pub charset: Option<String>,
697}
698
699pub trait SignedHeaderRequirements {
702 fn always_present(&self) -> &[Cow<'_, str>];
704
705 fn if_in_request(&self) -> &[Cow<'_, str>];
707
708 fn prefixes(&self) -> &[Cow<'_, str>];
710}
711
712#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
714pub struct SliceSignedHeaderRequirements<'a, 'b, 'c> {
715 always_present: &'a [Cow<'a, str>],
717
718 if_in_request: &'b [Cow<'b, str>],
720
721 prefixes: &'c [Cow<'c, str>],
723}
724
725impl<'a, 'b, 'c> SignedHeaderRequirements for SliceSignedHeaderRequirements<'a, 'b, 'c> {
726 #[inline(always)]
727 fn always_present(&self) -> &[Cow<'_, str>] {
728 self.always_present
729 }
730
731 #[inline(always)]
732 fn if_in_request(&self) -> &[Cow<'_, str>] {
733 self.if_in_request
734 }
735
736 #[inline(always)]
737 fn prefixes(&self) -> &[Cow<'_, str>] {
738 self.prefixes
739 }
740}
741
742impl<'a, 'b, 'c> SliceSignedHeaderRequirements<'a, 'b, 'c> {
743 pub const fn new(
745 always_present: &'a [Cow<'a, str>],
746 if_in_request: &'b [Cow<'b, str>],
747 prefixes: &'c [Cow<'c, str>],
748 ) -> Self {
749 SliceSignedHeaderRequirements {
750 always_present,
751 if_in_request,
752 prefixes,
753 }
754 }
755}
756
757pub type ConstSignedHeaderRequirements = SliceSignedHeaderRequirements<'static, 'static, 'static>;
759
760pub const NO_ADDITIONAL_SIGNED_HEADERS: ConstSignedHeaderRequirements =
763 ConstSignedHeaderRequirements::new(&[], &[], &[]);
764
765#[derive(Clone, Debug, Default, PartialEq, Eq)]
767pub struct VecSignedHeaderRequirements {
768 always_present: Vec<Cow<'static, str>>,
770
771 if_in_request: Vec<Cow<'static, str>>,
773
774 prefixes: Vec<Cow<'static, str>>,
776}
777
778impl SignedHeaderRequirements for VecSignedHeaderRequirements {
779 #[inline(always)]
780 fn always_present(&self) -> &[Cow<'_, str>] {
781 &self.always_present
782 }
783
784 #[inline(always)]
785 fn if_in_request(&self) -> &[Cow<'_, str>] {
786 &self.if_in_request
787 }
788
789 #[inline(always)]
790 fn prefixes(&self) -> &[Cow<'_, str>] {
791 &self.prefixes
792 }
793}
794
795impl VecSignedHeaderRequirements {
796 pub fn new<A, B, C>(always_present: &[&A], if_in_request: &[&B], prefixes: &[&C]) -> Self
798 where
799 for<'a> &'a A: Into<String>,
800 for<'b> &'b B: Into<String>,
801 for<'c> &'c C: Into<String>,
802 A: ?Sized,
803 B: ?Sized,
804 C: ?Sized,
805 {
806 let always_present = always_present.iter().map(|s| Cow::Owned((*s).into())).collect();
807 let if_in_request = if_in_request.iter().map(|s| Cow::Owned((*s).into())).collect();
808 let prefixes = prefixes.iter().map(|s| Cow::Owned((*s).into())).collect();
809
810 VecSignedHeaderRequirements {
811 always_present,
812 if_in_request,
813 prefixes,
814 }
815 }
816
817 pub fn add_always_present(&mut self, header: &str) {
819 let header_lower = header.to_ascii_lowercase();
820
821 for h in self.always_present.iter() {
822 if h == &header_lower {
823 return;
824 }
825 }
826
827 self.always_present.push(Cow::Owned(header.to_string()));
828 }
829
830 pub fn add_if_in_request(&mut self, header: &str) {
832 let header_lower = header.to_ascii_lowercase();
833
834 for h in self.if_in_request.iter() {
835 if h == &header_lower {
836 return;
837 }
838 }
839
840 self.if_in_request.push(Cow::Owned(header.to_string()));
841 }
842
843 pub fn add_prefix(&mut self, prefix: &str) {
846 let prefix_lower = prefix.to_ascii_lowercase();
847
848 for h in self.prefixes.iter() {
849 if h == &prefix_lower {
850 return;
851 }
852 }
853
854 self.prefixes.push(Cow::Owned(prefix.to_string()));
855 }
856
857 pub fn remove_always_present(&mut self, header: &str) {
859 let header = header.to_ascii_lowercase();
860 self.always_present.retain(|h| h.to_ascii_lowercase() != header);
861 }
862
863 pub fn remove_if_in_request(&mut self, header: &str) {
865 let header = header.to_ascii_lowercase();
866 self.if_in_request.retain(|h| h.to_ascii_lowercase() != header);
867 }
868
869 pub fn remove_prefix(&mut self, prefix: &str) {
872 let prefix = prefix.to_ascii_lowercase();
873 self.prefixes.retain(|h| h.to_ascii_lowercase() != prefix);
874 }
875}
876
877#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
880#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
881#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
882enum UriElement {
883 Path,
885
886 Query,
888}
889
890#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
892#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
893#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
894pub fn canonicalize_query_to_string(query_parameters: &HashMap<String, Vec<String>>) -> String {
895 let mut results = Vec::new();
896
897 for (key, values) in query_parameters.iter() {
898 if key != X_AMZ_SIGNATURE {
900 for value in values.iter() {
901 results.push(format!("{}={}", key, value));
902 }
903 }
904 }
905
906 results.sort_unstable();
907 results.join("&")
908}
909
910#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
913#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
914#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
915pub fn canonicalize_uri_path(uri_path: &str, s3: bool) -> Result<String, SignatureError> {
916 if uri_path.is_empty() || uri_path == "/" {
918 return Ok("/".to_string());
919 }
920
921 if !uri_path.starts_with('/') {
923 return Err(SignatureError::InvalidURIPath(format!("Path is not absolute: {}", uri_path)));
924 }
925
926 let uri_path = if s3 {
927 Cow::Borrowed(uri_path)
928 } else {
929 MULTISLASH.replace_all(uri_path, "/")
931 };
932
933 let mut components: Vec<String> = uri_path.split('/').map(|s| s.to_string()).collect();
935 let mut i = 1; while i < components.len() {
937 let component = normalize_uri_path_component(&components[i])?;
938
939 if component == "." && !s3 {
940 components.remove(i);
942
943 } else if component == ".." && !s3 {
945 if i <= 1 {
948 return Err(SignatureError::InvalidURIPath(format!(
950 "Relative path entry '..' navigates above root: {}",
951 uri_path
952 )));
953 }
954
955 components.remove(i - 1);
956 components.remove(i - 1);
957
958 i -= 1;
960 } else {
961 components[i] = component;
963 i += 1;
964 }
965 }
966
967 assert!(!components.is_empty());
968 match components.len() {
969 1 => Ok("/".to_string()),
970 _ => Ok(components.join("/")),
971 }
972}
973
974#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
976#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
977#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
978fn debug_headers(headers: &HashMap<String, Vec<Vec<u8>>>) -> String {
979 use std::io::Write;
980 let mut result = Vec::new();
981 for (key, values) in headers.iter() {
982 for value in values {
983 match String::from_utf8(value.clone()) {
984 Ok(s) => writeln!(result, "{}: {}", key, s).unwrap(),
985 Err(_) => writeln!(result, "{}: {:?}", key, value).unwrap(),
986 }
987 }
988 }
989
990 if result.is_empty() {
991 return String::new();
992 }
993
994 let result_except_last = &result[..result.len() - 1];
996 String::from_utf8_lossy(result_except_last).to_string()
997}
998
999#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1001#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1002#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1003fn get_content_type_and_charset(headers: &HeaderMap<HeaderValue>) -> Option<ContentTypeCharset> {
1004 let content_type_opts = match headers.get(CONTENT_TYPE) {
1005 Some(value) => value.as_ref(),
1006 None => return None,
1007 };
1008
1009 let mut parts = content_type_opts.split(|c| *c == b';').map(trim_ascii);
1010 let content_type = latin1_to_string(parts.next().expect("split always returns at least one element"));
1011
1012 for option in parts {
1013 let opt_trim = trim_ascii(option);
1014 let mut opt_parts = opt_trim.splitn(2, |c| *c == b'=');
1015
1016 let opt_name = opt_parts.next().unwrap();
1017 if latin1_to_string(opt_name).to_lowercase() == CHARSET {
1018 if let Some(opt_value) = opt_parts.next() {
1019 return Some(ContentTypeCharset {
1020 content_type,
1021 charset: Some(latin1_to_string(opt_value)),
1022 });
1023 }
1024 }
1025 }
1026
1027 Some(ContentTypeCharset {
1028 content_type,
1029 charset: None,
1030 })
1031}
1032
1033#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1036#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1037#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1038#[inline(always)]
1039pub fn is_rfc3986_unreserved(c: u8) -> bool {
1040 c.is_ascii_alphanumeric() || c == b'-' || c == b'.' || c == b'_' || c == b'~'
1041}
1042
1043#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1045#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1046#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1047pub fn latin1_to_string(bytes: &[u8]) -> String {
1048 let mut result = String::new();
1049 for b in bytes {
1050 result.push(*b as char);
1051 }
1052 result
1053}
1054
1055#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1057#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1058#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1059pub fn normalize_headers(headers: &HeaderMap<HeaderValue>) -> HashMap<String, Vec<Vec<u8>>> {
1060 let mut result = HashMap::<String, Vec<Vec<u8>>>::new();
1061 for (key, value) in headers.iter() {
1062 let key = key.as_str().to_lowercase();
1063 let value = normalize_header_value(value.as_bytes());
1064 result.entry(key).or_default().push(value);
1065 }
1066
1067 result
1068}
1069
1070#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1072#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1073#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1074pub fn normalize_header_value(value: &[u8]) -> Vec<u8> {
1075 let mut result = Vec::with_capacity(value.len());
1076
1077 let mut last_was_space = true;
1079
1080 for c in value {
1081 if *c == b' ' {
1082 if !last_was_space {
1083 result.push(b' ');
1084 last_was_space = true;
1085 }
1086 } else {
1087 result.push(*c);
1088 last_was_space = false;
1089 }
1090 }
1091
1092 if last_was_space {
1093 while result.last() == Some(&b' ') {
1095 result.pop();
1096 }
1097 }
1098
1099 result
1100}
1101
1102#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1104#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1105#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1106pub fn normalize_query_string_element(element: &str) -> Result<String, SignatureError> {
1107 normalize_uri_element(element, UriElement::Query)
1108}
1109
1110#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1112#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1113#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1114pub fn normalize_uri_path_component(path: &str) -> Result<String, SignatureError> {
1115 normalize_uri_element(path, UriElement::Path)
1116}
1117
1118#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1127#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1128#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1129fn normalize_uri_element(uri_el: &str, uri_el_type: UriElement) -> Result<String, SignatureError> {
1130 let path_component = uri_el.as_bytes();
1131 let mut i = 0;
1132 let result = &mut Vec::<u8>::new();
1133
1134 while i < path_component.len() {
1135 let c = path_component[i];
1136
1137 if is_rfc3986_unreserved(c) {
1138 result.push(c);
1139 i += 1;
1140 } else if c == b'%' {
1141 if i + 2 >= path_component.len() {
1142 return Err(match uri_el_type {
1144 UriElement::Path => {
1145 SignatureError::InvalidURIPath(MSG_INCOMPLETE_TRAILING_ESCAPE.to_string())
1147 }
1148 UriElement::Query => {
1149 SignatureError::MalformedQueryString(MSG_INCOMPLETE_TRAILING_ESCAPE.to_string())
1151 }
1152 });
1153 }
1154
1155 let hex_digits = &path_component[i + 1..i + 3];
1156 match hex::decode(hex_digits) {
1157 Ok(value) => {
1158 assert_eq!(value.len(), 1);
1159 let c = value[0];
1160
1161 if is_rfc3986_unreserved(c) {
1162 result.push(c);
1163 } else {
1164 result.push(b'%');
1166 result.extend(u8_to_upper_hex(c));
1167 }
1168 i += 3;
1169 }
1170 Err(_) => {
1171 let message = format!("{}{}{}", MSG_ILLEGAL_HEX_CHAR, hex_digits[0] as char, hex_digits[1] as char);
1172 return Err(match uri_el_type {
1173 UriElement::Path => SignatureError::InvalidURIPath(message),
1175 UriElement::Query => SignatureError::MalformedQueryString(message),
1177 });
1178 }
1179 }
1180 } else if c == b'+' {
1181 result.extend_from_slice(b"%20");
1183 i += 1;
1184 } else {
1185 result.push(b'%');
1187 result.extend(u8_to_upper_hex(c));
1188 i += 1;
1189 }
1190 }
1191
1192 Ok(from_utf8(result.as_slice()).unwrap().to_string())
1193}
1194
1195#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1201#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1202#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1203pub fn query_string_to_normalized_map(query_string: &str) -> Result<HashMap<String, Vec<String>>, SignatureError> {
1204 if query_string.is_empty() {
1205 return Ok(HashMap::new());
1206 }
1207
1208 let components = query_string.split('&');
1210 let mut result = HashMap::<String, Vec<String>>::new();
1211
1212 for component in components {
1213 if component.is_empty() {
1214 continue;
1216 }
1217
1218 let parts: Vec<&str> = component.splitn(2, '=').collect();
1220 let key = parts[0];
1221 let value = if parts.len() > 1 {
1222 parts[1]
1223 } else {
1224 ""
1225 };
1226
1227 let norm_key = normalize_query_string_element(key)?;
1229 let norm_value = normalize_query_string_element(value)?;
1230
1231 if let Some(result_value) = result.get_mut(&norm_key) {
1233 result_value.push(norm_value);
1234 } else {
1235 result.insert(norm_key, vec![norm_value]);
1236 }
1237 }
1238
1239 Ok(result)
1240}
1241
1242#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1249#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1250#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1251pub const fn trim_ascii_start(bytes: &[u8]) -> &[u8] {
1252 let mut bytes = bytes;
1253 while let [first, rest @ ..] = bytes {
1256 if first.is_ascii_whitespace() {
1257 bytes = rest;
1258 } else {
1259 break;
1260 }
1261 }
1262 bytes
1263}
1264
1265#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1272#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1273#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1274pub const fn trim_ascii_end(bytes: &[u8]) -> &[u8] {
1275 let mut bytes = bytes;
1276 while let [rest @ .., last] = bytes {
1279 if last.is_ascii_whitespace() {
1280 bytes = rest;
1281 } else {
1282 break;
1283 }
1284 }
1285 bytes
1286}
1287
1288#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1295#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1296#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1297pub const fn trim_ascii(bytes: &[u8]) -> &[u8] {
1298 trim_ascii_end(trim_ascii_start(bytes))
1299}
1300
1301#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1303#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1304#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1305#[inline(always)]
1306pub const fn u8_to_upper_hex(b: u8) -> [u8; 2] {
1307 let result: [u8; 2] = [HEX_DIGITS_UPPER[((b >> 4) & 0xf) as usize], HEX_DIGITS_UPPER[(b & 0xf) as usize]];
1308 result
1309}
1310
1311#[cfg_attr(doc, doc(cfg(feature = "unstable")))]
1315#[cfg_attr(any(doc, feature = "unstable"), qualifiers(pub))]
1316#[cfg_attr(not(any(doc, feature = "unstable")), qualifiers(pub(crate)))]
1317pub fn unescape_uri_encoding(s: &str) -> String {
1318 let mut result = String::with_capacity(s.len());
1319 let mut chars = s.bytes();
1320
1321 while let Some(c) = chars.next() {
1322 if c == b'%' {
1323 let mut hex_digits = [0u8; 2];
1324 hex_digits[0] = chars.next().expect(MSG_INCOMPLETE_TRAILING_ESCAPE);
1325 hex_digits[1] = chars.next().expect(MSG_INCOMPLETE_TRAILING_ESCAPE);
1326 match u8::from_str_radix(from_utf8(&hex_digits).unwrap(), 16) {
1327 Ok(c) => result.push(c as char),
1328 Err(_) => panic!("{}{}{}", MSG_ILLEGAL_HEX_CHAR, hex_digits[0] as char, hex_digits[1] as char),
1329 }
1330 } else {
1331 result.push(c as char);
1332 }
1333 }
1334
1335 result
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340 use {
1341 super::{debug_headers, u8_to_upper_hex},
1342 crate::{
1343 canonical::{
1344 canonicalize_query_to_string, canonicalize_uri_path, normalize_uri_path_component,
1345 query_string_to_normalized_map, unescape_uri_encoding, CanonicalRequest,
1346 },
1347 SignatureError, SignatureOptions, NO_ADDITIONAL_SIGNED_HEADERS,
1348 },
1349 bytes::Bytes,
1350 http::{
1351 method::Method,
1352 request::Request,
1353 uri::{PathAndQuery, Uri},
1354 },
1355 scratchstack_errors::ServiceError,
1356 std::collections::HashMap,
1357 };
1358
1359 macro_rules! expect_err {
1360 ($test:expr, $expected:ident) => {
1361 match $test {
1362 Ok(ref v) => panic!("Expected Err({}); got Ok({:?})", stringify!($expected), v),
1363 Err(ref e) => match e {
1364 SignatureError::$expected(_) => e.to_string(),
1365 _ => panic!("Expected {}; got {:#?}: {}", stringify!($expected), &e, &e),
1366 },
1367 }
1368 };
1369 }
1370
1371 #[test_log::test]
1372 fn canonicalize_uri_path_empty() {
1373 assert_eq!(canonicalize_uri_path("", false).unwrap(), "/".to_string());
1374 assert_eq!(canonicalize_uri_path("/", false).unwrap(), "/".to_string());
1375 }
1376
1377 #[test_log::test]
1378 fn canonicalize_valid() {
1379 assert_eq!(canonicalize_uri_path("/hello/world", false).unwrap(), "/hello/world".to_string());
1380 assert_eq!(canonicalize_uri_path("/hello///world", false).unwrap(), "/hello/world".to_string());
1381 assert_eq!(canonicalize_uri_path("/hello/./world", false).unwrap(), "/hello/world".to_string());
1382 assert_eq!(canonicalize_uri_path("/hello/foo/../world", false).unwrap(), "/hello/world".to_string());
1383 assert_eq!(canonicalize_uri_path("/hello/foo/%2E%2E/world", false).unwrap(), "/hello/world".to_string());
1384 assert_eq!(canonicalize_uri_path("/hello/%77%6F%72%6C%64", false).unwrap(), "/hello/world".to_string());
1385 assert_eq!(canonicalize_uri_path("/hello/w*rld", false).unwrap(), "/hello/w%2Arld".to_string());
1386 assert_eq!(canonicalize_uri_path("/hello/w%2arld", false).unwrap(), "/hello/w%2Arld".to_string());
1387 assert_eq!(canonicalize_uri_path("/hello/w+rld", false).unwrap(), "/hello/w%20rld".to_string());
1388
1389 assert_eq!(canonicalize_uri_path("/hello/world", true).unwrap(), "/hello/world".to_string());
1390 assert_eq!(canonicalize_uri_path("/hello///world", true).unwrap(), "/hello///world".to_string());
1391 assert_eq!(canonicalize_uri_path("/hello/./world", true).unwrap(), "/hello/./world".to_string());
1392 assert_eq!(canonicalize_uri_path("/hello/foo/../world", true).unwrap(), "/hello/foo/../world".to_string());
1393 assert_eq!(canonicalize_uri_path("/hello/%77%6F%72%6C%64", true).unwrap(), "/hello/world".to_string());
1394 assert_eq!(canonicalize_uri_path("/hello/w*rld", true).unwrap(), "/hello/w%2Arld".to_string());
1395 assert_eq!(canonicalize_uri_path("/hello/w%2arld", true).unwrap(), "/hello/w%2Arld".to_string());
1396 assert_eq!(canonicalize_uri_path("/hello/w+rld", true).unwrap(), "/hello/w%20rld".to_string());
1397 assert_eq!(canonicalize_uri_path("/hello/../../world", true).unwrap(), "/hello/../../world".to_string());
1398 assert_eq!(
1399 canonicalize_uri_path("/hello/%2e%2e/%2e%2e/world", true).unwrap(),
1400 "/hello/../../world".to_string()
1401 );
1402 }
1403
1404 #[test_log::test]
1405 fn canonicalize_invalid() {
1406 let e = expect_err!(canonicalize_uri_path("hello/world", false), InvalidURIPath);
1407 assert_eq!(e.to_string(), "Path is not absolute: hello/world");
1408 let e = canonicalize_uri_path("/hello/../../world", false).unwrap_err();
1409 if let SignatureError::InvalidURIPath(_) = e {
1410 assert_eq!(e.to_string(), "Relative path entry '..' navigates above root: /hello/../../world");
1411 assert_eq!(e.error_code(), "InvalidURIPath");
1412 assert_eq!(e.http_status(), 400);
1413 } else {
1414 panic!("Expected InvalidURIPath; got {:#?}", &e);
1415 }
1416
1417 let e = canonicalize_uri_path("/hello/%2E%2E/%2E%2E/world", false).unwrap_err();
1418 if let SignatureError::InvalidURIPath(_) = e {
1419 assert_eq!(e.to_string(), "Relative path entry '..' navigates above root: /hello/%2E%2E/%2E%2E/world");
1420 assert_eq!(e.error_code(), "InvalidURIPath");
1421 assert_eq!(e.http_status(), 400);
1422 } else {
1423 panic!("Expected InvalidURIPath; got {:#?}", &e);
1424 }
1425 }
1426
1427 #[test_log::test]
1428 fn canonicalize_query_excludes_signature() {
1429 let query = HashMap::from([
1430 ("X-Amz-Signature".to_string(), vec!["abcdef".to_string()]),
1431 ("b".to_string(), vec!["B".to_string()]),
1432 ("c".to_string(), vec!["C".to_string()]),
1433 ("a".to_string(), vec!["A".to_string()]),
1434 ("e".to_string(), vec!["E".to_string()]),
1435 ("d".to_string(), vec!["d".to_string()]),
1436 ]);
1437
1438 let query = canonicalize_query_to_string(&query);
1439 assert_eq!(query, "a=A&b=B&c=C&d=d&e=E");
1440 }
1441
1442 #[test_log::test]
1443 fn normalize_valid1() {
1444 let result = query_string_to_normalized_map("Hello=World&foo=bar&baz=bomb&foo=2&name").unwrap();
1445 let hello = result.get("Hello").unwrap();
1446 assert_eq!(hello.len(), 1);
1447 assert_eq!(hello[0], "World");
1448
1449 let foo = result.get("foo").unwrap();
1450 assert_eq!(foo.len(), 2);
1451 assert_eq!(foo[0], "bar");
1452 assert_eq!(foo[1], "2");
1453
1454 let baz = result.get("baz").unwrap();
1455 assert_eq!(baz.len(), 1);
1456 assert_eq!(baz[0], "bomb");
1457
1458 let name = result.get("name").unwrap();
1459 assert_eq!(name.len(), 1);
1460 assert_eq!(name[0], "");
1461 }
1462
1463 #[test_log::test]
1464 fn normalize_empty() {
1465 let result = query_string_to_normalized_map("Hello=World&&foo=bar");
1466 let v = result.unwrap();
1467 let hello = v.get("Hello").unwrap();
1468
1469 assert_eq!(hello.len(), 1);
1470 assert_eq!(hello[0], "World");
1471
1472 let foo = v.get("foo").unwrap();
1473 assert_eq!(foo.len(), 1);
1474 assert_eq!(foo[0], "bar");
1475
1476 assert!(!v.contains_key(""));
1477 }
1478
1479 #[test_log::test]
1480 fn normalize_invalid_hex() {
1481 let e = expect_err!(normalize_uri_path_component("abcd%yy"), InvalidURIPath);
1482 assert_eq!(e.as_str(), "Illegal hex character in escape % pattern: %yy");
1483 expect_err!(normalize_uri_path_component("abcd%yy"), InvalidURIPath);
1484 expect_err!(normalize_uri_path_component("abcd%0"), InvalidURIPath);
1485 expect_err!(normalize_uri_path_component("abcd%"), InvalidURIPath);
1486 assert_eq!(normalize_uri_path_component("abcd%65").unwrap(), "abcde");
1487 }
1488
1489 struct PathAndQuerySimulate {
1490 data: Bytes,
1491 _query: u16,
1492 }
1493
1494 #[test_log::test]
1495 fn normalize_invalid_hex_path_cr() {
1496 for (path, error_message) in [
1498 ("/abcd%yy", "Illegal hex character in escape % pattern: %yy"),
1499 ("/abcd%0", "Incomplete trailing escape % sequence"),
1500 ("/abcd%", "Incomplete trailing escape % sequence"),
1501 ] {
1502 let mut fake_path = "/".to_string();
1503 while fake_path.len() < path.len() {
1504 fake_path.push('a');
1505 }
1506
1507 let mut pq = PathAndQuery::from_maybe_shared(fake_path.clone()).unwrap();
1508 let pq_path = Bytes::from_static(path.as_bytes());
1509
1510 unsafe {
1511 let pq_ptr: *mut PathAndQuerySimulate = &mut pq as *mut PathAndQuery as *mut PathAndQuerySimulate;
1514 (*pq_ptr).data = pq_path;
1515 }
1516
1517 let uri = Uri::builder().path_and_query(pq).build().unwrap();
1518 let request = Request::builder()
1519 .method(Method::GET)
1520 .uri(uri)
1521 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1522 .header("authorization", "Basic foobar")
1523 .header("x-amz-date", "20150830T123600Z")
1524 .body(Bytes::new())
1525 .unwrap();
1526 let (parts, body) = request.into_parts();
1527
1528 let e = CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap_err();
1529 if let SignatureError::InvalidURIPath(msg) = e {
1530 assert_eq!(msg.as_str(), error_message);
1531 }
1532 }
1533 }
1534
1535 #[test_log::test]
1536 fn normalize_invalid_hex_query_cr() {
1537 for (path, error_message) in [
1539 ("/?x=abcd%yy", "Illegal hex character in escape % pattern: %yy"),
1540 ("/?x=abcd%0", "Incomplete trailing escape % sequence"),
1541 ("/?x=abcd%", "Incomplete trailing escape % sequence"),
1542 ] {
1543 let mut fake_path = "/?x=".to_string();
1544 while fake_path.len() < path.len() {
1545 fake_path.push('a');
1546 }
1547
1548 let mut pq = PathAndQuery::from_maybe_shared(fake_path.clone()).unwrap();
1549 let pq_path = Bytes::from_static(path.as_bytes());
1550
1551 unsafe {
1552 let pq_ptr: *mut PathAndQuerySimulate = &mut pq as *mut PathAndQuery as *mut PathAndQuerySimulate;
1554 (*pq_ptr).data = pq_path;
1555 }
1556
1557 let uri = Uri::builder().path_and_query(pq).build().unwrap();
1558 let request = Request::builder()
1559 .method(Method::GET)
1560 .uri(uri)
1561 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1562 .header("authorization", "Basic foobar")
1563 .header("x-amz-date", "20150830T123600Z")
1564 .body(Bytes::new())
1565 .unwrap();
1566 let (parts, body) = request.into_parts();
1567
1568 let e = CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap_err();
1569 if let SignatureError::MalformedQueryString(msg) = e {
1570 assert_eq!(msg.as_str(), error_message);
1571 }
1572 }
1573 }
1574
1575 #[test_log::test]
1578 fn normalize_query_parameters_missing_value() {
1579 let result = query_string_to_normalized_map("Key1=Value1&Key2&Key3=Value3");
1580 assert!(result.is_ok());
1581 let result = result.unwrap();
1582 assert_eq!(result["Key1"], vec!["Value1"]);
1583 assert_eq!(result["Key2"], vec![""]);
1584 assert_eq!(result["Key3"], vec!["Value3"]);
1585 }
1586
1587 #[test_log::test]
1588 fn test_multiple_algorithms() {
1589 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1590 let request = Request::builder()
1591 .method(Method::GET)
1592 .uri(uri)
1593 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1594 .header("authorization", "Basic foobar")
1595 .header("x-amz-date", "20150830T123600Z")
1596 .body(Bytes::new())
1597 .unwrap();
1598 let (parts, body) = request.into_parts();
1599
1600 let (cr, _, _) =
1601 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
1602
1603 let _ = format!("{:?}", cr);
1605
1606 assert_eq!(cr.request_method(), "GET");
1607 assert_eq!(cr.canonical_path(), "/");
1608 assert!(cr.query_parameters().is_empty());
1609 assert_eq!(cr.headers().len(), 2);
1610 assert_eq!(cr.headers().get("authorization").unwrap().len(), 2);
1611 assert_eq!(
1612 cr.headers().get("authorization").unwrap()[0],
1613 b"AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678"
1614 );
1615 assert_eq!(cr.body_sha256(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
1616
1617 let params = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap();
1618 let _ = format!("{:?}", params);
1620 assert_eq!(params.signed_headers, vec!["date", "host"]);
1621 }
1622
1623 #[test_log::test]
1624 fn test_bad_form_urlencoded_charset() {
1625 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1626 let request = Request::builder()
1627 .method(Method::POST)
1628 .uri(uri)
1629 .header("content-type", "application/x-www-form-urlencoded; hello=world; charset=foobar")
1630 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1631 .header("x-amz-date", "20150830T123600Z")
1632 .body(Bytes::from_static(b"foo=ba\x80r"))
1633 .unwrap();
1634 let (parts, body) = request.into_parts();
1635
1636 let e = CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap_err();
1637 if let SignatureError::InvalidBodyEncoding(_) = e {
1638 assert_eq!(e.to_string(), "application/x-www-form-urlencoded body uses unsupported charset 'foobar'");
1639 assert_eq!(e.error_code(), "InvalidBodyEncoding");
1640 assert_eq!(e.http_status(), 400);
1641 } else {
1642 panic!("Unexpected error: {:?}", e);
1643 }
1644 }
1645
1646 #[test_log::test]
1647 fn test_empty_form() {
1648 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1649 let request = Request::builder()
1650 .method(Method::POST)
1651 .uri(uri)
1652 .header("content-type", "application/x-www-form-urlencoded; charset=utf-8")
1653 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1654 .header("x-amz-date", "20150830T123600Z")
1655 .body(Bytes::from_static(b""))
1656 .unwrap();
1657 let (parts, body) = request.into_parts();
1658
1659 let (cr, _, _) =
1660 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
1661 assert!(cr.query_parameters().is_empty());
1662 }
1663
1664 #[test_log::test]
1665 fn test_default_form_encoding() {
1666 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1667 let request = Request::builder()
1668 .method(Method::POST)
1669 .uri(uri)
1670 .header("content-type", "application/x-www-form-urlencoded")
1671 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1672 .header("x-amz-date", "20150830T123600Z")
1673 .body(Bytes::from(b"foo=bar\xc3\xbf".to_vec()))
1674 .unwrap();
1675 let (parts, body) = request.into_parts();
1676
1677 let (cr, _, _) =
1678 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
1679 assert_eq!(cr.query_parameters().get("foo").unwrap(), &vec!["bar%C3%BF".to_string()]);
1680
1681 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1682 let request = Request::builder()
1683 .method(Method::POST)
1684 .uri(uri)
1685 .header("content-type", "application/x-www-form-urlencoded; hello=world")
1686 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1687 .header("x-amz-date", "20150830T123600Z")
1688 .body(Bytes::from(b"foo=bar\xc3\xbf".to_vec()))
1689 .unwrap();
1690 let (parts, body) = request.into_parts();
1691
1692 let (cr, _, _) =
1693 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
1694 assert_eq!(cr.query_parameters().get("foo").unwrap(), &vec!["bar%C3%BF".to_string()]);
1695 }
1696
1697 #[test_log::test]
1698 fn test_no_map_form() {
1699 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1700 let request = Request::builder()
1701 .method(Method::POST)
1702 .uri(uri)
1703 .header("content-type", "application/x-www-form-urlencoded")
1704 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1705 .header("x-amz-date", "20150830T123600Z")
1706 .body(Bytes::from(b"foo=bar\xc3\xbf".to_vec()))
1707 .unwrap();
1708 let (parts, body) = request.into_parts();
1709
1710 let (cr, _, _) = CanonicalRequest::from_request_parts(parts, body, SignatureOptions::default()).unwrap();
1711 assert!(!cr.query_parameters().contains_key("foo"));
1712 }
1713
1714 #[test_log::test]
1715 fn test_bad_debug_headers() {
1716 let mut headers = HashMap::new();
1717 headers.insert("Host".to_string(), vec![vec![0xffu8]]);
1718 let debug = debug_headers(&headers);
1719 assert_eq!(debug, "Host: [255]");
1720
1721 assert_eq!(debug_headers(&HashMap::new()), "");
1722 }
1723
1724 #[test_log::test]
1725 fn test_bad_form_encoding() {
1726 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1727 let request = Request::builder()
1728 .method(Method::POST)
1729 .uri(uri)
1730 .header("content-type", "application/x-www-form-urlencoded; charset=utf-8")
1731 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1732 .header("x-amz-date", "20150830T123600Z")
1733 .body(Bytes::from(b"foo=ba\x80r".to_vec()))
1734 .unwrap();
1735 let (parts, body) = request.into_parts();
1736
1737 let e = CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap_err();
1738 if let SignatureError::InvalidBodyEncoding(msg) = e {
1739 assert_eq!(
1740 msg.as_str(),
1741 "Invalid body data encountered parsing application/x-www-form-urlencoded with charset 'UTF-8'"
1742 )
1743 } else {
1744 panic!("Unexpected error: {:?}", e);
1745 }
1746 }
1747
1748 #[test_log::test]
1749 fn test_bad_form_charset_param() {
1750 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1751 let request = Request::builder()
1752 .method(Method::POST)
1753 .uri(uri)
1754 .header("content-type", "application/x-www-form-urlencoded; charset")
1755 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1756 .header("x-amz-date", "20150830T123600Z")
1757 .body(Bytes::from(b"foo=bar".to_vec()))
1758 .unwrap();
1759 let (parts, body) = request.into_parts();
1760
1761 let (_, _, body) =
1762 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
1763 assert_eq!(body.as_ref(), b"");
1764 }
1765
1766 #[test_log::test]
1767 fn test_bad_form_urlencoding() {
1768 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1769 let request = Request::builder()
1770 .method(Method::POST)
1771 .uri(uri)
1772 .header("content-type", "application/x-www-form-urlencoded; charset=utf-8")
1773 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1774 .header("x-amz-date", "20150830T123600Z")
1775 .body(Bytes::from(b"foo=bar%yy".to_vec()))
1776 .unwrap();
1777 let (parts, body) = request.into_parts();
1778
1779 let e = CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap_err();
1780 if let SignatureError::MalformedQueryString(msg) = e {
1781 assert_eq!(msg.as_str(), "Illegal hex character in escape % pattern: %yy")
1782 } else {
1783 panic!("Unexpected error: {:?}", e);
1784 }
1785
1786 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1787 let request = Request::builder()
1788 .method(Method::POST)
1789 .uri(uri)
1790 .header("content-type", "application/x-www-form-urlencoded; charset=utf-8")
1791 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1792 .header("x-amz-date", "20150830T123600Z")
1793 .body(Bytes::from(b"foo%tt=bar".to_vec()))
1794 .unwrap();
1795 let (parts, body) = request.into_parts();
1796
1797 let e = CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap_err();
1798 if let SignatureError::MalformedQueryString(msg) = e {
1799 assert_eq!(msg.as_str(), "Illegal hex character in escape % pattern: %tt")
1800 } else {
1801 panic!("Unexpected error: {:?}", e);
1802 }
1803
1804 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1805 let request = Request::builder()
1806 .method(Method::POST)
1807 .uri(uri)
1808 .header("content-type", "application/x-www-form-urlencoded; charset=utf-8")
1809 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678")
1810 .header("x-amz-date", "20150830T123600Z")
1811 .body(Bytes::from(b"foo=bar%y".to_vec()))
1812 .unwrap();
1813 let (parts, body) = request.into_parts();
1814
1815 let e = CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap_err();
1816 if let SignatureError::MalformedQueryString(msg) = e {
1817 assert_eq!(msg.as_str(), "Incomplete trailing escape % sequence")
1818 } else {
1819 panic!("Unexpected error: {:?}", e);
1820 }
1821 }
1822
1823 #[test_log::test]
1824 fn test_u8_to_upper_hex() {
1825 for i in 0..=255 {
1826 let result = u8_to_upper_hex(i);
1827 assert_eq!(String::from_utf8_lossy(result.as_slice()), format!("{:02X}", i));
1828 }
1829 }
1830
1831 #[test_log::test]
1832 fn test_missing_auth_header_components() {
1833 for i in 0..15 {
1834 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1835 let mut error_messages = Vec::with_capacity(4);
1836 let mut auth_header = Vec::with_capacity(3);
1837
1838 if i & 1 != 0 {
1839 auth_header.push(" Credential=1234 ");
1840 } else {
1841 error_messages.push("Authorization header requires 'Credential' parameter.");
1842 }
1843
1844 if i & 2 != 0 {
1845 auth_header.push(" Signature=5678 ");
1846 } else {
1847 error_messages.push("Authorization header requires 'Signature' parameter.");
1848 }
1849
1850 if i & 4 != 0 {
1851 auth_header.push(" SignedHeaders=host;x-amz-date");
1852 } else {
1853 error_messages.push("Authorization header requires 'SignedHeaders' parameter.");
1854 }
1855
1856 let auth_header = format!("AWS4-HMAC-SHA256 {}", auth_header.join(", "));
1857 let builder = Request::builder().method(Method::GET).uri(uri).header("authorization", auth_header);
1858
1859 let builder = if i & 8 != 0 {
1860 builder.header("x-amz-date", "20150830T123600Z")
1861 } else {
1862 error_messages
1863 .push("Authorization header requires existence of either a 'X-Amz-Date' or a 'Date' header.");
1864 builder
1865 };
1866
1867 let request = builder.body(Bytes::new()).unwrap();
1868 let (parts, body) = request.into_parts();
1869
1870 let (cr, _, _) =
1871 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
1872 let e = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap_err();
1873 if let SignatureError::IncompleteSignature(msg) = e {
1874 let error_message = format!("{} Authorization=AWS4-HMAC-SHA256", error_messages.join(" "));
1875 assert_eq!(msg.as_str(), error_message.as_str());
1876 } else {
1877 panic!("Unexpected error: {:?}", e);
1878 }
1879 }
1880 }
1881
1882 #[test_log::test]
1883 fn test_malformed_auth_header() {
1884 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1885 let request = Request::builder()
1886 .method(Method::GET)
1887 .uri(uri)
1888 .header("x-amz-date", "20150830T123600Z")
1889 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeadersdate;host")
1890 .body(Bytes::new())
1891 .unwrap();
1892
1893 let (parts, body) = request.into_parts();
1894
1895 let (cr, _, _) =
1896 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
1897 let e = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap_err();
1898 if let SignatureError::IncompleteSignature(msg) = e {
1899 assert_eq!(msg.as_str(), "'SignedHeadersdate;host' not a valid key=value pair (missing equal-sign) in Authorization header: 'AWS4-HMAC-SHA256 Credential=1234, SignedHeadersdate;host'");
1900 } else {
1901 panic!("Unexpected error: {:?}", e);
1902 }
1903 }
1904
1905 #[test_log::test]
1906 fn test_missing_auth_query_components() {
1907 for i in 0..15 {
1908 let mut error_messages = Vec::with_capacity(4);
1909 let mut auth_query = Vec::with_capacity(5);
1910
1911 auth_query.push("X-Amz-Algorithm=AWS4-HMAC-SHA256");
1912
1913 if i & 1 != 0 {
1914 auth_query.push("X-Amz-Credential=1234");
1915 } else {
1916 error_messages.push("AWS query-string parameters must include 'X-Amz-Credential'.");
1917 }
1918
1919 if i & 2 != 0 {
1920 auth_query.push("X-Amz-Signature=5678");
1921 } else {
1922 error_messages.push("AWS query-string parameters must include 'X-Amz-Signature'.");
1923 }
1924
1925 if i & 4 != 0 {
1926 auth_query.push("X-Amz-SignedHeaders=host;x-amz-date");
1927 } else {
1928 error_messages.push("AWS query-string parameters must include 'X-Amz-SignedHeaders'.");
1929 }
1930
1931 if i & 8 != 0 {
1932 auth_query.push("X-Amz-Date=20150830T123600Z")
1933 } else {
1934 error_messages.push("AWS query-string parameters must include 'X-Amz-Date'.");
1935 };
1936
1937 let query_string = auth_query.join("&");
1938
1939 let pq = PathAndQuery::from_maybe_shared(format!("/?{}", query_string)).unwrap();
1940 let uri = Uri::builder().path_and_query(pq).build().unwrap();
1941 let builder = Request::builder().method(Method::GET).uri(uri);
1942
1943 let request = builder.body(Bytes::new()).unwrap();
1944 let (parts, body) = request.into_parts();
1945
1946 let (cr, _, _) =
1947 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
1948 let e = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap_err();
1949 if let SignatureError::IncompleteSignature(msg) = e {
1950 let error_message = format!("{} Re-examine the query-string parameters.", error_messages.join(" "));
1951 assert_eq!(msg.as_str(), error_message.as_str());
1952 } else {
1953 panic!("Unexpected error: {:?}", e);
1954 }
1955 }
1956 }
1957
1958 #[test_log::test]
1959 fn test_auth_component_ordering() {
1960 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
1961 let request = Request::builder()
1962 .method(Method::GET)
1963 .uri(uri)
1964 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=date;host, Signature=5678, Credential=ABCD, SignedHeaders=foo;bar;host, Signature=DEFG")
1965 .header("authorization", "AWS3 Credential=1234, SignedHeaders=date;host, Signature=5678, Credential=ABCD, SignedHeaders=foo;bar;host, Signature=DEFG")
1966 .header("host", "example.amazonaws.com")
1967 .header("x-amz-date", "20150830T123600Z")
1968 .header("x-amz-date", "20161231T235959Z")
1969 .header("x-amz-security-token", "Test1")
1970 .header("x-amz-security-token", "Test2")
1971 .body(Bytes::new())
1972 .unwrap();
1973 let (parts, body) = request.into_parts();
1974
1975 let (cr, _, _) =
1976 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
1977 let auth = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap();
1978 assert_eq!(auth.builder.get_credential(), Some("ABCD"));
1980 assert_eq!(auth.builder.get_signature(), Some("DEFG"));
1981 assert_eq!(auth.signed_headers, vec!["bar", "foo", "host"]);
1982 assert_eq!(auth.builder.get_session_token(), Some("Test1"));
1984 assert_eq!(auth.timestamp_str, "20150830T123600Z");
1985
1986 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Algorithm=AWS3&X-Amz-Credential=1234&X-Amz-SignedHeaders=date%3Bhost&X-Amz-Signature=5678&X-Amz-Security-Token=Test1&X-Amz-Date=20150830T123600Z&X-Amz-Credential=ABCD&X-Amz-SignedHeaders=foo%3Bbar%3Bhost&X-Amz-Signature=DEFG&X-Amz-SecurityToken=Test2&X-Amz-Date=20161231T235959Z")).build().unwrap();
1987 let request = Request::builder()
1988 .method(Method::GET)
1989 .uri(uri)
1990 .header("host", "example.amazonaws.com")
1991 .body(Bytes::new())
1992 .unwrap();
1993 let (parts, body) = request.into_parts();
1994
1995 let (cr, _, _) =
1996 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
1997 let auth = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap();
1998 assert_eq!(auth.builder.get_credential(), Some("1234"));
2000 assert_eq!(auth.builder.get_signature(), Some("5678"));
2001 assert_eq!(auth.builder.get_session_token(), Some("Test1"));
2002 assert_eq!(auth.timestamp_str, "20150830T123600Z");
2003 assert_eq!(auth.signed_headers, vec!["date", "host"]);
2004
2005 let auth = cr.get_authenticator(&NO_ADDITIONAL_SIGNED_HEADERS);
2006 assert!(auth.is_ok());
2007 }
2008
2009 #[test_log::test]
2010 fn test_signed_headers_missing_host() {
2011 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
2012 let request = Request::builder()
2013 .method(Method::GET)
2014 .uri(uri)
2015 .header("x-amz-date", "20150830T123600Z")
2016 .header("host", "example.amazonaws.com")
2017 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=x-amz-date, Signature=5678")
2018 .body(Bytes::new())
2019 .unwrap();
2020
2021 let (parts, body) = request.into_parts();
2022
2023 let (cr, _, _) =
2024 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
2025 let required_headers = NO_ADDITIONAL_SIGNED_HEADERS;
2026 let required_headers2 = required_headers;
2027 assert_eq!(&required_headers, &required_headers2);
2028 assert_eq!(format!("{:?}", required_headers), format!("{:?}", required_headers2));
2029 let e = cr.get_auth_parameters(&required_headers).unwrap_err();
2030 if let SignatureError::SignatureDoesNotMatch(msg) = e {
2031 let msg = msg.expect("Expected error message");
2032 assert_eq!(msg.as_str(), "'Host' or ':authority' must be a 'SignedHeader' in the AWS Authorization.");
2033 } else {
2034 panic!("Unexpected error: {:?}", e);
2035 }
2036
2037 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=1234&X-Amz-SignedHeaders=&X-Amz-Signature=5678&X-Amz-Date=20150830T123600Z&X-Amz-SecurityToken=Foo")).build().unwrap();
2038 let request = Request::builder()
2039 .method(Method::GET)
2040 .uri(uri)
2041 .header("host", "example.amazonaws.com")
2042 .body(Bytes::new())
2043 .unwrap();
2044
2045 let (parts, body) = request.into_parts();
2046
2047 let (cr, _, _) =
2048 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
2049 let e = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap_err();
2050 if let SignatureError::SignatureDoesNotMatch(msg) = e {
2051 let msg = msg.expect("Expected error message");
2052 assert_eq!(msg.as_str(), "'Host' or ':authority' must be a 'SignedHeader' in the AWS Authorization.");
2053 } else {
2054 panic!("Unexpected error: {:?}", e);
2055 }
2056 }
2057
2058 #[test_log::test]
2059 fn test_missing_signed_header() {
2060 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
2061 let request = Request::builder()
2062 .method(Method::GET)
2063 .uri(uri)
2064 .header("x-amz-date", "20150830T123600Z")
2065 .header("host", "example.amazonaws.com")
2066 .header(
2067 "authorization",
2068 "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=a;host;x-amz-date, Signature=5678",
2069 )
2070 .body(Bytes::new())
2071 .unwrap();
2072
2073 let (parts, body) = request.into_parts();
2074
2075 let (cr, _, _) =
2076 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
2077 let a = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap();
2078 assert_eq!(a.signed_headers, vec!["a", "host", "x-amz-date"]);
2079 let cr_bytes = cr.canonical_request(&a.signed_headers);
2080 assert!(!cr_bytes.is_empty());
2081 }
2082
2083 #[test_log::test]
2084 fn test_bad_algorithms() {
2085 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
2087 let request = Request::builder()
2088 .method(Method::POST)
2089 .uri(uri)
2090 .header("content-type", "application/json")
2091 .header("x-amz-date", "20150830T123600Z")
2092 .header("host", "example.amazonaws.com")
2093 .body(Bytes::from_static(b"{}"))
2094 .unwrap();
2095
2096 let (parts, body) = request.into_parts();
2097
2098 let (cr, _, _) =
2099 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
2100 let e = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap_err();
2101 if let SignatureError::MissingAuthenticationToken(msg) = e {
2102 assert_eq!(msg.as_str(), "Request is missing Authentication Token");
2103 } else {
2104 panic!("Unexpected error: {:?}", e);
2105 }
2106
2107 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=1234&X-Amz-SignedHeaders=&X-Amz-Signature=5678&X-Amz-Date=20150830T123600Z&X-Amz-SecurityToken=Foo")).build().unwrap();
2109 let request = Request::builder()
2110 .method(Method::GET)
2111 .uri(uri)
2112 .header("authorization", "AWS4-HMAC-SHA256 Credential=1234, SignedHeaders=x-amz-date, Signature=5678")
2113 .header("x-amz-date", "20150830T123600Z")
2114 .header("host", "example.amazonaws.com")
2115 .body(Bytes::new())
2116 .unwrap();
2117
2118 let (parts, body) = request.into_parts();
2119
2120 let (cr, _, _) =
2121 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
2122 let e = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap_err();
2123 if let SignatureError::SignatureDoesNotMatch(ref msg) = e {
2124 assert!(msg.is_none());
2125 assert_eq!(e.to_string(), "");
2126 } else {
2127 panic!("Unexpected error: {:?}", e);
2128 }
2129
2130 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/")).build().unwrap();
2132 let request = Request::builder()
2133 .method(Method::GET)
2134 .uri(uri)
2135 .header("authorization", "AWS3-HMAC-SHA256 Credential=1234, SignedHeaders=x-amz-date, Signature=5678")
2136 .header("x-amz-date", "20150830T123600Z")
2137 .header("host", "example.amazonaws.com")
2138 .body(Bytes::new())
2139 .unwrap();
2140
2141 let (parts, body) = request.into_parts();
2142
2143 let (cr, _, _) =
2144 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
2145 let e = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap_err();
2146 if let SignatureError::IncompleteSignature(msg) = e {
2147 assert_eq!(msg.as_str(), "Unsupported AWS 'algorithm': 'AWS3-HMAC-SHA256'.");
2148 } else {
2149 panic!("Unexpected error: {:?}", e);
2150 }
2151
2152 let uri = Uri::builder().path_and_query(PathAndQuery::from_static("/?X-Amz-Algorithm=AWS3-HMAC-SHA256&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=1234&X-Amz-SignedHeaders=date%3Bhost&X-Amz-Signature=5678&X-Amz-Security-Token=Test1&X-Amz-Date=20150830T123600Z&X-Amz-Credential=ABCD&X-Amz-SignedHeaders=foo%3Bbar%3Bhost&X-Amz-Signature=DEFG&X-Amz-SecurityToken=Test2&X-Amz-Date=20161231T235959Z")).build().unwrap();
2154 let request = Request::builder()
2155 .method(Method::GET)
2156 .uri(uri)
2157 .header("x-amz-date", "20150830T123600Z")
2158 .header("host", "example.amazonaws.com")
2159 .body(Bytes::new())
2160 .unwrap();
2161
2162 let (parts, body) = request.into_parts();
2163
2164 let (cr, _, _) =
2165 CanonicalRequest::from_request_parts(parts, body, SignatureOptions::url_encode_form()).unwrap();
2166 let e = cr.get_auth_parameters(&NO_ADDITIONAL_SIGNED_HEADERS).unwrap_err();
2167 if let SignatureError::MissingAuthenticationToken(msg) = e {
2168 assert_eq!(msg.as_str(), "Request is missing Authentication Token");
2169 } else {
2170 panic!("Unexpected error: {:?}", e);
2171 }
2172 }
2173
2174 #[test_log::test]
2175 #[should_panic]
2176 fn unescape_uri_encoding_invalid_panics() {
2177 unescape_uri_encoding("%YY");
2178 }
2179}