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