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.99';
4 99     99   799 use Moose;
  99         250  
  99         750  
5 99     99   655660 use URI::Escape qw( uri_escape_utf8 );
  99         342  
  99         5499  
6 99     99   1140 use HTTP::Date qw[ time2str ];
  99         277  
  99         5577  
7 99     99   723 use MIME::Base64 qw( encode_base64 );
  99         257  
  99         4380  
8 99     99   715 use URI::QueryParam;
  99         224  
  99         2845  
9 99     99   629 use URI;
  99         217  
  99         2521  
10              
11 99     99   676 use Net::Amazon::S3::Constants;
  99         269  
  99         2952  
12              
13 99     99   699 use namespace::clean;
  99         257  
  99         1193  
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 29 0;
21             }
22              
23             sub sign_request {
24 209     209 1 140284 my ($self, $request) = @_;
25              
26 209         825 $self->_add_auth_header( $request );
27             }
28              
29             sub sign_uri {
30 2     2 1 8 my ($self, $request, $expires) = @_;
31              
32 2         63 my $aws_access_key_id = $self->http_request->s3->aws_access_key_id;
33              
34 2         11 my $canonical_string = $self->_canonical_string( $request, $expires );
35 2         10 my $encoded_canonical = $self->_encode( $canonical_string );
36              
37 2         9 my $uri = URI->new( $request->uri );
38              
39 2         201 $uri->query_param( AWSAccessKeyId => $aws_access_key_id );
40 2         266 $uri->query_param( Expires => $expires );
41 2         383 $uri->query_param( Signature => $encoded_canonical );
42              
43 2         410 $uri->as_string;
44             }
45              
46             sub _add_auth_header {
47 209     209   562 my ( $self, $request ) = @_;
48              
49 209         6593 my $aws_access_key_id = $self->http_request->s3->aws_access_key_id;
50 209         6069 my $aws_secret_access_key = $self->http_request->s3->aws_secret_access_key;
51              
52 209 50       984 if ( not $request->headers->header('Date') ) {
53 209         10497 $request->header( Date => time2str(time) );
54             }
55              
56 209         16712 $self->_append_authorization_headers ($request);
57              
58 209         972 my $canonical_string = $self->_canonical_string( $request );
59 209         815 my $encoded_canonical = $self->_encode( $canonical_string );
60 209         1492 $request->header( Authorization => "AWS $aws_access_key_id:$encoded_canonical" );
61             }
62              
63             sub _canonical_string {
64 211     211   577 my ( $self, $request, $expires ) = @_;
65 211         903 my $method = $request->method;
66 211         8374 my $path = $self->http_request->path;
67              
68 211         622 my %interesting_headers = ();
69 211         1156 for my $key ($request->headers->header_field_names) {
70 574         8868 my $lk = lc $key;
71 574 100 100     4955 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 353         1046 $interesting_headers{$lk} = $self->_trim( $request->header( $lk ) );
77             }
78             }
79              
80             # these keys get empty strings if they don't exist
81 211   100     1285 $interesting_headers{'content-type'} ||= '';
82 211   100     1126 $interesting_headers{'content-md5'} ||= '';
83              
84             # just in case someone used this. it's not necessary in this lib.
85             $interesting_headers{'date'} = ''
86 211 50       1934 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 211 100       675 $interesting_headers{'date'} = $expires if $expires;
91              
92 211         656 my $buf = "$method\n";
93 211         1114 foreach my $key ( sort keys %interesting_headers ) {
94 696 100       2477 if ( $key =~ /^$AMAZON_HEADER_PREFIX/ ) {
95 63         252 $buf .= "$key:$interesting_headers{$key}\n";
96             } else {
97 633         1801 $buf .= "$interesting_headers{$key}\n";
98             }
99             }
100              
101             # don't include anything after the first ? in the resource...
102 211         968 $path =~ /^([^?]*)/;
103 211         813 $buf .= "/$1";
104              
105             # ...unless there any parameters we're interested in...
106 211 100       1531 if ( $path =~ /[&?](acl|torrent|location|uploads|delete)($|=|&)/ ) {
    100          
107 56         177 $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     1559 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 211         13590 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 211     211   686 my ( $self, $str, $urlencode ) = @_;
126 211         7335 my $hmac = Digest::HMAC_SHA1->new($self->http_request->s3->aws_secret_access_key);
127 211         12348 $hmac->add($str);
128 211         2151 my $b64 = encode_base64( $hmac->digest, '' );
129 211 50       7140 if ($urlencode) {
130 0         0 return $self->_urlencode($b64);
131             } else {
132 211         1730 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 353     353   15311 my ( $self, $value ) = @_;
143 353         1100 $value =~ s/^\s+//;
144 353         1234 $value =~ s/\s+$//;
145 353         1320 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.99
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