File Coverage

blib/lib/Net/Amazon/S3/Policy.pm
Criterion Covered Total %
statement 94 112 83.9
branch 25 44 56.8
condition 1 3 33.3
subroutine 23 26 88.4
pod 14 14 100.0
total 157 199 78.8


line stmt bran cond sub pod time code
1             package Net::Amazon::S3::Policy;
2              
3 2     2   65198 use warnings;
  2         4  
  2         62  
4 2     2   10 use strict;
  2         5  
  2         69  
5 2     2   1729 use version; our $VERSION = qv('0.1.6');
  2         4525  
  2         13  
6              
7 2     2   174 use Carp;
  2         5  
  2         220  
8 2     2   2006 use English qw( -no_match_vars );
  2         10016  
  2         12  
9 2     2   3335 use JSON;
  2         35784  
  2         14  
10 2     2   2813 use Encode ();
  2         23172  
  2         66  
11 2     2   1967 use MIME::Base64 qw< decode_base64 >;
  2         1560  
  2         144  
12              
13 2     2   11 use Exporter;
  2         4  
  2         2731  
14             our @ISA = qw( Exporter );
15             our @EXPORT_OK = qw( exact starts_with range );
16             our %EXPORT_TAGS = (all => \@EXPORT_OK,);
17              
18             # Module implementation here
19             sub new {
20 3     3 1 2920 my $class = shift;
21 3 100       14 my %args = ref($_[0]) ? %{$_[0]} : @_;
  1         5  
22 3         69 my $self = bless {}, $class;
23              
24 3 100       10 if ($args{json}) {
25 2         9 $self->parse($args{json});
26             }
27             else {
28 1 50       9 $self->expiration($args{expiration}) if defined $args{expiration};
29 1         5 $self->conditions([]);
30 1 50       2 $self->add($_) for @{$args{conditions} || []};
  1         9  
31             }
32              
33 3         11 return $self;
34             } ## end sub new
35              
36             # Accessors
37             sub expiration {
38 4     4 1 1974 my $self = shift;
39 4         16 my $previous = $self->{expiration};
40 4 100       11 if (@_) {
41 1         2 my $time = shift;
42 1 50 33     14 if ($time && $time =~ /\A \d+ \z/mxs) {
43 1         24 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
44             gmtime($time);
45 1         9 $time = sprintf "%04d-%02d-%02dT%02d:%02d:%02d.000Z",
46             $year + 1900,
47             $mon + 1, $mday, $hour, $min, $sec;
48             } ## end if ($time && $time =~ ...
49 1 50       5 $time ? ($self->{expiration} = $time) : delete $self->{expiration};
50             } ## end if (@_)
51 4         17 return $previous;
52             } ## end sub expiration
53              
54             sub conditions {
55 10     10 1 17 my $self = shift;
56 10         18 my $previous = $self->{conditions};
57              
58 10 100       38 if (@_) {
59 1 50       6 $self->{conditions} = (scalar(@_) == 1) ? shift : [@_];
60             }
61              
62 10         28 return $previous;
63             } ## end sub conditions
64              
65             { # try to understand rules
66              
67             sub _prepend_dollar {
68 5 50   5   20 return substr($_[0], 0, 1) eq '$' ? $_[0] : '$' . $_[0];
69             }
70             my @DWIMs = (
71             qr{\A\s* (\S+?) \s* \* \s*\z}mxs => sub {
72             my $target = _prepend_dollar(shift);
73             return starts_with($target, '');
74             },
75             qr{\A\s* (\S+) \s+ eq \s+ (.*?) \s*\z}mxs => sub{
76             my $target = _prepend_dollar(shift);
77             my $value = shift;
78             return $value eq '*' ? starts_with($target, '') : exact($target, $value);
79             },
80             qr{\A\s* (\S+) \s+ (?: ^ | starts[_-]?with) \s+ (.*?) \s*\z}mxs => sub {
81             my $target = _prepend_dollar(shift);
82             my $prefix = shift;
83             return starts_with($target, $prefix);
84             },
85             qr{\A\s* (\d+) \s*<=\s* (\S+) \s*<=\s* (\d+) \s*\z}mxs => sub {
86             my ($min, $value, $max) = @_;
87             s{_}{}g for $min, $max;
88              
89             # no "_prepend_dollar" for range conditions
90             return range($value, $min, $max);
91             },
92             );
93              
94             sub _resolve_rule {
95 6     6   9 my ($string) = @_;
96              
97 6         14 for my $i (0 .. (@DWIMs - 1) / 2) {
98 16         31 my ($regex, $callback) = @DWIMs[$i * 2, $i * 2 + 1];
99 16 100       94 if (my @captures = $string =~ /$regex/) {
100 6         15 my $result = $callback->(@captures);
101 6 50       25 return $result if defined $result;
102             }
103             }
104              
105 0         0 croak "could not understand '$_', bailing out";
106             } ## end sub _resolve_rule
107             }
108              
109             sub add {
110 6     6 1 16 my ($self, $condition) = @_;
111 6 50       8 push @{$self->conditions()},
  6         11  
112             ref($condition) ? $condition : _resolve_rule($condition);
113 6         14 return;
114             }
115              
116             sub remove {
117 0     0 1 0 my ($self, $condition) = @_;
118 0 0       0 $condition = _resolve_rule($condition) unless ref $condition;
119 0         0 my $conditions = $self->conditions();
120 0         0 my @filtered = grep {
121 0         0 my $keep;
122 0 0       0 if (@$condition != @$_) { # different lengths => different
123 0         0 $keep = 1;
124             }
125             else {
126 0         0 for my $i (0 .. $#$condition) {
127 0 0       0 last if $keep = $condition->[$i] ne $_->[$i];
128             }
129             }
130 0         0 $keep;
131             } @$conditions;
132 0         0 $self->conditions(\@filtered);
133 0         0 return;
134             } ## end sub remove
135              
136             sub exact {
137 1 50   1 1 5 shift if ref $_[0];
138 1         3 my ($target, $value) = @_;
139 1         5 return ['eq', $target, $value];
140             }
141              
142             sub starts_with {
143 4 50   4 1 9 shift if ref $_[0];
144 4         7 my ($target, $value) = @_;
145 4         12 return ['starts-with', $target, $value];
146             }
147              
148             sub range {
149 1 50   1 1 3 shift if ref $_[0];
150 1         3 my ($target, $min, $max) = @_;
151 1         4 return [$target, $min, $max];
152             }
153              
154             sub json {
155 2     2 1 2234 my ($self, $args) = @_;
156 2         11 my %params = %$self;
157 2 50       10 delete $params{expiration} unless defined $params{expiration};
158 2         12 return to_json(\%params, $args);
159             } ## end sub json
160              
161             sub base64 {
162 1     1 1 2817 my $self = shift;
163 1         6 return encode_base64(Encode::encode('utf-8', $self->json(@_)));
164             }
165              
166             {
167 2     2   30 no warnings;
  2         5  
  2         749  
168             *stringify = \&json;
169             *json_base64 = \&base64;
170             *stringify_base64 = \&base64;
171             }
172              
173             sub parse {
174 2     2 1 4 my ($self, $json) = @_;
175              
176 2 50       13 $json = decode_base64($json)
177             unless substr($json, 0, 1) eq '{';
178              
179 2         5 my %decoded = %{from_json($json)};
  2         8  
180             $self->{conditions} = [
181             map {
182 14 100       26 if (ref($_) eq 'ARRAY') { $_; }
  12         23  
  2         5  
183             else {
184 2         7 my ($name, $value) = %$_;
185 2         13 ['eq', '$' . $name, $value];
186             }
187 2         84 } @{$decoded{conditions}}
188             ];
189 2         5 $self->{expiration} = $decoded{expiration};
190              
191 2         9 return $self;
192             } ## end sub parse
193              
194             sub signature {
195 0     0 1 0 my ($self, $key) = @_;
196 0         0 require Digest::HMAC_SHA1;
197 0         0 return Digest::HMAC_SHA1::hmac_sha1($self->base64(), $key);
198             }
199              
200             sub signature_base64 {
201 0     0 1 0 my ($self, $key) = @_;
202 0         0 return encode_base64($self->signature($key));
203             }
204              
205             # Wrapper around base64 encoder, ensuring that there's no newline
206             # to make AWS S3 happy
207             sub encode_base64 {
208 1     1 1 265 return MIME::Base64::encode_base64($_[0], '');
209             }
210              
211             1; # Magic true value required at end of module
212             __END__