| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Net::Amazon::S3::Signature::V4Implementation; | 
| 2 |  |  |  |  |  |  | # ABSTRACT: Implements the Amazon Web Services signature version 4, AWS4-HMAC-SHA256 (copy of Net::Amazon::Signature::V4) | 
| 3 |  |  |  |  |  |  | $Net::Amazon::S3::Signature::V4Implementation::VERSION = '0.99'; | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 99 |  |  | 99 |  | 815 | use strict; | 
|  | 99 |  |  |  |  | 293 |  | 
|  | 99 |  |  |  |  | 3424 |  | 
| 6 | 99 |  |  | 99 |  | 596 | use warnings; | 
|  | 99 |  |  |  |  | 278 |  | 
|  | 99 |  |  |  |  | 3296 |  | 
| 7 | 99 |  |  | 99 |  | 52608 | use sort 'stable'; | 
|  | 99 |  |  |  |  | 63983 |  | 
|  | 99 |  |  |  |  | 727 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 99 |  |  | 99 |  | 5470 | use Digest::SHA qw/sha256_hex hmac_sha256 hmac_sha256_hex/; | 
|  | 99 |  |  |  |  | 284 |  | 
|  | 99 |  |  |  |  | 7124 |  | 
| 10 | 99 |  |  | 99 |  | 53939 | use Time::Piece (); | 
|  | 99 |  |  |  |  | 741399 |  | 
|  | 99 |  |  |  |  | 3106 |  | 
| 11 | 99 |  |  | 99 |  | 951 | use URI::Escape; | 
|  | 99 |  |  |  |  | 301 |  | 
|  | 99 |  |  |  |  | 6652 |  | 
| 12 | 99 |  |  | 99 |  | 720 | use URI; | 
|  | 99 |  |  |  |  | 291 |  | 
|  | 99 |  |  |  |  | 2207 |  | 
| 13 | 99 |  |  | 99 |  | 596 | use URI::QueryParam; | 
|  | 99 |  |  |  |  | 254 |  | 
|  | 99 |  |  |  |  | 250778 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | our $ALGORITHM = 'AWS4-HMAC-SHA256'; | 
| 16 |  |  |  |  |  |  | our $MAX_EXPIRES = 604800; # Max, 7 days | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | our $X_AMZ_ALGORITHM      = 'X-Amz-Algorithm'; | 
| 19 |  |  |  |  |  |  | our $X_AMZ_CONTENT_SHA256 = 'X-Amz-Content-Sha256'; | 
| 20 |  |  |  |  |  |  | our $X_AMZ_CREDENTIAL     = 'X-Amz-Credential'; | 
| 21 |  |  |  |  |  |  | our $X_AMZ_DATE           = 'X-Amz-Date'; | 
| 22 |  |  |  |  |  |  | our $X_AMZ_EXPIRES        = 'X-Amz-Expires'; | 
| 23 |  |  |  |  |  |  | our $X_AMZ_SIGNEDHEADERS  = 'X-Amz-SignedHeaders'; | 
| 24 |  |  |  |  |  |  | our $X_AMZ_SIGNATURE      = 'X-Amz-Signature'; | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | sub new { | 
| 29 | 7 |  |  | 7 | 0 | 19 | my $class = shift; | 
| 30 | 7 |  |  |  |  | 20 | my ( $access_key_id, $secret, $endpoint, $service ) = @_; | 
| 31 | 7 |  |  |  |  | 34 | my $self = { | 
| 32 |  |  |  |  |  |  | access_key_id => $access_key_id, | 
| 33 |  |  |  |  |  |  | secret        => $secret, | 
| 34 |  |  |  |  |  |  | endpoint      => $endpoint, | 
| 35 |  |  |  |  |  |  | service       => $service, | 
| 36 |  |  |  |  |  |  | }; | 
| 37 | 7 |  |  |  |  | 16 | bless $self, $class; | 
| 38 | 7 |  |  |  |  | 26 | return $self; | 
| 39 |  |  |  |  |  |  | } | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | sub sign { | 
| 43 | 1 |  |  | 1 | 1 | 3 | my ( $self, $request ) = @_; | 
| 44 |  |  |  |  |  |  |  | 
| 45 | 1 |  |  |  |  | 5 | $request = $self->_augment_request( $request ); | 
| 46 |  |  |  |  |  |  |  | 
| 47 | 1 |  |  |  |  | 7 | my $authz = $self->_authorization( $request ); | 
| 48 | 1 |  |  |  |  | 6 | $request->header( Authorization => $authz ); | 
| 49 | 1 |  |  |  |  | 54 | return $request; | 
| 50 |  |  |  |  |  |  | } | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | sub sign_uri { | 
| 54 | 6 |  |  | 6 | 1 | 146 | my ( $self, $uri, $expires_in, $for_method ) = @_; | 
| 55 |  |  |  |  |  |  |  | 
| 56 | 6 |  |  |  |  | 34 | my $request = $self->_augment_uri( $uri, $expires_in, $for_method ); | 
| 57 |  |  |  |  |  |  |  | 
| 58 | 6 |  |  |  |  | 23 | my $signature = $self->_signature( $request ); | 
| 59 |  |  |  |  |  |  |  | 
| 60 | 6 |  |  |  |  | 21 | $uri = $request->uri; | 
| 61 | 6 |  |  |  |  | 52 | my $query = $uri->query; | 
| 62 | 6 |  |  |  |  | 86 | $uri->query( undef ); | 
| 63 | 6 |  |  |  |  | 109 | $uri = $uri . '?' . $self->_sort_query_string( $query ); | 
| 64 | 6 |  |  |  |  | 25 | $uri .= "&$X_AMZ_SIGNATURE=$signature"; | 
| 65 |  |  |  |  |  |  |  | 
| 66 | 6 |  |  |  |  | 84 | return $uri; | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | # _headers_to_sign: | 
| 70 |  |  |  |  |  |  | # Return the sorted lower case headers as required by the generation of canonical headers | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | sub _headers_to_sign { | 
| 73 | 15 |  |  | 15 |  | 27 | my $req = shift; | 
| 74 |  |  |  |  |  |  |  | 
| 75 | 15 | 100 |  |  |  | 66 | my @headers_to_sign = $req->uri->query_param( $X_AMZ_SIGNEDHEADERS ) | 
| 76 |  |  |  |  |  |  | ? $req->uri->query_param( $X_AMZ_SIGNEDHEADERS ) | 
| 77 |  |  |  |  |  |  | : $req->headers->header_field_names | 
| 78 |  |  |  |  |  |  | ; | 
| 79 |  |  |  |  |  |  |  | 
| 80 | 15 |  |  |  |  | 3746 | return sort { $a cmp $b } map { lc } @headers_to_sign | 
|  | 33 |  |  |  |  | 60 |  | 
|  | 33 |  |  |  |  | 95 |  | 
| 81 |  |  |  |  |  |  | } | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | # _augment_request: | 
| 84 |  |  |  |  |  |  | # Append mandatory header fields | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | sub _augment_request { | 
| 87 | 1 |  |  | 1 |  | 3 | my ( $self, $request ) = @_; | 
| 88 |  |  |  |  |  |  |  | 
| 89 | 1 | 50 |  |  |  | 4 | $request->header($X_AMZ_DATE => $self->_format_amz_date( $self->_req_timepiece($request) )) | 
| 90 |  |  |  |  |  |  | unless $request->header($X_AMZ_DATE); | 
| 91 |  |  |  |  |  |  |  | 
| 92 | 1 | 50 |  |  |  | 124 | $request->header($X_AMZ_CONTENT_SHA256 => sha256_hex($request->content)) | 
| 93 |  |  |  |  |  |  | unless $request->header($X_AMZ_CONTENT_SHA256); | 
| 94 |  |  |  |  |  |  |  | 
| 95 | 1 |  |  |  |  | 136 | return $request; | 
| 96 |  |  |  |  |  |  | } | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | # _augment_uri: | 
| 99 |  |  |  |  |  |  | # Append mandatory uri parameters | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | sub _augment_uri { | 
| 102 | 6 |  |  | 6 |  | 19 | my ($self, $uri, $expires_in, $method) = @_; | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 6 |  | 50 |  |  | 29 | my $request = HTTP::Request->new( $method || GET => $uri ); | 
| 105 |  |  |  |  |  |  |  | 
| 106 | 6 | 50 |  |  |  | 399 | $request->uri->query_param( $X_AMZ_DATE => $self->_format_amz_date( $self->_now ) ) | 
| 107 |  |  |  |  |  |  | unless $request->uri->query_param( $X_AMZ_DATE ); | 
| 108 |  |  |  |  |  |  |  | 
| 109 | 6 | 50 |  |  |  | 1105 | $request->uri->query_param( $X_AMZ_ALGORITHM => $ALGORITHM ) | 
| 110 |  |  |  |  |  |  | unless $request->uri->query_param( $X_AMZ_ALGORITHM ); | 
| 111 |  |  |  |  |  |  |  | 
| 112 | 6 | 50 |  |  |  | 1334 | $request->uri->query_param( $X_AMZ_CREDENTIAL => $self->_credential( $request ) ) | 
| 113 |  |  |  |  |  |  | unless $request->uri->query_param( $X_AMZ_CREDENTIAL ); | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 6 | 50 | 33 |  |  | 1403 | $request->uri->query_param( $X_AMZ_EXPIRES => $expires_in || $MAX_EXPIRES ) | 
| 116 |  |  |  |  |  |  | unless $request->uri->query_param( $X_AMZ_EXPIRES ); | 
| 117 | 6 | 50 |  |  |  | 2409 | $request->uri->query_param( $X_AMZ_EXPIRES => $MAX_EXPIRES ) | 
| 118 |  |  |  |  |  |  | if $request->uri->query_param( $X_AMZ_EXPIRES ) > $MAX_EXPIRES; | 
| 119 |  |  |  |  |  |  |  | 
| 120 | 6 |  |  |  |  | 900 | $request->uri->query_param( $X_AMZ_SIGNEDHEADERS => 'host' ); | 
| 121 |  |  |  |  |  |  |  | 
| 122 | 6 |  |  |  |  | 1771 | return $request; | 
| 123 |  |  |  |  |  |  | } | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | # _canonical_request: | 
| 126 |  |  |  |  |  |  | # Construct the canonical request string from an HTTP::Request. | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | sub _canonical_request { | 
| 129 | 7 |  |  | 7 |  | 32 | my ( $self, $req ) = @_; | 
| 130 |  |  |  |  |  |  |  | 
| 131 | 7 |  |  |  |  | 25 | my $creq_method = $req->method; | 
| 132 |  |  |  |  |  |  |  | 
| 133 | 7 | 100 |  |  |  | 94 | my ( $creq_canonical_uri, $creq_canonical_query_string ) = | 
| 134 |  |  |  |  |  |  | ( $req->uri =~ m@([^?]*)\?(.*)$@ ) | 
| 135 |  |  |  |  |  |  | ? ( $1, $2 ) | 
| 136 |  |  |  |  |  |  | : ( $req->uri, '' ); | 
| 137 | 7 |  |  |  |  | 183 | $creq_canonical_uri =~ s@^https?://[^/]*/?@/@; | 
| 138 | 7 |  |  |  |  | 44 | $creq_canonical_uri = _simplify_uri( $creq_canonical_uri ); | 
| 139 | 7 |  |  |  |  | 24 | $creq_canonical_query_string = $self->_sort_query_string( $creq_canonical_query_string ); | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | # Ensure Host header is present as its required | 
| 142 | 7 | 50 |  |  |  | 30 | if (!$req->header('host')) { | 
| 143 | 7 | 100 |  |  |  | 370 | my $host = $req->uri->_port | 
| 144 |  |  |  |  |  |  | ? $req->uri->host_port | 
| 145 |  |  |  |  |  |  | : $req->uri->host | 
| 146 |  |  |  |  |  |  | ; | 
| 147 | 7 |  |  |  |  | 458 | $req->header('Host' => $host); | 
| 148 |  |  |  |  |  |  | } | 
| 149 | 7 |  | 100 |  |  | 378 | my $creq_payload_hash = $req->header($X_AMZ_CONTENT_SHA256) | 
| 150 |  |  |  |  |  |  | # Signed uri doesn't have content | 
| 151 |  |  |  |  |  |  | || 'UNSIGNED-PAYLOAD'; | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | # There's a bug in AMS4 which causes requests without x-amz-date set to be rejected | 
| 154 |  |  |  |  |  |  | # so we always add one if its not present. | 
| 155 | 7 |  |  |  |  | 374 | my $amz_date = $req->header($X_AMZ_DATE); | 
| 156 | 7 |  |  |  |  | 291 | my @sorted_headers = _headers_to_sign( $req ); | 
| 157 |  |  |  |  |  |  | my $creq_canonical_headers = join '', | 
| 158 |  |  |  |  |  |  | map { | 
| 159 | 7 |  |  |  |  | 31 | sprintf "%s:%s\x0a", | 
| 160 |  |  |  |  |  |  | lc, | 
| 161 | 13 |  |  |  |  | 47 | join ',', sort {$a cmp $b } _trim_whitespace($req->header($_) ) | 
|  | 0 |  |  |  |  | 0 |  | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  | @sorted_headers; | 
| 164 | 7 |  |  |  |  | 31 | my $creq_signed_headers = $self->_signed_headers( $req ); | 
| 165 | 7 |  |  |  |  | 25 | my $creq = join "\x0a", | 
| 166 |  |  |  |  |  |  | $creq_method, $creq_canonical_uri, $creq_canonical_query_string, | 
| 167 |  |  |  |  |  |  | $creq_canonical_headers, $creq_signed_headers, $creq_payload_hash; | 
| 168 |  |  |  |  |  |  |  | 
| 169 | 7 |  |  |  |  | 26 | return $creq; | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | # _string_to_sign | 
| 173 |  |  |  |  |  |  | # Construct the string to sign. | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | sub _string_to_sign { | 
| 176 | 7 |  |  | 7 |  | 18 | my ( $self, $req ) = @_; | 
| 177 | 7 |  |  |  |  | 21 | my $dt = $self->_req_timepiece( $req ); | 
| 178 | 7 |  |  |  |  | 434 | my $creq = $self->_canonical_request($req); | 
| 179 | 7 |  |  |  |  | 49 | my $sts_request_date = $self->_format_amz_date( $dt ); | 
| 180 | 7 |  |  |  |  | 284 | my $sts_credential_scope = join '/', $dt->strftime('%Y%m%d'), $self->{endpoint}, $self->{service}, 'aws4_request'; | 
| 181 | 7 |  |  |  |  | 285 | my $sts_creq_hash = sha256_hex( $creq ); | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 7 |  |  |  |  | 31 | my $sts = join "\x0a", $ALGORITHM, $sts_request_date, $sts_credential_scope, $sts_creq_hash; | 
| 184 | 7 |  |  |  |  | 26 | return $sts; | 
| 185 |  |  |  |  |  |  | } | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | # _authorization | 
| 188 |  |  |  |  |  |  | # Construct the authorization string | 
| 189 |  |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  | sub _signature { | 
| 191 | 7 |  |  | 7 |  | 18 | my ( $self, $req ) = @_; | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 7 |  |  |  |  | 24 | my $dt = $self->_req_timepiece( $req ); | 
| 194 | 7 |  |  |  |  | 499 | my $sts = $self->_string_to_sign( $req ); | 
| 195 | 7 |  |  |  |  | 22 | my $k_date    = hmac_sha256( $dt->strftime('%Y%m%d'), 'AWS4' . $self->{secret} ); | 
| 196 | 7 |  |  |  |  | 283 | my $k_region  = hmac_sha256( $self->{endpoint},        $k_date    ); | 
| 197 | 7 |  |  |  |  | 76 | my $k_service = hmac_sha256( $self->{service},         $k_region  ); | 
| 198 | 7 |  |  |  |  | 64 | my $k_signing = hmac_sha256( 'aws4_request',           $k_service ); | 
| 199 |  |  |  |  |  |  |  | 
| 200 | 7 |  |  |  |  | 88 | my $authz_signature = hmac_sha256_hex( $sts, $k_signing ); | 
| 201 | 7 |  |  |  |  | 25 | return $authz_signature; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | sub _credential { | 
| 205 | 7 |  |  | 7 |  | 501 | my ( $self, $req ) = @_; | 
| 206 |  |  |  |  |  |  |  | 
| 207 | 7 |  |  |  |  | 21 | my $dt = $self->_req_timepiece( $req ); | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 7 |  |  |  |  | 553 | my $authz_credential = join '/', $self->{access_key_id}, $dt->strftime('%Y%m%d'), $self->{endpoint}, $self->{service}, 'aws4_request'; | 
| 210 | 7 |  |  |  |  | 258 | return $authz_credential; | 
| 211 |  |  |  |  |  |  | } | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | sub _signed_headers { | 
| 214 | 8 |  |  | 8 |  | 22 | my ( $self, $req ) = @_; | 
| 215 |  |  |  |  |  |  |  | 
| 216 | 8 |  |  |  |  | 22 | my $authz_signed_headers = join ';', _headers_to_sign( $req ); | 
| 217 | 8 |  |  |  |  | 26 | return $authz_signed_headers; | 
| 218 |  |  |  |  |  |  | } | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | sub _authorization { | 
| 221 | 1 |  |  | 1 |  | 5 | my ( $self, $req ) = @_; | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 1 |  |  |  |  | 5 | my $authz_signature = $self->_signature( $req ); | 
| 224 | 1 |  |  |  |  | 5 | my $authz_credential = $self->_credential( $req ); | 
| 225 | 1 |  |  |  |  | 4 | my $authz_signed_headers = $self->_signed_headers( $req ); | 
| 226 |  |  |  |  |  |  |  | 
| 227 | 1 |  |  |  |  | 7 | my $authz = "$ALGORITHM Credential=$authz_credential,SignedHeaders=$authz_signed_headers,Signature=$authz_signature"; | 
| 228 | 1 |  |  |  |  | 5 | return $authz; | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | } | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | sub _simplify_uri { | 
| 233 | 7 |  |  | 7 |  | 16 | my $orig_uri = shift; | 
| 234 | 7 |  |  |  |  | 27 | my @parts = split /\//, $orig_uri; | 
| 235 | 7 |  |  |  |  | 16 | my @simple_parts = (); | 
| 236 | 7 |  |  |  |  | 18 | for my $part ( @parts ) { | 
| 237 | 18 | 100 | 66 |  |  | 105 | if ( ! length $part || $part eq '.' ) { | 
|  |  | 50 |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | } elsif ( $part eq '..' ) { | 
| 239 | 0 |  |  |  |  | 0 | pop @simple_parts; | 
| 240 |  |  |  |  |  |  | } else { | 
| 241 | 12 |  |  |  |  | 29 | push @simple_parts, $part; | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  | } | 
| 244 | 7 |  |  |  |  | 28 | my $simple_uri = '/' . join '/', @simple_parts; | 
| 245 | 7 | 50 | 66 |  |  | 33 | $simple_uri .= '/' if $orig_uri =~ m@/$@ && $simple_uri !~ m@/$@; | 
| 246 | 7 |  |  |  |  | 60 | return $simple_uri; | 
| 247 |  |  |  |  |  |  | } | 
| 248 |  |  |  |  |  |  | sub _sort_query_string { | 
| 249 | 13 |  |  | 13 |  | 52 | my $self = shift; | 
| 250 | 13 | 100 |  |  |  | 33 | return '' unless $_[0]; | 
| 251 | 12 |  |  |  |  | 23 | my @params; | 
| 252 | 12 |  |  |  |  | 44 | for my $param ( split /&/, $_[0] ) { | 
| 253 |  |  |  |  |  |  | my ( $key, $value ) = | 
| 254 | 60 |  |  |  |  | 148 | map { tr/+/ /; uri_escape( uri_unescape( $_ ) ) } # escape all non-unreserved chars | 
|  | 120 |  |  |  |  | 1064 |  | 
|  | 120 |  |  |  |  | 238 |  | 
| 255 |  |  |  |  |  |  | split /=/, $param; | 
| 256 | 60 | 50 |  |  |  | 1379 | push @params, [$key, (defined $value ? $value : '')]; | 
| 257 |  |  |  |  |  |  | #push @params, [$key, $value]; | 
| 258 |  |  |  |  |  |  | } | 
| 259 |  |  |  |  |  |  | return join '&', | 
| 260 | 60 |  |  |  |  | 218 | map { join '=', grep defined, @$_ } | 
| 261 | 12 | 50 |  |  |  | 54 | sort { ( $a->[0] cmp $b->[0] ) || ( $a->[1] cmp $b->[1] ) } | 
|  | 108 |  |  |  |  | 235 |  | 
| 262 |  |  |  |  |  |  | @params; | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  | sub _trim_whitespace { | 
| 265 | 13 |  |  | 13 |  | 551 | return map { my $str = $_; $str =~ s/^\s*//; $str =~ s/\s*$//; $str } @_; | 
|  | 13 |  |  |  |  | 24 |  | 
|  | 13 |  |  |  |  | 66 |  | 
|  | 13 |  |  |  |  | 105 |  | 
|  | 13 |  |  |  |  | 100 |  | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  | sub _str_to_timepiece { | 
| 268 | 21 |  |  | 21 |  | 37 | my $date = shift; | 
| 269 | 21 | 50 |  |  |  | 104 | if ( $date =~ m/^\d{8}T\d{6}Z$/ ) { | 
| 270 |  |  |  |  |  |  | # assume basic ISO 8601, as demanded by AWS | 
| 271 | 21 |  |  |  |  | 86 | return Time::Piece->strptime($date, '%Y%m%dT%H%M%SZ'); | 
| 272 |  |  |  |  |  |  | } else { | 
| 273 |  |  |  |  |  |  | # assume the format given in the AWS4 test suite | 
| 274 | 0 |  |  |  |  | 0 | $date =~ s/^.{5}//; # remove weekday, as Amazon's test suite contains internally inconsistent dates | 
| 275 | 0 |  |  |  |  | 0 | return Time::Piece->strptime($date, '%d %b %Y %H:%M:%S %Z'); | 
| 276 |  |  |  |  |  |  | } | 
| 277 |  |  |  |  |  |  | } | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | sub _format_amz_date { | 
| 280 | 14 |  |  | 14 |  | 464 | my ($self, $dt) = @_; | 
| 281 |  |  |  |  |  |  |  | 
| 282 | 14 |  |  |  |  | 66 | $dt->strftime('%Y%m%dT%H%M%SZ'); | 
| 283 |  |  |  |  |  |  | } | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | sub _now { | 
| 286 | 7 |  |  | 7 |  | 333 | return scalar Time::Piece->gmtime; | 
| 287 |  |  |  |  |  |  | } | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | sub _req_timepiece { | 
| 290 | 22 |  |  | 22 |  | 124 | my ($self, $req) = @_; | 
| 291 | 22 |  | 100 |  |  | 83 | my $x_date = $req->header($X_AMZ_DATE) || $req->uri->query_param($X_AMZ_DATE); | 
| 292 | 22 |  | 66 |  |  | 3488 | my $date = $x_date || $req->header('Date'); | 
| 293 | 22 | 100 |  |  |  | 109 | if (!$date) { | 
| 294 |  |  |  |  |  |  | # No date set by the caller so set one up | 
| 295 | 1 |  |  |  |  | 8 | my $piece = $self->_now; | 
| 296 | 1 |  |  |  |  | 102 | $req->date($piece->epoch); | 
| 297 | 1 |  |  |  |  | 271 | return $piece | 
| 298 |  |  |  |  |  |  | } | 
| 299 | 21 |  |  |  |  | 73 | return _str_to_timepiece($date); | 
| 300 |  |  |  |  |  |  | } | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | 1; | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | __END__ | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | =pod | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | =encoding UTF-8 | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | =head1 NAME | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | Net::Amazon::S3::Signature::V4Implementation - Implements the Amazon Web Services signature version 4, AWS4-HMAC-SHA256 (copy of Net::Amazon::Signature::V4) | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | =head1 VERSION | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | version 0.99 | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | This package clones L<Net::Amazon::Signature::V4> 0.19 adding support for | 
| 321 |  |  |  |  |  |  | signing URIs (GET request) | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | Until https://github.com/Grinnz/Net-Amazon-Signature-V4/pull/5 will be merged | 
| 324 |  |  |  |  |  |  | we have to maintain our clone. | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | =head1 Net::Amazon::Signature::S4 AUTHORS | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | Tim Nordenfur, C<< <tim at gurka.se> >> | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | Maintained by Dan Book, C<< <dbook at cpan.org> >> | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | =head2 sign( $request ) | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | Signs a request with your credentials by appending the Authorization header. $request should be an HTTP::Request. The signed request is returned. | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | =head2 sign_uri( $uri, $expires_in?, $for_method? ) | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | Signs an uri with your credentials by appending the Authorization query parameters. | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | C<< $expires_in >> integer value in range 1..604800 (1 second .. 7 days). | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | C<< $expires_in >> default value is its maximum: 604800 | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | C<< $for_method >> HTTP method this uri should be signed for, default C<GET> | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | The signed uri is returned. | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | =head1 AUTHOR | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | Branislav Zahradník <barney@cpan.org> | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | This software is copyright (c) 2021 by Amazon Digital Services, Leon Brocard, Brad Fitzpatrick, Pedro Figueiredo, Rusty Conover, Branislav Zahradník. | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
| 357 |  |  |  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | =cut |