Skip to content

Commit 739b32c

Browse files
committed
Add InvalidHeaderError to make the error explicit
This commit introduces an error type InvalidHeaderError to indicate that we ran into a problem in handling a HeaderValue within CanonicalRequest. The error type contains a source error such as Utf8Error so a diagnostic message can be printed if needed.
1 parent 221b219 commit 739b32c

File tree

2 files changed

+77
-4
lines changed

2 files changed

+77
-4
lines changed

aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,42 @@ pub(super) struct HeaderValues<'a> {
5050
pub(super) signed_headers: SignedHeaders,
5151
}
5252

53+
#[derive(Debug)]
54+
pub(crate) enum InvalidHeaderErrorKind {
55+
/// Invalid UTF-8 contained in HeaderValue
56+
InvalidUtf8,
57+
}
58+
59+
impl fmt::Display for InvalidHeaderErrorKind {
60+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
61+
match self {
62+
InvalidHeaderErrorKind::InvalidUtf8 => {
63+
write!(f, "invalid UTF-8 contained in header value")
64+
}
65+
}
66+
}
67+
}
68+
69+
/// Error occurred while handling a `HeaderValue` in a `CanonicalRequest`.
70+
/// Thus the context of this error is specific to `CanonicalRequest`.
71+
#[derive(Debug)]
72+
pub(crate) struct InvalidHeaderError {
73+
source: Error,
74+
kind: InvalidHeaderErrorKind,
75+
}
76+
77+
impl fmt::Display for InvalidHeaderError {
78+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
79+
write!(f, "{}: {}", self.kind, self.source)
80+
}
81+
}
82+
83+
impl std::error::Error for InvalidHeaderError {
84+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
85+
Some(self.source.as_ref())
86+
}
87+
}
88+
5389
#[derive(Debug, PartialEq)]
5490
pub(super) struct QueryParamValues<'a> {
5591
pub(super) algorithm: &'static str,
@@ -377,7 +413,11 @@ fn trim_spaces_from_byte_string(bytes: &[u8]) -> &[u8] {
377413
/// Will ensure that the underlying bytes are valid UTF-8.
378414
fn normalize_header_value(header_value: &HeaderValue) -> Result<HeaderValue, Error> {
379415
let trimmed_value = trim_all(header_value.as_bytes());
380-
let trimmed_value_as_utf8_str = std::str::from_utf8(&trimmed_value).map_err(Error::from)?;
416+
let trimmed_value_as_utf8_str =
417+
std::str::from_utf8(&trimmed_value).map_err(|e| InvalidHeaderError {
418+
source: Error::from(e),
419+
kind: InvalidHeaderErrorKind::InvalidUtf8,
420+
})?;
381421
HeaderValue::from_str(trimmed_value_as_utf8_str).map_err(Error::from)
382422
}
383423

@@ -499,7 +539,8 @@ impl<'a> fmt::Display for StringToSign<'a> {
499539
mod tests {
500540
use crate::date_time::test_parsers::parse_date_time;
501541
use crate::http_request::canonical_request::{
502-
normalize_header_value, trim_all, CanonicalRequest, SigningScope, StringToSign,
542+
normalize_header_value, trim_all, CanonicalRequest, InvalidHeaderError, SigningScope,
543+
StringToSign,
503544
};
504545
use crate::http_request::query_writer::QueryWriter;
505546
use crate::http_request::test::{test_canonical_request, test_request, test_sts};
@@ -752,8 +793,12 @@ mod tests {
752793
}
753794

754795
#[test]
755-
fn test_normalize_header_value_returns_err_on_invalid_utf8() {
796+
fn test_normalize_header_value_returns_expected_error_on_invalid_utf8() {
756797
let header_value = HeaderValue::from_bytes(&[0xC0, 0xC1]).unwrap();
757-
assert!(normalize_header_value(&header_value).is_err());
798+
assert!(normalize_header_value(&header_value)
799+
.err()
800+
.unwrap()
801+
.downcast_ref::<InvalidHeaderError>()
802+
.is_some());
758803
}
759804
}

aws/rust-runtime/aws-sigv4/src/http_request/sign.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ fn build_authorization_header(
303303
mod tests {
304304
use super::{sign, SigningInstructions};
305305
use crate::date_time::test_parsers::parse_date_time;
306+
use crate::http_request::canonical_request::InvalidHeaderError;
306307
use crate::http_request::sign::SignableRequest;
307308
use crate::http_request::test::{
308309
make_headers_comparable, test_request, test_signed_request,
@@ -516,6 +517,33 @@ mod tests {
516517
assert_req_eq!(expected, signed);
517518
}
518519

520+
#[test]
521+
fn test_sign_headers_returning_expected_error_on_invalid_utf8() {
522+
let settings = SigningSettings::default();
523+
let params = SigningParams {
524+
access_key: "123",
525+
secret_key: "asdf",
526+
security_token: None,
527+
region: "us-east-1",
528+
service_name: "foo",
529+
time: std::time::SystemTime::now(),
530+
settings,
531+
};
532+
533+
let req = http::Request::builder()
534+
.uri("https://foo.com/")
535+
.header("x-sign-me", HeaderValue::from_bytes(&[0xC0, 0xC1]).unwrap())
536+
.body(&[])
537+
.unwrap();
538+
539+
let creq = crate::http_request::sign(SignableRequest::from(&req), &params);
540+
assert!(creq
541+
.err()
542+
.unwrap()
543+
.downcast_ref::<InvalidHeaderError>()
544+
.is_some());
545+
}
546+
519547
proptest! {
520548
#[test]
521549
// Only byte values between 32 and 255 (inclusive) are permitted, excluding byte 127, for

0 commit comments

Comments
 (0)