File Coverage

blib/lib/Net/Amazon/S3/Client/Object.pm
Criterion Covered Total %
statement 143 203 70.4
branch 37 82 45.1
condition 8 37 21.6
subroutine 35 43 81.4
pod 18 22 81.8
total 241 387 62.2


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