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   14837 use strict;
  1         2  
  1         42  
5 1     1   5 use warnings;
  1         1  
  1         38  
6              
7 1     1   520 use Module::Runtime qw[ require_module ];
  1         1220  
  1         5  
8              
9             our $VERSION = "0.054";
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   497 use Moo;
  1         17160  
  1         13  
16 1     1   2547 use POSIX;
  1         7362  
  1         8  
17 1     1   4252 use File::Spec;
  1         2  
  1         33  
18 1     1   850 use Log::Contextual qw[ :log :dlog set_logger ];
  1         46848  
  1         9  
19 1     1   48442 use Log::Contextual::SimpleLogger;
  1         692  
  1         43  
20 1     1   799 use Digest::SHA::PurePerl;
  1         42166  
  1         134  
21 1     1   997 use MIME::Base64 qw(encode_base64);
  1         950  
  1         92  
22 1     1   761 use IPC::System::Simple qw[ capture ];
  1         10464  
  1         178  
23             my $DIGEST_HMAC;
24             BEGIN {
25 1     1   4 eval {
26 1         7 require_module("Digest::HMAC");
27 0         0 $DIGEST_HMAC = "Digest::HMAC";
28             };
29 1 50       288 if ($@) { #They dont have Digest::HMAC, use our packaged alternative
30 1         3 $DIGEST_HMAC = "Amazon::S3Curl::PurePerl::Digest::HMAC";
31 1         17 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   3 my ( $self, $method, $url ) = @_;
96 1 50       5 die "method required" unless $method;
97 1   33     11 $url ||= $self->url;
98 1         2 my $to_sign = $url;
99 1         5 my $resource = sprintf( "%s%s" , $self->s3_scheme_host_url, $url );
100 1         6 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         5 my $stringToSign = join( "\n" =>
106             ( $method, $contentMD5, $contentType, $httpDate, "$xamzHeadersToSign$to_sign" ) );
107 1         14 my $hmac =
108             $DIGEST_HMAC->new( $self->aws_secret_key, "Digest::SHA::PurePerl",
109             64 );
110 1         7 $hmac->add($stringToSign);
111 1         6 my $signature = encode_base64( $hmac->digest, "" );
112             return [
113 1         4362 $self->curl,
114             -H => "Date: $httpDate",
115             -H => "Authorization: AWS $keyId:$signature",
116             -H => "content-type: $contentType",
117             "-L",
118             "-f",
119             $resource,
120             ];
121             }
122              
123              
124              
125              
126             sub download_cmd {
127 1     1 1 474 my ($self) = @_;
128 1         36 my $args = $self->_req('GET');
129 1         9 push @$args, ( "-o", $self->local_file );
130 1         10 return $args;
131             }
132              
133             sub upload_cmd {
134 0     0 1 0 my ($self) = @_;
135 0         0 my $url = $self->url;
136             #trailing slash for upload means curl will plop on the filename at the end, ruining the hash signature.
137 0 0       0 if ( $url =~ m|/$| ) {
138 0         0 my $file_name = ( File::Spec->splitpath( $self->local_file ) )[-1];
139 0         0 $url .= $file_name;
140             }
141 0         0 my $args = $self->_req('PUT',$url);
142 0         0 splice( @$args, $#$args, 0, "-T", $self->local_file );
143 0         0 return $args;
144             }
145              
146             sub delete_cmd {
147 0     0 1 0 my $args = shift->_req('DELETE');
148 0         0 splice( @$args, $#$args, 0, qw[ -X DELETE ] );
149 0         0 return $args;
150             }
151              
152             sub head_cmd {
153 0     0 0 0 my $args = shift->_req('HEAD');
154 0         0 splice( @$args, $#$args, 0, qw[ -I -X HEAD ] );
155 0         0 return $args;
156             }
157              
158             sub url_exists {
159 0     0 1 0 my $self = shift;
160 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  
161 0     0   0 log_info { "running " . join( " ", @_ ) } @args;
  0         0  
162 0         0 my @output = capture( @args );
163 0 0       0 die "no output received!" unless @output;
164 0 0       0 return 1 if $output[0] =~ /200 OK/;
165 0 0       0 return 0 if $output[0] =~ /404 Not Found/;
166 0         0 die "url_exists did not find a 200 or 404: $output[0]";
167             }
168              
169             sub _exec {
170 0     0   0 my($self,$method) = @_;
171 0         0 my $meth = $method."_cmd";
172 0 0       0 die "cannot $meth" unless $self->can($meth);
173 0         0 my $args = $self->$meth;
174 0     0   0 log_info { "running " . join( " ", @_ ) } @$args;
  0         0  
175 0         0 capture(@$args);
176 0         0 return 1;
177             }
178              
179             sub download {
180             return shift->_exec("download");
181             }
182              
183             sub upload {
184             return shift->_exec("upload");
185             }
186              
187             sub delete {
188 0     0 1 0 return shift->_exec("delete");
189             }
190              
191             sub head {
192 0     0 0 0 return shift->_exec("head");
193             }
194              
195             sub _local_file_required {
196 2     2   6 my $method = shift;
197             sub {
198 0 0   0     die "parameter local_file required for $method"
199             unless shift->local_file;
200 2         16 };
201             }
202              
203             before download => _local_file_required('download');
204             before upload => _local_file_required('upload');
205             1;
206             __END__