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