@@ -101,11 +101,14 @@ pub struct AwsAuthorizer<'a> {
101101 region : & ' a str ,
102102 token_header : Option < HeaderName > ,
103103 sign_payload : bool ,
104+ request_payer : bool ,
104105}
105106
106107static DATE_HEADER : HeaderName = HeaderName :: from_static ( "x-amz-date" ) ;
107108static HASH_HEADER : HeaderName = HeaderName :: from_static ( "x-amz-content-sha256" ) ;
108109static TOKEN_HEADER : HeaderName = HeaderName :: from_static ( "x-amz-security-token" ) ;
110+ static REQUEST_PAYER_HEADER : HeaderName = HeaderName :: from_static ( "x-amz-request-payer" ) ;
111+ static REQUEST_PAYER_HEADER_VALUE : HeaderValue = HeaderValue :: from_static ( "requester" ) ;
109112const ALGORITHM : & str = "AWS4-HMAC-SHA256" ;
110113
111114impl < ' a > AwsAuthorizer < ' a > {
@@ -118,6 +121,7 @@ impl<'a> AwsAuthorizer<'a> {
118121 date : None ,
119122 sign_payload : true ,
120123 token_header : None ,
124+ request_payer : false ,
121125 }
122126 }
123127
@@ -134,6 +138,14 @@ impl<'a> AwsAuthorizer<'a> {
134138 self
135139 }
136140
141+ /// Set whether to include requester pays headers
142+ ///
143+ /// <https://docs.aws.amazon.com/AmazonS3/latest/userguide/ObjectsinRequesterPaysBuckets.html>
144+ pub fn with_request_payer ( mut self , request_payer : bool ) -> Self {
145+ self . request_payer = request_payer;
146+ self
147+ }
148+
137149 /// Authorize `request` with an optional pre-calculated SHA256 digest by attaching
138150 /// the relevant [AWS SigV4] headers
139151 ///
@@ -180,6 +192,15 @@ impl<'a> AwsAuthorizer<'a> {
180192 let header_digest = HeaderValue :: from_str ( & digest) . unwrap ( ) ;
181193 request. headers_mut ( ) . insert ( & HASH_HEADER , header_digest) ;
182194
195+ if self . request_payer {
196+ // For DELETE, GET, HEAD, POST, and PUT requests, include x-amz-request-payer :
197+ // requester in the header
198+ // https://docs.aws.amazon.com/AmazonS3/latest/userguide/ObjectsinRequesterPaysBuckets.html
199+ request
200+ . headers_mut ( )
201+ . insert ( & REQUEST_PAYER_HEADER , REQUEST_PAYER_HEADER_VALUE . clone ( ) ) ;
202+ }
203+
183204 let ( signed_headers, canonical_headers) = canonicalize_headers ( request. headers ( ) ) ;
184205
185206 let scope = self . scope ( date) ;
@@ -226,6 +247,13 @@ impl<'a> AwsAuthorizer<'a> {
226247 . append_pair ( "X-Amz-Expires" , & expires_in. as_secs ( ) . to_string ( ) )
227248 . append_pair ( "X-Amz-SignedHeaders" , "host" ) ;
228249
250+ if self . request_payer {
251+ // For signed URLs, include x-amz-request-payer=requester in the request
252+ // https://docs.aws.amazon.com/AmazonS3/latest/userguide/ObjectsinRequesterPaysBuckets.html
253+ url. query_pairs_mut ( )
254+ . append_pair ( "x-amz-request-payer" , "requester" ) ;
255+ }
256+
229257 // For S3, you must include the X-Amz-Security-Token query parameter in the URL if
230258 // using credentials sourced from the STS service.
231259 if let Some ( ref token) = self . credential . token {
@@ -763,12 +791,53 @@ mod tests {
763791 region : "us-east-1" ,
764792 sign_payload : true ,
765793 token_header : None ,
794+ request_payer : false ,
766795 } ;
767796
768797 signer. authorize ( & mut request, None ) ;
769798 assert_eq ! ( request. headers( ) . get( & AUTHORIZATION ) . unwrap( ) , "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20220806/us-east-1/ec2/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=a3c787a7ed37f7fdfbfd2d7056a3d7c9d85e6d52a2bfbec73793c0be6e7862d4" )
770799 }
771800
801+ #[ test]
802+ fn test_sign_with_signed_payload_request_payer ( ) {
803+ let client = Client :: new ( ) ;
804+
805+ // Test credentials from https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html
806+ let credential = AwsCredential {
807+ key_id : "AKIAIOSFODNN7EXAMPLE" . to_string ( ) ,
808+ secret_key : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" . to_string ( ) ,
809+ token : None ,
810+ } ;
811+
812+ // method = 'GET'
813+ // service = 'ec2'
814+ // host = 'ec2.amazonaws.com'
815+ // region = 'us-east-1'
816+ // endpoint = 'https://ec2.amazonaws.com'
817+ // request_parameters = ''
818+ let date = DateTime :: parse_from_rfc3339 ( "2022-08-06T18:01:34Z" )
819+ . unwrap ( )
820+ . with_timezone ( & Utc ) ;
821+
822+ let mut request = client
823+ . request ( Method :: GET , "https://ec2.amazon.com/" )
824+ . build ( )
825+ . unwrap ( ) ;
826+
827+ let signer = AwsAuthorizer {
828+ date : Some ( date) ,
829+ credential : & credential,
830+ service : "ec2" ,
831+ region : "us-east-1" ,
832+ sign_payload : true ,
833+ token_header : None ,
834+ request_payer : true ,
835+ } ;
836+
837+ signer. authorize ( & mut request, None ) ;
838+ assert_eq ! ( request. headers( ) . get( & AUTHORIZATION ) . unwrap( ) , "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20220806/us-east-1/ec2/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-request-payer, Signature=7030625a9e9b57ed2a40e63d749f4a4b7714b6e15004cab026152f870dd8565d" )
839+ }
840+
772841 #[ test]
773842 fn test_sign_with_unsigned_payload ( ) {
774843 let client = Client :: new ( ) ;
@@ -802,6 +871,7 @@ mod tests {
802871 region : "us-east-1" ,
803872 token_header : None ,
804873 sign_payload : false ,
874+ request_payer : false ,
805875 } ;
806876
807877 authorizer. authorize ( & mut request, None ) ;
@@ -828,6 +898,7 @@ mod tests {
828898 region : "us-east-1" ,
829899 token_header : None ,
830900 sign_payload : false ,
901+ request_payer : false ,
831902 } ;
832903
833904 let mut url = Url :: parse ( "https://examplebucket.s3.amazonaws.com/test.txt" ) . unwrap ( ) ;
@@ -848,6 +919,48 @@ mod tests {
848919 ) ;
849920 }
850921
922+ #[ test]
923+ fn signed_get_url_request_payer ( ) {
924+ // Values from https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
925+ let credential = AwsCredential {
926+ key_id : "AKIAIOSFODNN7EXAMPLE" . to_string ( ) ,
927+ secret_key : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" . to_string ( ) ,
928+ token : None ,
929+ } ;
930+
931+ let date = DateTime :: parse_from_rfc3339 ( "2013-05-24T00:00:00Z" )
932+ . unwrap ( )
933+ . with_timezone ( & Utc ) ;
934+
935+ let authorizer = AwsAuthorizer {
936+ date : Some ( date) ,
937+ credential : & credential,
938+ service : "s3" ,
939+ region : "us-east-1" ,
940+ token_header : None ,
941+ sign_payload : false ,
942+ request_payer : true ,
943+ } ;
944+
945+ let mut url = Url :: parse ( "https://examplebucket.s3.amazonaws.com/test.txt" ) . unwrap ( ) ;
946+ authorizer. sign ( Method :: GET , & mut url, Duration :: from_secs ( 86400 ) ) ;
947+
948+ assert_eq ! (
949+ url,
950+ Url :: parse(
951+ "https://examplebucket.s3.amazonaws.com/test.txt?\
952+ X-Amz-Algorithm=AWS4-HMAC-SHA256&\
953+ X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request&\
954+ X-Amz-Date=20130524T000000Z&\
955+ X-Amz-Expires=86400&\
956+ X-Amz-SignedHeaders=host&\
957+ x-amz-request-payer=requester&\
958+ X-Amz-Signature=9ad7c781cc30121f199b47d35ed3528473e4375b63c5d91cd87c927803e4e00a"
959+ )
960+ . unwrap( )
961+ ) ;
962+ }
963+
851964 #[ test]
852965 fn test_sign_port ( ) {
853966 let client = Client :: new ( ) ;
@@ -880,6 +993,7 @@ mod tests {
880993 region : "us-east-1" ,
881994 token_header : None ,
882995 sign_payload : true ,
996+ request_payer : false ,
883997 } ;
884998
885999 authorizer. authorize ( & mut request, None ) ;
0 commit comments