File Coverage

blib/lib/Net/Amazon/S3/Signature/V2.pm
Criterion Covered Total %
statement 78 83 93.9
branch 15 20 75.0
condition 14 16 87.5
subroutine 15 16 93.7
pod 2 3 66.6
total 124 138 89.8


line stmt bran cond sub pod time code
1             package Net::Amazon::S3::Signature::V2;
2             # ABSTRACT: V2 signatures
3             $Net::Amazon::S3::Signature::V2::VERSION = '0.98';
4 96     96   734 use Moose;
  96         331  
  96         1249  
5 96     96   646362 use URI::Escape qw( uri_escape_utf8 );
  96         269  
  96         5407  
6 96     96   670 use HTTP::Date qw[ time2str ];
  96         231  
  96         6452  
7 96     96   715 use MIME::Base64 qw( encode_base64 );
  96         238  
  96         4327  
8 96     96   712 use URI::QueryParam;
  96         224  
  96         3077  
9 96     96   577 use URI;
  96         221  
  96         2359  
10              
11 96     96   594 use Net::Amazon::S3::Constants;
  96         227  
  96         2887  
12              
13 96     96   579 use namespace::clean;
  96         244  
  96         1025  
14              
15             extends 'Net::Amazon::S3::Signature';
16              
17             my $AMAZON_HEADER_PREFIX = 'x-amz-';
18              
19             sub enforce_use_virtual_host {
20 1     1 0 28 0;
21             }
22              
23             sub sign_request {
24 202     202 1 135671 my ($self, $request) = @_;
25              
26 202         841 $self->_add_auth_header( $request );
27             }
28              
29             sub sign_uri {
30 2     2 1 7 my ($self, $request, $expires) = @_;
31              
32 2         63 my $aws_access_key_id = $self->http_request->s3->aws_access_key_id;
33              
34 2         9 my $canonical_string = $self->_canonical_string( $request, $expires );
35 2         6 my $encoded_canonical = $self->_encode( $canonical_string );
36              
37 2         9 my $uri = URI->new( $request->uri );
38              
39 2         186 $uri->query_param( AWSAccessKeyId => $aws_access_key_id );
40 2         259 $uri->query_param( Expires => $expires );
41 2         322 $uri->query_param( Signature => $encoded_canonical );
42              
43 2         445 $uri->as_string;
44             }
45              
46             sub _add_auth_header {
47 202     202   539 my ( $self, $request ) = @_;
48              
49 202         6426 my $aws_access_key_id = $self->http_request->s3->aws_access_key_id;
50 202         5695 my $aws_secret_access_key = $self->http_request->s3->aws_secret_access_key;
51              
52 202 50       986 if ( not $request->headers->header('Date') ) {
53 202         10200 $request->header( Date => time2str(time) );
54             }
55              
56 202         16497 $self->_append_authorization_headers ($request);
57              
58 202         726 my $canonical_string = $self->_canonical_string( $request );
59 202         929 my $encoded_canonical = $self->_encode( $canonical_string );
60 202         1691 $request->header( Authorization => "AWS $aws_access_key_id:$encoded_canonical" );
61             }
62              
63             sub _canonical_string {
64 204     204   620 my ( $self, $request, $expires ) = @_;
65 204         797 my $method = $request->method;
66 204         8085 my $path = $self->http_request->path;
67              
68 204         647 my %interesting_headers = ();
69 204         982 for my $key ($request->headers->header_field_names) {
70 550         8366 my $lk = lc $key;
71 550 100 100     4923 if ( $lk eq 'content-md5'
      100        
      100        
72             or $lk eq 'content-type'
73             or $lk eq 'date'
74             or $lk =~ /^$AMAZON_HEADER_PREFIX/ )
75             {
76 338         1321 $interesting_headers{$lk} = $self->_trim( $request->header( $lk ) );
77             }
78             }
79              
80             # these keys get empty strings if they don't exist
81 204   100     1302 $interesting_headers{'content-type'} ||= '';
82 204   100     1209 $interesting_headers{'content-md5'} ||= '';
83              
84             # just in case someone used this. it's not necessary in this lib.
85             $interesting_headers{'date'} = ''
86 204 50       1762 if $interesting_headers{Net::Amazon::S3::Constants->HEADER_DATE};
87              
88             # if you're using expires for query string auth, then it trumps date
89             # (and x-amz-date)
90 204 100       601 $interesting_headers{'date'} = $expires if $expires;
91              
92 204         628 my $buf = "$method\n";
93 204         1045 foreach my $key ( sort keys %interesting_headers ) {
94 675 100       2341 if ( $key =~ /^$AMAZON_HEADER_PREFIX/ ) {
95 63         247 $buf .= "$key:$interesting_headers{$key}\n";
96             } else {
97 612         1741 $buf .= "$interesting_headers{$key}\n";
98             }
99             }
100              
101             # don't include anything after the first ? in the resource...
102 204         937 $path =~ /^([^?]*)/;
103 204         797 $buf .= "/$1";
104              
105             # ...unless there any parameters we're interested in...
106 204 100       1513 if ( $path =~ /[&?](acl|torrent|location|uploads|delete)($|=|&)/ ) {
    100          
107 56         195 $buf .= "?$1";
108             } elsif ( my %query_params = URI->new($path)->query_form ){
109             #see if the remaining parsed query string provides us with any query string or upload id
110 12 50 33     1543 if($query_params{partNumber} && $query_params{uploadId}){
    50          
111             #re-evaluate query string, the order of the params is important for request signing, so we can't depend on URI to do the right thing
112 0         0 $buf .= sprintf("?partNumber=%s&uploadId=%s", $query_params{partNumber}, $query_params{uploadId});
113             }
114             elsif($query_params{uploadId}){
115 0         0 $buf .= sprintf("?uploadId=%s",$query_params{uploadId});
116             }
117             }
118              
119 204         12870 return $buf;
120             }
121              
122             # finds the hmac-sha1 hash of the canonical string and the aws secret access key and then
123             # base64 encodes the result (optionally urlencoding after that).
124             sub _encode {
125 204     204   660 my ( $self, $str, $urlencode ) = @_;
126 204         7163 my $hmac = Digest::HMAC_SHA1->new($self->http_request->s3->aws_secret_access_key);
127 204         12457 $hmac->add($str);
128 204         2038 my $b64 = encode_base64( $hmac->digest, '' );
129 204 50       6946 if ($urlencode) {
130 0         0 return $self->_urlencode($b64);
131             } else {
132 204         1598 return $b64;
133             }
134             }
135              
136             sub _urlencode {
137 0     0   0 my ( $self, $unencoded ) = @_;
138 0         0 return uri_escape_utf8( $unencoded, '^A-Za-z0-9_-' );
139             }
140              
141             sub _trim {
142 338     338   14899 my ( $self, $value ) = @_;
143 338         1097 $value =~ s/^\s+//;
144 338         1206 $value =~ s/\s+$//;
145 338         1253 return $value;
146             }
147              
148             1;
149              
150             __END__
151              
152             =pod
153              
154             =encoding UTF-8
155              
156             =head1 NAME
157              
158             Net::Amazon::S3::Signature::V2 - V2 signatures
159              
160             =head1 VERSION
161              
162             version 0.98
163              
164             =head1 AUTHOR
165              
166             Branislav Zahradník <barney@cpan.org>
167              
168             =head1 COPYRIGHT AND LICENSE
169              
170             This software is copyright (c) 2021 by Amazon Digital Services, Leon Brocard, Brad Fitzpatrick, Pedro Figueiredo, Rusty Conover, Branislav Zahradník.
171              
172             This is free software; you can redistribute it and/or modify it under
173             the same terms as the Perl 5 programming language system itself.
174              
175             =cut