File Coverage

blib/lib/Net/Amazon/S3/Client/Object.pm
Criterion Covered Total %
statement 167 201 83.0
branch 45 74 60.8
condition 7 31 22.5
subroutine 40 46 86.9
pod 19 23 82.6
total 278 375 74.1


line stmt bran cond sub pod time code
1             package Net::Amazon::S3::Client::Object;
2             $Net::Amazon::S3::Client::Object::VERSION = '0.99';
3 99     99   923 use Moose 0.85;
  99         2392  
  99         873  
4 99     99   685691 use MooseX::StrictConstructor 0.16;
  99         2285  
  99         848  
5 99     99   395788 use DateTime::Format::HTTP;
  99         205333  
  99         3885  
6 99     99   804 use Digest::MD5 qw(md5 md5_hex);
  99         237  
  99         7477  
7 99     99   49814 use Digest::MD5::File qw(file_md5 file_md5_hex);
  99         2176103  
  99         821  
8 99     99   22251 use File::stat;
  99         262  
  99         1802  
9 99     99   51612 use MIME::Base64;
  99         65658  
  99         5689  
10 99     99   806 use Moose::Util::TypeConstraints;
  99         240  
  99         1171  
11 99     99   230354 use MooseX::Types::DateTime::MoreCoercions 0.07 qw( DateTime );
  99         2953  
  99         924  
12 99     99   128953 use IO::File 1.14;
  99         2221  
  99         15379  
13 99     99   774 use Ref::Util ();
  99         280  
  99         3259  
14              
15             # ABSTRACT: An easy-to-use Amazon S3 client object
16              
17 99     99   44320 use Net::Amazon::S3::Constraint::ACL::Canned;
  99         322  
  99         4389  
18 99     99   19235 use Net::Amazon::S3::Constraint::Etag;
  99         261  
  99         2882  
19 99     99   54785 use Net::Amazon::S3::Client::Object::Range;
  99         372  
  99         305408  
