File Coverage

blib/lib/Amazon/S3Curl/PurePerl.pm
Criterion Covered Total %
statement 59 97 60.8
branch 2 16 12.5
condition 2 6 33.3
subroutine 15 26 57.6
pod 5 8 62.5
total 83 153 54.2


line stmt bran cond sub pod time code
1             package Amazon::S3Curl::PurePerl;
2              
3             #ABSTRACT: Amazon::S3Curl::PurePerl - Pure Perl s3 helper/downloader.
4 1     1   15667 use strict;
  1         2  
  1         30  
5 1     1   4 use warnings;
  1         1  
  1         25  
6              
7 1     1   487 use Module::Runtime qw[ require_module ];
  1         1356  
  1         4  
8              
9             our $VERSION = "0.052";
10             $VERSION = eval $VERSION;
11              
12             #For instances when you want to use s3, but don't want to install anything. ( and you have curl )
13             #Amazon S3 Authentication Tool for Curl
14             #Copyright 2006-2010 Amazon.com, Inc. or its affiliates. All Rights Reserved.
15 1     1   529 use Moo;
  1         10348  
  1         7  
16 1     1   1517 use POSIX;
  1         4713  
  1         5  
17 1     1   2714 use File::Spec;
  1         2  
  1         22  
18 1     1   521 use Log::Contextual qw[ :log :dlog set_logger ];
  1         29916  
  1         7  
19 1     1   28586 use Log::Contextual::SimpleLogger;
  1         404  
  1         27  
20 1     1   538 use Digest::SHA::PurePerl;
  1         26085  
  1         79  
21 1     1   608 use MIME::Base64 qw(encode_base64);
  1         582  
  1         57  
22 1     1   502 use IPC::System::Simple qw[ capture ];
  1         6544  
  1         105  