20              
21             with 'Net::Amazon::S3::Role::ACL';
22              
23             enum 'StorageClass' =>
24             # Current list at https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html#AmazonS3-PutObject-request-header-StorageClass
25             [ qw(standard reduced_redundancy standard_ia onezone_ia intelligent_tiering glacier deep_archive) ];
26              
27             has 'client' =>
28             ( is => 'ro', isa => 'Net::Amazon::S3::Client', required => 1 );
29             has 'bucket' =>
30             ( is => 'ro', isa => 'Net::Amazon::S3::Client::Bucket', required => 1 );
31             has 'key' => ( is => 'ro', isa => 'Str', required => 1 );
32             has 'etag' => ( is => 'ro', isa => 'Net::Amazon::S3::Constraint::Etag', required => 0 );
33             has 'size' => ( is => 'ro', isa => 'Int', required => 0 );
34             has 'last_modified' =>
35             ( is => 'ro', isa => DateTime, coerce => 1, required => 0, default => sub { shift->last_modified_raw }, lazy => 1 );
36             has 'last_modified_raw' =>
37             ( is => 'ro', isa => 'Str', required => 0 );
38             has 'expires' => ( is => 'rw', isa => DateTime, coerce => 1, required => 0 );
39             has 'content_type' => (
40             is => 'ro',
41             isa => 'Str',
42             required => 0,
43             default => 'binary/octet-stream'
44             );
45             has 'content_disposition' => (
46             is => 'ro',
47             isa => 'Str',
48             required => 0,
49             );
50             has 'content_encoding' => (
51             is => 'ro',
52             isa => 'Str',
53             required => 0,
54             );
55             has 'cache_control' => (
56             is => 'ro',
57             isa => 'Str',
58             required => 0,
59             );
60             has 'storage_class' => (
61             is => 'ro',
62             isa => 'StorageClass',
63             required => 0,
64             default => 'standard',
65             );
66             has 'user_metadata' => (
67             is => 'ro',
68             isa => 'HashRef',
69             required => 0,
70             default => sub { {} },
71             );
72             has 'website_redirect_location' => (
73             is => 'ro',
74             isa => 'Str',
75             required => 0,
76             );
77             has 'encryption' => (
78             is => 'ro',
79             isa => 'Maybe[Str]',
80             required => 0,
81             );
82              
83             __PACKAGE__->meta->make_immutable;
84              
85             sub range {
86 1     1 1 4 my ($self, $range) = @_;
87              
88 1         15 return Net::Amazon::S3::Client::Object::Range->new (
89             object => $self,
90             range => $range,
91             );
92             }
93              
94             sub exists {
95 6     6 1 17 my $self = shift;
96              
97 6         31 my $response = $self->_perform_operation (
98             'Net::Amazon::S3::Operation::Object::Head',
99             );
100              
101 3         29 return $response->is_success;
102             }
103              
104             sub _get {
105 6     6   12 my $self = shift;
106              
107 6         22 my $response = $self->_perform_operation (
108             'Net::Amazon::S3::Operation::Object::Fetch',
109              
110             method => 'GET',
111             );
112              
113 1         30 $self->_load_user_metadata ($response->http_response);
114              
115 1         9 return $response;
116             }
117              
118             sub get {
119 5     5 1 10 my $self = shift;
120 5         15 return $self->_get->content;
121             }
122              
123             sub get_decoded {
124 1     1 1 3 my $self = shift;
125 1         6 return $self->_get->decoded_content(@_);
126             }
127              
128             sub get_callback {
129 1     1 0 5 my ( $self, $callback ) = @_;
130              
131 1         6 my $response = $self->_perform_operation (
132             'Net::Amazon::S3::Operation::Object::Fetch',
133             filename => $callback,
134             method => 'GET',
135             );
136              
137 0         0 return $response->http_response;
138             }
139              
140             sub get_filename {
141 1     1 1 4 my ( $self, $filename ) = @_;
142              
143 1         8 my $response = $self->_perform_operation (
144             'Net::Amazon::S3::Operation::Object::Fetch',
145             filename => $filename,
146             method => 'GET',
147             );
148              
149 0         0 $self->_load_user_metadata($response->http_response);
150             }
151              
152             sub _load_user_metadata {
153 1     1   3 my ( $self, $http_response ) = @_;
154              
155 1         3 my %user_metadata;
156 1         19 for my $header_name ($http_response->header_field_names) {
157 6 50       122 my ($metadata_name) = lc($header_name) =~ /\A x-amz-meta- (.*) \z/xms
158             or next;
159 0         0 $user_metadata{$metadata_name} = $http_response->header($header_name);
160             }
161              
162 1         4 %{ $self->user_metadata } = %user_metadata;
  1         39  
163             }
164              
165             sub put {
166 11     11 1 31 my ( $self, $value ) = @_;
167 11         73 $self->_put( $value, length $value, md5_hex($value) );
168             }
169              
170             sub _put {
171 12     12   30 my ( $self, $value, $size, $md5_hex ) = @_;
172              
173 12         91 my $md5_base64 = encode_base64( pack( 'H*', $md5_hex ) );
174 12         31 chomp $md5_base64;
175              
176 12         467 my $conf = {
177             'Content-MD5' => $md5_base64,
178             'Content-Length' => $size,
179             'Content-Type' => $self->content_type,
180             };
181              
182 12 100       358 if ( $self->expires ) {
183             $conf->{Expires}
184 3         102 = DateTime::Format::HTTP->format_datetime( $self->expires );
185             }
186 12 100       1596 if ( $self->content_encoding ) {
187 3         93 $conf->{'Content-Encoding'} = $self->content_encoding;
188             }
189 12 100       377 if ( $self->content_disposition ) {
190 1         32 $conf->{'Content-Disposition'} = $self->content_disposition;
191             }
192 12 100       351 if ( $self->cache_control ) {
193 1         45 $conf->{'Cache-Control'} = $self->cache_control;
194             }
195 12 100 66     386 if ( $self->storage_class && $self->storage_class ne 'standard' ) {
196 1         30 $conf->{'x-amz-storage-class'} = uc $self->storage_class;
197             }
198 12 100       383 if ( $self->website_redirect_location ) {
199 2         62 $conf->{'x-amz-website-redirect-location'} = $self->website_redirect_location;
200             }
201             $conf->{"x-amz-meta-\L$_"} = $self->user_metadata->{$_}
202 12         21 for keys %{ $self->user_metadata };
  12         375  
203              
204 12         345 my $response = $self->_perform_operation (
205             'Net::Amazon::S3::Operation::Object::Add',
206              
207             value => $value,
208             headers => $conf,
209             acl => $self->acl,
210             encryption => $self->encryption,
211             );
212              
213 7         202 my $http_response = $response->http_response;
214              
215 7 50       27 confess 'Error uploading ' . $http_response->as_string
216             unless $http_response->is_success;
217              
218 7         87 return '';
219             }
220              
221             sub put_filename {
222 1     1 1 4 my ( $self, $filename ) = @_;
223              
224 1   33     53 my $md5_hex = $self->etag || file_md5_hex($filename);
225 1         950 my $size = $self->size;
226 1 50       4 unless ($size) {
227 1   33     6 my $stat = stat($filename) || confess("No $filename: $!");
228 1         172 $size = $stat->size;
229             }
230              
231 1         12 $self->_put( $self->_content_sub($filename), $size, $md5_hex );
232             }
233              
234             sub delete {
235 6     6 1 17 my $self = shift;
236              
237 6         31 my $response = $self->_perform_operation (
238             'Net::Amazon::S3::Operation::Object::Delete',
239             );
240              
241 1         36 return $response->is_success;
242             }
243              
244             sub set_acl {
245 10     10 0 48 my ($self, %params) = @_;
246              
247 10         55 my $response = $self->_perform_operation (
248             'Net::Amazon::S3::Operation::Object::Acl::Set',
249             %params,
250             );
251              
252 5         377 return $response->is_success;
253             }
254              
255             sub add_tags {
256 6     6 1 19 my ($self, %params) = @_;
257              
258 6         26 my $response = $self->_perform_operation (
259             'Net::Amazon::S3::Operation::Object::Tags::Add',
260             %params,
261             );
262              
263 1         18 return $response->is_success;
264             }
265              
266             sub delete_tags {
267 7     7 1 23 my ($self, %params) = @_;
268              
269             my $response = $self->_perform_operation (
270             'Net::Amazon::S3::Operation::Object::Tags::Delete',
271              
272             (version_id => $params{version_id}) x!! defined $params{version_id},
273 7         39 );
274              
275 2         16 return $response->is_success;
276             }
277              
278             sub initiate_multipart_upload {
279 10     10 1 20 my $self = shift;
280 10 100       32 my %args = ref($_[0]) ? %{$_[0]} : @_;
  5         15  
281              
282 10 100       29 $args{acl} = $args{acl_short} if exists $args{acl_short};
283 10         19 delete $args{acl_short};
284 10 100       190 $args{acl} = $self->acl unless $args{acl};
285              
286             my $response = $self->_perform_operation (
287             'Net::Amazon::S3::Operation::Object::Upload::Create',
288              
289             encryption => $self->encryption,
290             ($args{acl} ? (acl => $args{acl}) : ()),
291 10 100       304 ($args{headers} ? (headers => $args{headers}) : ()),
    100          
292             );
293              
294 0 0       0 return unless $response->is_success;
295              
296 0 0       0 confess "Couldn't get upload id from initiate_multipart_upload response XML"
297             unless $response->upload_id;
298              
299 0         0 return $response->upload_id;
300             }
301              
302             sub complete_multipart_upload {
303 2     2 1 5 my $self = shift;
304              
305 2 100       11 my %args = ref($_[0]) ? %{$_[0]} : @_;
  1         9  
306              
307             my $response = $self->_perform_operation (
308             'Net::Amazon::S3::Operation::Object::Upload::Complete',
309              
310             upload_id => $args{upload_id},
311             etags => $args{etags},
312             part_numbers => $args{part_numbers},
313 2         14 );
314              
315 0         0 return $response->http_response;
316             }
317              
318             sub abort_multipart_upload {
319 2     2 0 5 my $self = shift;
320              
321 2 100       17 my %args = ref($_[0]) ? %{$_[0]} : @_;
  1         14  
322              
323             my $response = $self->_perform_operation (
324             'Net::Amazon::S3::Operation::Object::Upload::Abort',
325              
326             upload_id => $args{upload_id},
327 2         10 );
328              
329 0         0 return $response->http_response;
330             }
331              
332              
333             sub put_part {
334 2     2 1 6 my $self = shift;
335              
336 2 100       24 my %args = ref($_[0]) ? %{$_[0]} : @_;
  1         14  
337              
338             #work out content length header
339             $args{headers}->{'Content-Length'} = length $args{value}
340 2 50       13 if(defined $args{value});
341              
342             my $response = $self->_perform_operation (
343             'Net::Amazon::S3::Operation::Object::Upload::Part',
344              
345             upload_id => $args{upload_id},
346             part_number => $args{part_number},
347             acl_short => $args{acl_short},
348             copy_source => $args{copy_source},
349             headers => $args{headers},
350             value => $args{value},
351 2         15 );
352              
353 0         0 return $response->http_response;
354             }
355              
356             sub list_parts {
357 0     0 0 0 confess "Not implemented";
358             # TODO - Net::Amazon::S3::Request:ListParts is implemented, but need to
359             # define better interface at this level. Currently returns raw XML.
360             }
361              
362             sub uri {
363 0     0 1 0 my $self = shift;
364 0         0 return Net::Amazon::S3::Operation::Object::Fetch::Request->new (
365             s3 => $self->client->s3,
366             bucket => $self->bucket->name,
367             key => $self->key,
368             method => 'GET',
369             )->http_request->uri;
370             }
371              
372             sub query_string_authentication_uri {
373 0     0 1 0 my ($self, $query_form) = @_;
374 0         0 return $self->query_string_authentication_uri_for_method (GET => $query_form);
375             }
376              
377             sub query_string_authentication_uri_for_method {
378 4     4 1 13 my ($self, $method, $query_form) = @_;
379 4         146 return Net::Amazon::S3::Operation::Object::Fetch::Request->new (
380             s3 => $self->client->s3,
381             bucket => $self->bucket->name,
382             key => $self->key,
383             method => $method,
384             )->query_string_authentication_uri ($self->expires->epoch, $query_form);
385             }
386              
387             sub head {
388 5     5 1 15 my $self = shift;
389              
390 5         267 my $response = $self->_perform_operation (
391             'Net::Amazon::S3::Operation::Object::Fetch',
392              
393             key => $self->key,
394             method => 'HEAD',
395             );
396              
397 1 50       11 return unless $
398             response->is_success;
399 1         52 my $http_response = $response->http_response;
400              
401 1         4 my %metadata;
402 1         11 for my $header_name ($http_response->header_field_names) {
403 6 100       288 if ($self->_is_metadata_header ($header_name)) {
404 4         11 my $metadata_name = $self->_format_metadata_name ($header_name);
405 4         13 $metadata{$metadata_name} = $http_response->header ($header_name);
406             }
407             }
408              
409 1         53 return \%metadata;
410             }
411              
412             sub _is_metadata_header {
413 6     6   15 my (undef, $header) = @_;
414 6         11 $header = lc($header);
415              
416 6         30 my %valid_metadata_headers = map +($_ => 1), (
417             'accept-ranges',
418             'cache-control',
419             'etag',
420             'expires',
421             'last-modified',
422             );
423              
424 6 100       19 return 1 if exists $valid_metadata_headers{$header};
425 5 100       15 return 1 if $header =~ m/^x-amz-(?!id-2$)/;
426 4 100       14 return 1 if $header =~ m/^content-/;
427 2         37 return 0;
428             }
429              
430             sub _format_metadata_name {
431 4     4   10 my (undef, $header) = @_;
432 4         7 $header = lc($header);
433 4         11 $header =~ s/^x-amz-//;
434              
435 4         21 my $metadata_name = join('', map (ucfirst, split(/-/, $header)));
436 4 100       13 $metadata_name = 'ETag' if ($metadata_name eq 'Etag');
437              
438 4         9 return $metadata_name;
439             }
440              
441             sub available {
442 0     0 1 0 my $self = shift;
443              
444 0         0 my %metadata = %{$self->head};
  0         0  
445              
446             # An object is available if:
447             # - the storage class isn't GLACIER;
448             # - the storage class is GLACIER and the object was fully restored (Restore: ongoing-request="false");
449 0 0 0     0 my $glacier = (exists($metadata{StorageClass}) and $metadata{StorageClass} eq 'GLACIER') ? 1 : 0;
450 0 0 0     0 my $restored = (exists($metadata{Restore}) and $metadata{Restore} =~ m/ongoing-request="false"/) ? 1 : 0;
451 0 0 0     0 return (!$glacier or $restored) ? 1 :0;
452             }
453              
454             sub restore {
455 1     1 1 5 my $self = shift;
456 1         5 my (%conf) = @_;
457              
458             my $request = $self->_perform_operation (
459             'Net::Amazon::S3::Operation::Object::Restore',
460              
461             key => $self->key,
462             days => $conf{days},
463             tier => $conf{tier},
464 1         51 );
465              
466 0         0 return $request->http_response;
467             }
468              
469             sub _content_sub {
470 1     1   2 my $self = shift;
471 1         2 my $filename = shift;
472 1         4 my $stat = stat($filename);
473 1         150 my $remaining = $stat->size;
474 1   50     21 my $blksize = $stat->blksize || 4096;
475              
476 1 50 33     32 confess "$filename not a readable file with fixed size"
      33        
477             unless -r $filename and ( -f _ || $remaining );
478 1 50       9 my $fh = IO::File->new( $filename, 'r' )
479             or confess "Could not open $filename: $!";
480 1         114 $fh->binmode;
481              
482             return sub {
483 0     0   0 my $buffer;
484              
485             # upon retries the file is closed and we must reopen it
486 0 0       0 unless ( $fh->opened ) {
487 0 0       0 $fh = IO::File->new( $filename, 'r' )
488             or confess "Could not open $filename: $!";
489 0         0 $fh->binmode;
490 0         0 $remaining = $stat->size;
491             }
492              
493             # warn "read remaining $remaining";
494 0 0       0 unless ( my $read = $fh->read( $buffer, $blksize ) ) {
495              
496             # warn "read $read buffer $buffer remaining $remaining";
497 0 0 0     0 confess
498             "Error while reading upload content $filename ($remaining remaining) $!"
499             if $! and $remaining;
500              
501             # otherwise, we found EOF
502 0 0       0 $fh->close
503             or confess "close of upload content $filename failed: $!";
504 0   0     0 $buffer ||= ''
505             ; # LWP expects an emptry string on finish, read returns 0
506             }
507 0         0 $remaining -= length($buffer);
508 0         0 return $buffer;
509 1         18 };
510             }
511              
512             sub _is_multipart_etag {
513 0     0   0 my ( $self, $etag ) = @_;
514 0 0       0 return 1 if($etag =~ /\-\d+$/);
515             }
516              
517             sub _perform_operation {
518 78     78   329 my ($self, $operation, %params) = @_;
519              
520 78         2521 $self->bucket->_perform_operation ($operation => (
521             key => $self->key,
522             %params,
523             ));
524             }
525              
526             1;
527              
528             __END__
529              
530             =pod
531              
532             =encoding UTF-8
533              
534             =head1 NAME
535              
536             Net::Amazon::S3::Client::Object - An easy-to-use Amazon S3 client object
537              
538             =head1 VERSION
539              
540             version 0.99
541              
542             =head1 SYNOPSIS
543              
544             # show the key
545             print $object->key . "\n";
546              
547             # show the etag of an existing object (if fetched by listing
548             # a bucket)
549             print $object->etag . "\n";
550              
551             # show the size of an existing object (if fetched by listing
552             # a bucket)
553             print $object->size . "\n";
554              
555             # to create a new object
556             my $object = $bucket->object( key => 'this is the key' );
557             $object->put('this is the value');
558              
559             # to get the value of an object
560             my $value = $object->get;
561              
562             # to get the metadata of an object
563             my %metadata = %{$object->head};
564              
565             # to see if an object exists
566             if ($object->exists) { ... }
567              
568             # to delete an object
569             $object->delete;
570              
571             # to create a new object which is publically-accessible with a
572             # content-type of text/plain which expires on 2010-01-02
573             my $object = $bucket->object(
574             key => 'this is the public key',
575             acl => Net::Amazon::S3::ACL::CANNED->PUBLIC_READ,
576             content_type => 'text/plain',
577             expires => '2010-01-02',
578             );
579             $object->put('this is the public value');
580              
581             # return the URI of a publically-accessible object
582             my $uri = $object->uri;
583              
584             # to view if an object is available for downloading
585             # Basically, the storage class isn't GLACIER or the object was
586             # fully restored
587             $object->available;
588              
589             # to restore an object on a GLACIER storage class
590             $object->restore(
591             days => 1,
592             tier => 'Standard',
593             );
594              
595             # to store a new object with server-side encryption enabled
596             my $object = $bucket->object(
597             key => 'my secret',
598             encryption => 'AES256',
599             );
600             $object->put('this data will be stored using encryption.');
601              
602             # upload a file
603             my $object = $bucket->object(
604             key => 'images/my_hat.jpg',
605             content_type => 'image/jpeg',
606             );
607             $object->put_filename('hat.jpg');
608              
609             # upload a file if you already know its md5_hex and size
610             my $object = $bucket->object(
611             key => 'images/my_hat.jpg',
612             content_type => 'image/jpeg',
613             etag => $md5_hex,
614             size => $size,
615             );
616             $object->put_filename('hat.jpg');
617              
618             # download the value of the object into a file
619             my $object = $bucket->object( key => 'images/my_hat.jpg' );
620             $object->get_filename('hat_backup.jpg');
621              
622             # use query string authentication for object fetch
623             my $object = $bucket->object(
624             key => 'images/my_hat.jpg',
625             expires => '2009-03-01',
626             );
627             my $uri = $object->query_string_authentication_uri();
628              
629             # use query string authentication for object upload
630             my $object = $bucket->object(
631             key => 'images/my_hat.jpg',
632             expires => '2009-03-01',
633             );
634             my $uri = $object->query_string_authentication_uri_for_method('PUT');
635              
636             =head1 DESCRIPTION
637              
638             This module represents objects in buckets.
639              
640             =for test_synopsis no strict 'vars'
641              
642             =head1 METHODS
643              
644             =head2 range
645              
646             my $value = $object->range ('bytes=1024-10240')->get;
647              
648             Provides simple interface to ranged download. See also L<Net::Amazon::S3::Client::Object::Range>.
649              
650             =head2 etag
651              
652             # show the etag of an existing object (if fetched by listing
653             # a bucket)
654             print $object->etag . "\n";
655              
656             =head2 delete
657              
658             # to delete an object
659             $object->delete;
660              
661             =head2 exists
662              
663             # to see if an object exists
664             if ($object->exists) { ... }
665              
666             Method doesn't report error when bucket or key doesn't exist.
667              
668             =head2 get
669              
670             # to get the vaue of an object
671             my $value = $object->get;
672              
673             =head2 head
674              
675             # to get the metadata of an object
676             my %metadata = %{$object->head};
677              
678             Unlike C<exists> this method does report error.
679              
680             =head2 get_decoded
681              
682             # get the value of an object, and decode any Content-Encoding and/or
683             # charset; see decoded_content in HTTP::Response
684             my $value = $object->get_decoded;
685              
686             =head2 get_filename
687              
688             # download the value of the object into a file
689             my $object = $bucket->object( key => 'images/my_hat.jpg' );
690             $object->get_filename('hat_backup.jpg');
691              
692             =head2 last_modified, last_modified_raw
693              
694             # get the last_modified data as DateTime (slow)
695             my $dt = $obj->last_modified;
696             # or raw string in form '2015-05-15T10:12:40.000Z' (fast)
697             # use this form if you are working with thousands of objects and
698             # do not actually need an expensive DateTime for each of them
699             my $raw = $obj->last_modified_raw;
700              
701             =head2 key
702              
703             # show the key
704             print $object->key . "\n";
705              
706             =head2 available
707              
708             # to view if an object is available for downloading
709             # Basically, the storage class isn't GLACIER or the object was
710             # fully restored
711             $object->available;
712              
713             =head2 restore
714              
715             # to restore an object on a GLACIER storage class
716             $object->restore(
717             days => 1,
718             tier => 'Standard',
719             );
720              
721             =head2 put
722              
723             # to create a new object
724             my $object = $bucket->object( key => 'this is the key' );
725             $object->put('this is the value');
726              
727             # to create a new object which is publically-accessible with a
728             # content-type of text/plain
729             my $object = $bucket->object(
730             key => 'this is the public key',
731             acl => 'public-read',
732             content_type => 'text/plain',
733             );
734             $object->put('this is the public value');
735              
736             For C<acl> refer L<Net::Amazon::S3::ACL>.
737              
738             You may also set Content-Encoding using C<content_encoding>, and
739             Content-Disposition using C<content_disposition>.
740              
741             You may specify the S3 storage class by setting C<storage_class> to either
742             C<standard>, C<reduced_redundancy>, C<standard_ia>, C<onezone_ia>,
743             C<intelligent_tiering>, C<glacier>, or C<deep_archive>; the default
744             is C<standard>.
745              
746             You may set website-redirect-location object metadata by setting
747             C<website_redirect_location> to either another object name in the same
748             bucket, or to an external URL.
749              
750             =head2 put_filename
751              
752             # upload a file
753             my $object = $bucket->object(
754             key => 'images/my_hat.jpg',
755             content_type => 'image/jpeg',
756             );
757             $object->put_filename('hat.jpg');
758              
759             # upload a file if you already know its md5_hex and size
760             my $object = $bucket->object(
761             key => 'images/my_hat.jpg',
762             content_type => 'image/jpeg',
763             etag => $md5_hex,
764             size => $size,
765             );
766             $object->put_filename('hat.jpg');
767              
768             You may also set Content-Encoding using C<content_encoding>, and
769             Content-Disposition using C<content_disposition>.
770              
771             You may specify the S3 storage class by setting C<storage_class> to either
772             C<standard>, C<reduced_redundancy>, C<standard_ia>, C<onezone_ia>,
773             C<intelligent_tiering>, C<glacier>, or C<deep_archive>; the default
774             is C<standard>.
775              
776             You may set website-redirect-location object metadata by setting
777             C<website_redirect_location> to either another object name in the same
778             bucket, or to an external URL.
779              
780             User metadata may be set by providing a non-empty hashref as
781             C<user_metadata>.
782              
783             =head2 query_string_authentication_uri
784              
785             # use query string authentication, forcing download with custom filename
786             my $object = $bucket->object(
787             key => 'images/my_hat.jpg',
788             expires => '2009-03-01',
789             );
790             my $uri = $object->query_string_authentication_uri({
791             'response-content-disposition' => 'attachment; filename=abc.doc',
792             });
793              
794             =head2 query_string_authentication_uri_for_method
795              
796             my $uri = $object->query_string_authentication_uri_for_method ('PUT');
797              
798             Similar to L</query_string_authentication_uri> but creates presigned uri
799             for specified HTTP method (Signature V4 uses also HTTP method).
800              
801             Methods providee authenticated uri only for direct object operations.
802              
803             See L<https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html>
804              
805             =head2 size
806              
807             # show the size of an existing object (if fetched by listing
808             # a bucket)
809             print $object->size . "\n";
810              
811             =head2 uri
812              
813             # return the URI of a publically-accessible object
814             my $uri = $object->uri;
815              
816             =head2 initiate_multipart_upload
817              
818             #initiate a new multipart upload for this object
819             my $object = $bucket->object(
820             key => 'massive_video.avi',
821             acl => ...,
822             );
823             my $upload_id = $object->initiate_multipart_upload;
824              
825             For description of C<acl> refer C<Net::Amazon::S3::ACL>.
826              
827             =head2 put_part
828              
829             #add a part to a multipart upload
830             my $put_part_response = $object->put_part(
831             upload_id => $upload_id,
832             part_number => 1,
833             value => $chunk_content,
834             );
835             my $part_etag = $put_part_response->header('ETag')
836              
837             Returns an L<HTTP::Response> object. It is necessary to keep the ETags for
838             each part, as these are required to complete the upload.
839              
840             =head2 complete_multipart_upload
841              
842             #complete a multipart upload
843             $object->complete_multipart_upload(
844             upload_id => $upload_id,
845             etags => [$etag_1, $etag_2],
846             part_numbers => [$part_number_1, $part_number2],
847             );
848              
849             The etag and part_numbers parameters are ordered lists specifying the part
850             numbers and ETags for each individual part of the multipart upload.
851              
852             =head2 user_metadata
853              
854             my $object = $bucket->object(key => $key);
855             my $content = $object->get; # or use $object->get_filename($filename)
856              
857             # return the user metadata downloaded, as a hashref
858             my $user_metadata = $object->user_metadata;
859              
860             To upload an object with user metadata, set C<user_metadata> at construction
861             time to a hashref, with no C<x-amz-meta-> prefixes on the key names. When
862             downloading an object, the C<get>, C<get_decoded> and C<get_filename>
863             ethods set the contents of C<user_metadata> to the same format.
864              
865             =head2 add_tags
866              
867             $object->add_tags (
868             tags => { tag1 => 'val1', tag2 => 'val2' },
869             );
870              
871             $object->add_tags (
872             tags => { tag1 => 'val1', tag2 => 'val2' },
873             version_id => $version_id,
874             );
875              
876             =head2 delete_tags
877              
878             $object->delete_tags;
879              
880             $object->delete_tags (
881             version_id => $version_id,
882             );
883              
884             =head1 AUTHOR
885              
886             Branislav Zahradník <barney@cpan.org>
887              
888             =head1 COPYRIGHT AND LICENSE
889              
890             This software is copyright (c) 2021 by Amazon Digital Services, Leon Brocard, Brad Fitzpatrick, Pedro Figueiredo, Rusty Conover, Branislav Zahradník.
891              
892             This is free software; you can redistribute it and/or modify it under
893             the same terms as the Perl 5 programming language system itself.
894              
895             =cut