23             my $DIGEST_HMAC;
24             BEGIN {
25 1     1   1 eval {
26 1         5 require_module("Digest::HMAC");
27 0         0 $DIGEST_HMAC = "Digest::HMAC";
28             };
29 1 50       189 if ($@) { #They dont have Digest::HMAC, use our packaged alternative
30 1         1 $DIGEST_HMAC = "Amazon::S3Curl::PurePerl::Digest::HMAC";
31 1         11 require_module($DIGEST_HMAC);
32             }
33             };
34              
35              
36             set_logger(
37             Log::Contextual::SimpleLogger->new(
38             {
39             levels_upto => 'debug'
40             } ) );
41              
42              
43             has curl => (
44             is => 'ro',
45             default => sub { 'curl' } #maybe your curl isnt in PATH?
46             );
47              
48             for (
49             qw[
50             aws_access_key
51             aws_secret_key
52             ] )
53             {
54             has $_ => (
55             is => 'ro',
56             required => 1,
57             );
58              
59             }
60              
61             has url => (
62             is => 'ro',
63             required => 1,
64             isa => sub {
65             $_[0] =~ m|^/| or die "$_[0] is not a relative url. Should be /bucketname/file"
66             },
67             );
68              
69             has local_file => (
70             is => 'ro',
71             required => 0,
72             predicate => 1,
73             );
74              
75             has static_http_date => (
76             is => 'ro',
77             required => 0,
78             );
79              
80             has s3_scheme_host_url => (
81             is => 'ro',
82             lazy => 1,
83             default => sub {
84             my $env_var = $ENV{AMAZON_S3CURL_PUREPERL_SCHEME_HOST};
85             return $env_var if defined $env_var;
86             return 'https://s3.amazonaws.com'
87             }
88             );
89              
90             sub http_date {
91 0     0 0 0 POSIX::strftime( "%a, %d %b %Y %H:%M:%S +0000", gmtime );
92             }
93              
94             sub _req {
95 1     1   2 my ( $self, $method, $url ) = @_;
96 1 50       3 die "method required" unless $method;
97 1   33     7 $url ||= $self->url;
98 1         2 my $to_sign = $url;
99 1         4 my $resource = sprintf( "%s%s" , $self->s3_scheme_host_url, $url );
100 1         4 my $keyId = $self->aws_access_key;
101 1   33     5 my $httpDate = $self->static_http_date || $self->http_date;
102 1         2 my $contentMD5 = "";
103 1         2 my $contentType = "";
104 1         2 my $xamzHeadersToSign = "";
105 1         3 my $stringToSign = join( "\n" =>
106             ( $method, $contentMD5, $contentType, $httpDate, "$xamzHeadersToSign$to_sign" ) );
107 1         12 my $hmac =
108             $DIGEST_HMAC->new( $self->aws_secret_key, "Digest::SHA::PurePerl",
109             64 );
110 1         4 $hmac->add($stringToSign);
111 1         3 my $signature = encode_base64( $hmac->digest, "" );
112             return [
113 1         2815 $self->curl,
114             -H => "Date: $httpDate",
115             -H => "Authorization: AWS $keyId:$signature",
116             -H => "content-type: $contentType",
117             "-L",
118             "-f",
119             '--no-sslv2',
120             '--no-sslv3',
121             $resource,
122             ];
123             }
124              
125              
126              
127              
128             sub download_cmd {
129 1     1 1 338 my ($self) = @_;
130 1         21 my $args = $self->_req('GET');
131 1         6 push @$args, ( "-o", $self->local_file );
132 1         6 return $args;
133             }
134              
135             sub upload_cmd {
136 0     0 1 0 my ($self) = @_;
137 0         0 my $url = $self->url;
138             #trailing slash for upload means curl will plop on the filename at the end, ruining the hash signature.
139 0 0       0 if ( $url =~ m|/$| ) {
140 0         0 my $file_name = ( File::Spec->splitpath( $self->local_file ) )[-1];
141 0         0 $url .= $file_name;
142             }
143 0         0 my $args = $self->_req('PUT',$url);
144 0         0 splice( @$args, $#$args, 0, "-T", $self->local_file );
145 0         0 return $args;
146             }
147              
148             sub delete_cmd {
149 0     0 1 0 my $args = shift->_req('DELETE');
150 0         0 splice( @$args, $#$args, 0, qw[ -X DELETE ] );
151 0         0 return $args;
152             }
153              
154             sub head_cmd {
155 0     0 0 0 my $args = shift->_req('HEAD');
156 0         0 splice( @$args, $#$args, 0, qw[ -I -X HEAD ] );
157 0         0 return $args;
158             }
159              
160             sub url_exists {
161 0     0 1 0 my $self = shift;
162 0         0 my @args = grep { !/-f/ } @{ $self->head_cmd }; #take out fail mode, want to parse and look for the 404.
  0         0  
  0         0  
163 0     0   0 log_info { "running " . join( " ", @_ ) } @args;
  0         0  
164 0         0 my @output = capture( @args );
165 0 0       0 die "no output received!" unless @output;
166 0 0       0 return 1 if $output[0] =~ /200 OK/;
167 0 0       0 return 0 if $output[0] =~ /404 Not Found/;
168 0         0 die "url_exists did not find a 200 or 404: $output[0]";
169             }
170              
171             sub _exec {
172 0     0   0 my($self,$method) = @_;
173 0         0 my $meth = $method."_cmd";
174 0 0       0 die "cannot $meth" unless $self->can($meth);
175 0         0 my $args = $self->$meth;
176 0     0   0 log_info { "running " . join( " ", @_ ) } @$args;
  0         0  
177 0         0 capture(@$args);
178 0         0 return 1;
179             }
180              
181             sub download {
182             return shift->_exec("download");
183             }
184              
185             sub upload {
186             return shift->_exec("upload");
187             }
188              
189             sub delete {
190 0     0 1 0 return shift->_exec("delete");
191             }
192              
193             sub head {
194 0     0 0 0 return shift->_exec("head");
195             }
196              
197             sub _local_file_required {
198 2     2   3 my $method = shift;
199             sub {
200 0 0   0     die "parameter local_file required for $method"
201             unless shift->local_file;
202 2         11 };
203             }
204              
205             before download => _local_file_required('download');
206             before upload => _local_file_required('upload');
207             1;
208             __END__