AWS-S3

 view release on metacpan or  search on metacpan

README.markdown  view on Meta::CPAN

    # Add a file:
    my $new_file = $bucket->add_file(
      key       => 'foo/bar.txt',
      contents  => \'This is the contents of the file',
    );

    # You can also set the contents with a coderef:
    # Coderef should eturn a reference, not the actual string of content:
    $new_file = $bucket->add_file(
      key       => 'foo/bar.txt',
      contents  => sub { return \"This is the contents" }
    );

    # Get the file:
    my $same_file = $bucket->file( 'foo/bar.txt' );

    # Get the contents:
    my $scalar_ref = $same_file->contents;
    print $$scalar_ref;

    # Update the contents with a scalar ref:
    $same_file->contents( \"New file contents" );

    # Update the contents with a code ref:
    $same_file->contents( sub { return \"New file contents" } );

    # Delete the file:
    $same_file->delete();

    # Iterate through lots of files:
    my $iterator = $bucket->files(
      page_size   => 100,
      page_number => 1,
    );
    while( my @files = $iterator->next_page )

lib/AWS/S3.pm  view on Meta::CPAN

use AWS::S3::Bucket;

our $VERSION = '1.00';

has [qw/access_key_id secret_access_key/] => ( is => 'ro', isa => 'Str' );

has 'session_token' => (
    is      => 'ro',
    isa     => 'Maybe[Str]',
    lazy    => 1,
    default => sub { $ENV{AWS_SESSION_TOKEN} },
);

has 'region' => (
    is      => 'ro',
    isa     => 'Maybe[Str]',
    lazy    => 1,
    default => sub { $ENV{AWS_REGION} },
);

has 'secure' => (
    is      => 'ro',
    isa     => 'Bool',
    lazy    => 1,
    default => 0
);

has 'endpoint' => (
    is      => 'ro',
    isa     => 'Str',
    lazy    => 1,
    default => sub {
        my ( $s ) = @_;

        if ( my $region = $s->region ) {
            return "s3.$region.amazonaws.com"
        } else {
            return "s3.amazonaws.com"
        }
    },
);

has 'ua' => (
    is      => 'ro',
    isa     => 'LWP::UserAgent',
    default => sub { LWP::UserAgent::Determined->new }
);

has 'honor_leading_slashes' => (
    is      => 'ro',
    isa     => 'Bool',
    default => sub { 0 },
);

sub request {
    my ( $s, $type, %args ) = @_;

    my $class = "AWS::S3::Request::$type";
    load_class( $class );
    return $class->new( %args, s3 => $s, type => $type );
}    # end request()

sub owner {
    my $s = shift;

    my $type     = 'ListAllMyBuckets';
    my $request  = $s->request( $type );
    my $response = $request->request();
    my $xpc      = $response->xpc;
    return AWS::S3::Owner->new(
        id           => $xpc->findvalue( '//s3:Owner/s3:ID' ),
        display_name => $xpc->findvalue( '//s3:Owner/s3:DisplayName' ),
    );
}    # end owner()

sub buckets {
    my ( $s ) = @_;

    my $type     = 'ListAllMyBuckets';
    my $request  = $s->request( $type );
    my $response = $request->request();

    my $xpc     = $response->xpc;
    my @buckets = ();
    foreach my $node ( $xpc->findnodes( './/s3:Bucket' ) ) {
        push @buckets,
          AWS::S3::Bucket->new(
            name          => $xpc->findvalue( './/s3:Name',         $node ),
            creation_date => $xpc->findvalue( './/s3:CreationDate', $node ),
            s3            => $s,
          );
    }    # end foreach()

    return @buckets;
}    # end buckets()

sub bucket {
    my ( $s, $name ) = @_;

    my ( $bucket ) = grep { $_->name eq $name } $s->buckets
      or return;
    $bucket;
}    # end bucket()

sub add_bucket {
    my ( $s, %args ) = @_;

    my $type     = 'CreateBucket';
    my $request  = $s->request(
        $type,
        bucket => $args{name},
        (
            $args{location} ? ( location => $args{location} )
                : $s->region ? ( location => $s->region )
                : ()

lib/AWS/S3.pm  view on Meta::CPAN

  # Add a file:
  my $new_file = $bucket->add_file(
    key       => 'foo/bar.txt',
    contents  => \'This is the contents of the file',
  );

  # You can also set the contents with a coderef:
  # Coderef should eturn a reference, not the actual string of content:
  $new_file = $bucket->add_file(
    key       => 'foo/bar.txt',
    contents  => sub { return \"This is the contents" }
  );

  # Get the file:
  my $same_file = $bucket->file( 'foo/bar.txt' );

  # Get the contents:
  my $scalar_ref = $same_file->contents;
  print $$scalar_ref;

  # Update the contents with a scalar ref:
  $same_file->contents( \"New file contents" );

  # Update the contents with a code ref:
  $same_file->contents( sub { return \"New file contents" } );

  # Delete the file:
  $same_file->delete();

  # Iterate through lots of files:
  my $iterator = $bucket->files(
    page_size   => 100,
    page_number => 1,
  );
  while( my @files = $iterator->next_page )

lib/AWS/S3/Bucket.pm  view on Meta::CPAN

);

use MooseX::Types -declare => [qw/ACLShorts/];

has 'acl' => (
    is       => 'rw',
    isa      => 'Str',
    required => 0,
    lazy     => 1,
    clearer  => '_clear_acl',
    default  => sub {
        my $self = shift;
        my $type = 'GetBucketAccessControl';
        return $self->_get_property( $type )->response->decoded_content();
    },
    trigger  => sub {
        my ( $self, $new_val, $old_val ) = @_;

        my %shorts = map { $_ => undef } qw(
          private public-read public-read-write authenticated-read
        );

        my %acl = ();
        if ( $new_val =~ m{<} ) {
            $acl{acl_xml} = $new_val;
        }

lib/AWS/S3/Bucket.pm  view on Meta::CPAN


        $self->_clear_acl;
    }
);

has 'location_constraint' => (
    is       => 'ro',
    isa      => 'Str',
    required => 0,
    lazy     => 1,
    default  => sub {
        my $self = shift;

        my $type     = 'GetBucketLocationConstraint';
        my $response = $self->_get_property( $type );

        my $constraint = $response->xpc->findvalue( '//s3:LocationConstraint' );
        if ( defined $constraint && $constraint eq '' ) {
            return;
        } else {
            return $constraint;
        }
    }
);

has 'policy' => (
    is       => 'rw',
    isa      => 'Str',
    required => 0,
    lazy     => 1,
    clearer  => '_clear_policy',
    default  => sub {
        my $self = shift;

        my $type     = 'GetBucketPolicy';
        my $req      = $self->s3->request( $type, bucket => $self->name, );
        my $response = $req->request();

        eval { $response->_parse_errors };
        if ( my $msg = $response->friendly_error() ) {
            if ( $response->error_code eq 'NoSuchBucketPolicy' ) {
                return '';
            } else {
                die $msg;
            }    # end if()
        }    # end if()

        return $response->response->decoded_content();
    },
    trigger  => sub {
        my ( $self, $policy ) = @_;

        my $type = 'SetBucketPolicy';
        my $req  = $self->s3->request(
            $type,
            bucket => $self->name,
            policy => $policy,
        );
        my $response = $req->request();

lib/AWS/S3/Bucket.pm  view on Meta::CPAN

        if ( my $msg = $response->friendly_error() ) {
            die $msg;
        }    # end if()

        $self->_clear_policy;

    }
);

# XXX: Not tested.
sub enable_cloudfront_distribution {
    my ( $s, $cloudfront_dist ) = @_;

    $cloudfront_dist->isa( 'AWS::CloudFront::Distribution' )
      or die "Usage: enable_cloudfront_distribution( <AWS::CloudFront::Distribution object> )";

    my $ident = $cloudfront_dist->cf->create_origin_access_identity( Comment => "Access to s3://" . $s->name, );
    $s->policy( <<"JSON");
{
	"Version":"2008-10-17",
	"Id":"PolicyForCloudFrontPrivateContent",

lib/AWS/S3/Bucket.pm  view on Meta::CPAN

			  "CanonicalUser":"@{[ $ident->S3CanonicalUserId ]}"
			},
			"Action": "s3:GetObject",
			"Resource": "arn:aws:s3:::@{[ $s->name ]}/*"
		}
	]
}
JSON
}    # end enable_cloudfront_distribution()

sub files {
    my ( $s, %args ) = @_;

    return AWS::S3::FileIterator->new( %args, bucket => $s, );
}    # end files()

sub file {
    my ( $s, $key ) = @_;

    my $type = 'GetFileInfo';

    my $parser = $s->_get_property( $type, key => $key )
      or return;

    my $res = $parser->response;
    confess "Cannot get file: ", $res->as_string, " " unless $res->is_success;
    return AWS::S3::File->new(
        bucket       => $s,
        key          => $key || undef,
        size         => $res->header( 'content-length' ) || 0,
        contenttype  => $res->header( 'content-type' ) || 'application/octet-stream',
        etag         => $res->header( 'etag' ) || undef,
        lastmodified => $res->header( 'last-modified' ) || undef,
        is_encrypted => ( $res->header( 'x-amz-server-side-encryption' ) || '' ) eq 'AES256' ? 1 : 0,
    );
}    # end file()

sub add_file {
    my ( $s, %args ) = @_;

    my $file = AWS::S3::File->new(
        %args,
        bucket => $s
    );
    $file->contents( $args{contents} );
    return $file;
}    # end add_file()

sub delete {
    my ( $s ) = @_;

    my $type = 'DeleteBucket';

    my $req = $s->s3->request( $type, bucket => $s->name, );
    my $response = $req->request();

    if ( my $msg = $response->friendly_error() ) {
        die $msg;
    }    # end if()

    return 1;
}    # end delete()

# Working as of v0.023
sub delete_multi {
    my ( $s, @keys ) = @_;

    die "You can only delete up to 1000 keys at once"
      if @keys > 1000;
    my $type = 'DeleteMulti';

    my $req = $s->s3->request(
        $type,
        bucket => $s->name,
        keys   => \@keys,
    );
    my $response = $req->request();

    if ( my $msg = $response->friendly_error() ) {
        die $msg;
    }    # end if()

    return 1;
}    # end delete_multi()

sub _get_property {
    my ( $s, $type, %args ) = @_;

    my $req = $s->s3->request( $type, bucket => $s->name, %args );
    my $response = $req->request();

    return if $response->response->code == 404;

    if ( my $msg = $response->friendly_error() ) {
        die $msg;
    }    # end if()

lib/AWS/S3/File.pm  view on Meta::CPAN

    is       => 'ro',
    isa      => 'AWS::S3::Bucket',
    required => 1,
    weak_ref => 0,
);

has 'size' => (
    is       => 'ro',
    isa      => 'Int',
    required => 0,
    default  => sub {
      my $self = shift;
      return length ${$self->contents};
    }
);

has 'etag' => (
    is       => 'ro',
    isa      => 'Str',
    required => 0,
);

lib/AWS/S3/File.pm  view on Meta::CPAN

    isa      => 'Str',
    required => 0,
    default  => 'binary/octet-stream'
);

has 'is_encrypted' => (
    is       => 'rw',
    isa      => 'Bool',
    required => 1,
    lazy     => 1,
    default  => sub {
        my $s = shift;

        my $type = 'GetFileInfo';
        my $req  = $s->bucket->s3->request(
            $type,
            bucket => $s->bucket->name,
            key    => $s->key,
        );

        return $req->request->response->header( 'x-amz-server-side-encryption' ) ? 1 : 0;

lib/AWS/S3/File.pm  view on Meta::CPAN

has 'contents' => (
    is       => 'rw',
    isa      => fileContents,
    required => 0,
    lazy     => 1,
    coerce   => 1,
    default  => \&_get_contents,
    trigger  => \&_set_contents
);

sub BUILD {
    my $s = shift;

    return unless $s->etag;
    ( my $etag = $s->etag ) =~ s{^"}{};
    $etag =~ s{"$}{};
    $s->{etag} = $etag;
}    # end BUILD()

sub update {
    my $s       = shift;
    my %args    = @_;
    my @args_ok = grep { /^content(?:s|type)$/ } keys %args;
    if ( @args_ok ) {
        $s->{$_} = $args{$_} for @args_ok;
        $s->_set_contents();
        return 1;
    }
    return;
}    # end update()

sub _get_contents {
    my $s = shift;

    my $type = 'GetFileContents';
    my $req  = $s->bucket->s3->request(
        $type,
        bucket => $s->bucket->name,
        key    => $s->key,
    );

    return \$req->request->response->decoded_content;
}    # end contents()

sub _set_contents {
    my ( $s, $ref ) = @_;

    my $type     = 'SetFileContents';
    my %args     = ();
    my $response = $s->bucket->s3->request(
        $type,
        bucket                 => $s->bucket->name,
        file                   => $s,
        contents               => $ref,
        content_type           => $s->contenttype,

lib/AWS/S3/File.pm  view on Meta::CPAN


    ( my $etag = $response->response->header( 'etag' ) ) =~ s{^"}{};
    $etag =~ s{"$}{};
    $s->{etag} = $etag;

    if ( my $msg = $response->friendly_error() ) {
        die $msg;
    }    # end if()
}    # end _set_contents()

sub signed_url {
    my $s       = shift;
    my $expires = shift || 3600;

    # expiry for v4 signature is in seconds, not epoch time
    if ( $expires > time ) {
        $expires -= time;
    }

	my $key = $s->key;

lib/AWS/S3/File.pm  view on Meta::CPAN

    my $uri  = $s->bucket->s3->request(
        $type,
        bucket  => $s->bucket->name,
        key     => $key,
        expires => $expires,
    )->request;

    return $uri;
}

sub delete {
    my $s = shift;

    my $type = 'DeleteFile';
    my $req  = $s->bucket->s3->request(
        $type,
        bucket => $s->bucket->name,
        key    => $s->key,
    );
    my $response = $req->request();

lib/AWS/S3/File.pm  view on Meta::CPAN

  
  print $file->owner->display_name;
  
  print $file->bucket->name;
  
  # Set the contents with a scalarref:
  my $new_contents = "This is the new contents of the file.";
  $file->contents( \$new_contents );
  
  # Set the contents with a coderef:
  $file->contents( sub {
    return \$new_contents;
  });
  
  # Alternative update
  $file->update( 
    contents => \'New contents', # optional
    contenttype => 'text/plain'  # optional
  );

  # Get signed URL for the file for public access

lib/AWS/S3/File.pm  view on Meta::CPAN


Returns a scalar-reference of the file's contents.

Accepts either a scalar-ref or a code-ref (which would return a scalar-ref).

Once given a new value, the file is instantly updated on Amazon S3.

  # GOOD: (uses scalarrefs)
  my $value = "A string";
  $file->contents( \$value );
  $file->contents( sub { return \$value } );
  
  # BAD: (not scalarrefs)
  $file->contents( $value );
  $file->contents( sub { return $value } );

=head1 PUBLIC METHODS

=head2 delete()

Deletes the file from Amazon S3.

=head2 update()

Update contents and/or contenttype of the file.

lib/AWS/S3/FileIterator.pm  view on Meta::CPAN

package AWS::S3::FileIterator;

use strict;
use warnings 'all';
use Carp 'confess';
use AWS::S3::Owner;
use AWS::S3::File;

sub new {
  my ($class, %args) = @_;

  my $s = bless {
    data        => [ ],
    page_number => 0,
    idx         => 0,
    %args,
  }, $class;
  $s->_init;
  return $s;
}

sub _init {
    my ( $s ) = @_;

    foreach ( qw( bucket page_size page_number ) ) {
        confess "Required argument '$_' was not provided"
          unless $s->{$_};
    }    # end foreach()

    $s->{page_number}--;
    $s->{marker}               = '' unless defined( $s->{marker} );
    $s->{__fetched_first_page} = 0;
    $s->{data}                 = [];
    $s->{pattern} ||= qr(.*);
}    # end _init()

sub marker    { shift->{marker} }
sub pattern   { shift->{pattern} }
sub bucket    { shift->{bucket} }
sub page_size { shift->{page_size} }
sub prefix    { shift->{prefix} }

sub has_prev {
    my $s = shift;

    return $s->page_number > 1;
}    # end has_prev()

sub has_next { shift->{has_next} }

sub next {
  my $s = shift;

  if( exists( $s->{data}->[ $s->{idx} ] ) ) {
    return $s->{data}->[ $s->{idx}++ ];
  } else {
    # End of the current resultset, see if we can get another page of records:
    if( my $page = $s->next_page ) {
      $s->{data} = $page;
      $s->{idx} = 0;
      return $s->{data}->[ $s->{idx}++ ];
    } else {
      # No more pages, no more data:
      return;
    }
  }
}

sub reset {
  my $s = shift;
  $s->{idx} = 0;
}


sub page_number {
    my $s = shift;
    @_ ? $s->{page_number} = $_[0] - 1 : $s->{page_number};
}    # end page_number()

# S3 returns files 100 at a time.  If we want more or less than 100, we can't
# just fetch the next page over and over - that would be inefficient and likely
# to cause errors.

# If the page size is 5 and page number is 2, then we:
#   - fetch 100 items

lib/AWS/S3/FileIterator.pm  view on Meta::CPAN

#   - iterate internally until we get to 'page 2'
#   - return the result.
# If the page size is 105 and page number is 1, then we:
#   - fetch 100 items
#   - fetch the next 100 items
#   - return the first 105 items, keeping the remaining 95 items
#   - on page '2', fetch the next 100 items and return 105 items, saving 90 items.
# If the page size is 105 and page number is 3, then we:
#   - fetch items until our internal 'start' marker is 316-420
#   - return items 316-420
sub next_page {
    my $s = shift;

    # Advance to page X before proceding:
    if ( ( !$s->{__fetched_first_page}++ ) && $s->page_number ) {

        # Advance to $s->page_number
        my $start_page = $s->page_number;
        my $to_discard = $start_page * $s->page_size;
        my $discarded  = 0;
        while ( 1 ) {

lib/AWS/S3/FileIterator.pm  view on Meta::CPAN

        delete $_->{owner};
        AWS::S3::File->new( %$_, owner => $owner );
    } @chunk;

    $s->{page_number}++;

    return unless @out;
    wantarray ? @out : \@out;
}    # end next_page()

sub _next {
    my $s = shift;

    if ( my $item = shift( @{ $s->{data} } ) ) {
        return $item;
    } else {
        if ( my @chunk = $s->_fetch() ) {
            push @{ $s->{data} }, @chunk;
            return shift( @{ $s->{data} } );
        } else {
            return;
        }    # end if()
    }    # end if()
}    # end _next()

sub _fetch {
    my ( $s ) = @_;

    my $path   = $s->{bucket}->name . '/';
    my %params = ();
    $params{marker} = $s->{marker} if $s->{marker};
    $params{prefix} = $s->{prefix} if $s->{prefix};
    $params{max_keys} = 1000;
    $params{delimiter} = $s->{delimiter} if $s->{delimiter};

    my $type     = 'ListBucket';

lib/AWS/S3/HTTPRequest.pm  view on Meta::CPAN


coerce 'HTTP::Headers'
    => from 'HashRef'
    => via { my $h = HTTP::Headers->new( %$_ ) };

has 'headers' => (
    is       => 'ro',
    required => 1,
    isa      => 'HTTP::Headers',
    lazy     => 1,
    default  => sub { HTTP::Headers->new() },
    coerce   => 1,
);

has 'content' => (
    is       => 'ro',
    required => 1,
    isa      => 'Str|ScalarRef|CodeRef',
    default  => '',
);

has 'metadata' => (
    is       => 'ro',
    required => 1,
    isa      => 'HashRef',
    default  => sub { {} },
);

has 'contenttype' => (
    is       => 'ro',
    required => 0,
    isa      => 'Str',
);

# Make the HTTP::Request object:
sub http_request {
    my $s        = shift;
    my $method   = $s->method;
    my $headers  = $s->headers;
    my $content  = $s->content;
    my $metadata = $s->metadata;

    my $uri = $s->bucket_uri( $s->path );

    my $signer = AWS::S3::Signer->new(
        s3      => $s->s3,

lib/AWS/S3/Request/CreateBucket.pm  view on Meta::CPAN

    is       => 'ro',
    isa      => 'Str',
    required => 1,
);

has 'location' => (
    is       => 'ro',
    isa      => 'Maybe[Str]',
    lazy     => 1,
    required => 0,
    default  => sub { shift->s3->region || $ENV{AWS_REGION} },
);

has '+_expect_nothing' => ( default => 1 );

sub request {
    my $s = shift;

    # By default the bucket is put in us-east-1. But if you _ask_ for
    # us-east-1 you get an error.
    my $xml = q{};
    if ( $s->location && $s->location ne 'us-east-1' ) {
        $xml = <<"XML";
<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> 
  <LocationConstraint>@{[ $s->location ]}</LocationConstraint>
</CreateBucketConfiguration>

lib/AWS/S3/Request/DeleteMulti.pm  view on Meta::CPAN

    is       => 'ro',
    isa      => 'Str',
    init_arg => undef,
    default  => 'delete'
);



has '+_expect_nothing' => ( default => 0 );

sub request {
    my $s = shift;

    my $objects = join "\n", map { "<Object><Key>@{[ $_ ]}</Key></Object>" } @{ $s->keys };

    my $xml = <<"XML";
<?xml version="1.0" encoding="UTF-8"?>
<Delete>
$objects
</Delete>
XML

lib/AWS/S3/Request/GetPreSignedUrl.pm  view on Meta::CPAN


use AWS::S3::Signer;
use URI::Escape qw(uri_escape);

with 'AWS::S3::Roles::Request';

has 'bucket' => ( is => 'ro', isa => 'Str', required => 1 );
has 'key' => ( is => 'ro', isa => 'Str', required => 1 );
has 'expires' => ( is => 'ro', isa => 'Int', required => 1 );

sub request {
    my $s = shift;

    return $s->signerv4->signed_url(
        $s->_uri,
        $s->expires,
        'GET',
    );
}

__PACKAGE__->meta->make_immutable;

lib/AWS/S3/Request/ListBucket.pm  view on Meta::CPAN

);

has 'delimiter' => (
    is       => 'ro',
    isa      => 'Str',
    required => 0,
);

has '+_expect_nothing' => ( default => 0 );

sub request {
    my $s = shift;

    my @params = ();
    push @params, 'max-keys=' . $s->max_keys;
    push @params, 'marker=' . uri_escape( $s->marker ) if $s->marker;
    push @params, 'prefix=' . $s->prefix if $s->prefix;
    push @params, 'delimiter=' . $s->delimiter if $s->delimiter;

    my $uri = $s->bucket_uri;

lib/AWS/S3/Request/SetBucketAccessControl.pm  view on Meta::CPAN

);

has 'acl_xml' => (
    is       => 'ro',
    isa      => 'Str',
    required => 0,
);

has '+_expect_nothing' => ( default => 1 );

sub request {
    my $s = shift;

    if ( $s->acl_short ) {
        my $signer = AWS::S3::Signer->new(
            s3      => $s->s3,
            method  => 'PUT',
            uri     => $s->protocol . '://' . $s->bucket . '.' . $s->endpoint . '/?acl',
            headers => [ 'x-amz-acl' => $s->acl_short ]
        );
        return $s->_send_request(

lib/AWS/S3/Request/SetBucketPolicy.pm  view on Meta::CPAN

);

has 'policy' => (
    is       => 'ro',
    isa      => 'Maybe[Str]',
    required => 1,

    # Evan Carroll 6/14/2012
    # COMMENTED THIS OUT, not sure if it ever worked on VSO
    # Must be able to decode the JSON string:
    # where => sub {
    #     eval { decode_json( $_ ); 1 };
    # }
);

has '+_expect_nothing' => ( default => 1 );

sub request {
    my $s = shift;

    my $signer = AWS::S3::Signer->new(
        s3           => $s->s3,
        method       => 'PUT',
        uri          => $s->_uri,
        content      => \$s->policy,
        content_type => '',
        content_md5  => '',
    );

lib/AWS/S3/Request/SetFileContents.pm  view on Meta::CPAN

    is       => 'ro',
    isa      => 'AWS::S3::File',
    required => 1,
);

has 'content_type' => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
    lazy     => 1,
    default  => sub { 'binary/octet-stream' },
);

has '+_expect_nothing' => ( default => 0 );

sub request {
    my $s = shift;

    my $contents;
    if ( ref( $s->file->contents ) eq 'CODE' ) {
        $contents = $s->file->contents->();
    } elsif ( ref( $s->file->contents ) eq 'SCALAR' ) {
        $contents = $s->file->contents;
    }    # end if()

    my %other_args = ();

lib/AWS/S3/ResponseParser.pm  view on Meta::CPAN


use Moose;
use XML::LibXML;
use XML::LibXML::XPathContext;

has 'expect_nothing' => (
    is       => 'ro',
    isa      => 'Bool',
    required => 1,
    default  => 0,
    trigger  => sub {
        my ( $self, $expect_nothing) = @_;
        if ( $expect_nothing ) {
            my $code = $self->response->code;
            if ( $code =~ m{^2\d\d} && !$self->response->content ) {
                return; # not sure what jdrago wanted this to do originally
            }
            else {
                if ( $self->_parse_errors() ) {
                    # die $self->friendly_error();
                }

lib/AWS/S3/ResponseParser.pm  view on Meta::CPAN

has 'type' => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
);

has 'libxml' => (
    is       => 'ro',
    isa      => 'XML::LibXML',
    required => 1,
    default  => sub { return XML::LibXML->new() },
);

has 'error_code' => (
    is       => 'rw',
    isa      => 'Str',
    required => 0,
);

has 'error_message' => (
    is       => 'rw',
    isa      => 'Str',
    required => 0,
);

has 'xpc' => (
    is       => 'ro',
    isa      => 'XML::LibXML::XPathContext',
    required => 0,
    lazy    => 1,
    clearer => '_clear_xpc',
    default => sub {
        my $self = shift;

        my $src = $self->response->content;
        return unless $src =~ m/^[[:space:]]*</s;
        my $doc = $self->libxml->parse_string( $src );

        my $xpc = XML::LibXML::XPathContext->new( $doc );
        $xpc->registerNs( 's3', 'http://s3.amazonaws.com/doc/2006-03-01/' );

        return $xpc;
    }
);

has 'friendly_error' => (
    is       => 'ro',
    isa      => 'Maybe[Str]',
    lazy     => 1,
    required => 0,
    default  => sub {
        my $s = shift;

        return unless $s->error_code || $s->error_message;
        $s->type . " call had errors: [" . $s->error_code . "] " . $s->error_message;
    }
);

sub _parse_errors {
    my $self = shift;

    my $src = $self->response->content;

    # Do not try to parse non-xml:
    unless ( $src =~ m/^[[:space:]]*</s ) {
        ( my $code = $src ) =~ s/^[[:space:]]*\([0-9]*\).*$/$1/s;
        $self->error_code( $code );
        $self->error_message( $src );
        return 1;

lib/AWS/S3/Roles/Bucket.pm  view on Meta::CPAN

package AWS::S3::Roles::Bucket;

use Moose::Role;

sub bucket_uri {
    my ( $s,$path ) = @_;

    $path      //= $s->bucket;
    my $protocol = $s->s3->secure ? 'https' : 'http';
    my $endpoint = $s->s3->endpoint;
    my $uri = "$protocol://$endpoint/$path";
    if ( $path =~ m{^([^/?]+)(.*)} && $s->is_dns_bucket( $1 ) ) {
        $uri = "$protocol://$1.$endpoint$2";
    }    # end if()

    return $uri;
}

sub is_dns_bucket {
    my ( $s,$bucket ) = @_;

    # https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
    return 0 if ( length( $bucket ) < 3 or length( $bucket ) > 63 );
    return 0 if $bucket =~ /^(?:\d{1,3}\.){3}\d{1,3}$/;

    # DNS bucket names can contain lowercase letters, numbers, and hyphens
    # so anything outside this range we say isn't a valid DNS bucket
    return $bucket =~ /[^a-z0-9-\.]/ ? 0 : 1;
}

lib/AWS/S3/Roles/BucketAction.pm  view on Meta::CPAN

package AWS::S3::Roles::BucketAction;
use Moose::Role;
use HTTP::Request;
use AWS::S3::ResponseParser;

with 'AWS::S3::Roles::Request';

sub request {
    my $s = shift;

    my $signer = AWS::S3::Signer->new(
        s3     => $s->s3,
        method => $s->_action,
        uri    => $s->_uri
    );
    $s->_send_request(
        $signer->method => $signer->uri => {
            Authorization => $signer->auth_header,

lib/AWS/S3/Roles/Request.pm  view on Meta::CPAN

has 'type' => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
);

has 'protocol' => (
    is      => 'ro',
    isa     => 'Str',
    lazy    => 1,
    default => sub {
        shift->s3->secure ? 'https' : 'http';
    }
);

has 'endpoint' => (
    is      => 'ro',
    isa     => 'Str',
    lazy    => 1,
    default => sub {
        shift->s3->endpoint;
    }
);

# XXX should be required=>1; https://rt.cpan.org/Ticket/Display.html?id=77863
has "_action" => (
    isa       => 'Str',
    is        => 'ro',
    init_arg  => undef,
    #required  => 1
);

has '_expect_nothing' => ( isa => 'Bool', is => 'ro', init_arg => undef );

has '_uri' => (
    isa     => Uri,
    is      => 'ro',
    lazy    => 1,
    default => sub {
        my $self = shift;
        my $m = $self->meta;

        my $uri = URI->new(
            $self->protocol . '://'
            . ( $m->has_attribute('bucket') ? $self->bucket . '.' : '' )
            . $self->endpoint
            . '/'
        );

lib/AWS/S3/Roles/Request.pm  view on Meta::CPAN

          if $m->has_attribute('_subresource');

        $uri;
    }
);

has 'signerv4' => (
    is       => 'ro',
    isa      => 'AWS::S3::Signer::V4',
    lazy     => 1,
    default  => sub {
        my $s = shift;
        AWS::S3::Signer::V4->new(
            -access_key => $s->s3->access_key_id,
            -secret_key => $s->s3->secret_access_key,
        );
    }
);

sub _send_request {
    my ( $s, $method, $uri, $headers, $content ) = @_;

    my $req = HTTP::Request->new( $method => $uri );
    $req->content( $content ) if $content;

    delete($headers->{Authorization}); # we will use a v4 signature
    map { $req->header( $_ => $headers->{$_} ) } keys %$headers;

    $s->_sign($req);
    my $res = $s->s3->ua->request( $req );

    # After creating a bucket and setting its location constraint, we get this
    # strange 'TemporaryRedirect' response.  Deal with it.
    if ( $res->header( 'location' ) && $res->content =~ m{>TemporaryRedirect<}s ) {
        $req->uri( $res->header( 'location' ) );
        $res = $s->s3->ua->request( $req );
    }
    return $s->parse_response( $res );
}

sub _sign {
  my ($s, $request) = @_;
  my $signer = $s->signerv4;
  if (defined $s->s3->session_token) {
    $request->header('X-Amz-Security-Token', $s->s3->session_token);
  }
  my $digest = Digest::SHA::sha256_hex($request->content);
  $request->header('X-Amz-Content-SHA256', $digest);
  $signer->sign($request, $s->s3->region, $digest);
  $request;
}

sub parse_response {
    my ( $self, $res ) = @_;

    AWS::S3::ResponseParser->new(
        response       => $res,
        expect_nothing => $self->_expect_nothing,
        type           => $self->type,
    );
}

1;

lib/AWS/S3/Signer.pm  view on Meta::CPAN

    is       => 'ro',
    isa      => enum([qw/ HEAD GET PUT POST DELETE /]),
    required => 1,
);

has 'bucket_name' => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
    lazy     => 1,
    default  => sub {
        my $s = shift;
        my $endpoint = $s->s3->endpoint;
        if ( my ( $name ) = $s->uri->host =~ m{^(.+?)\.\Q$endpoint\E} ) {
            return $name;
        } else {
            return '';
        }    # end if()
    }
);

lib/AWS/S3/Signer.pm  view on Meta::CPAN

    is       => 'ro',
    isa      => Uri,
    required => 1,
    coerce   => 1,
);

has 'headers' => (
    is       => 'ro',
    isa      => 'ArrayRef[Str]',
    lazy     => 1,
    default  => sub { [] },
);

has 'date' => (
    is       => 'ro',
    isa      => 'Str',
    default  => sub {
        time2str( time );
    }
);

has 'string_to_sign' => (
    is       => 'ro',
    isa      => 'Str',
    lazy     => 1,
    default  => sub {
        my $s = shift;

        join "\n",
          (
            $s->method, $s->content_md5,
            $s->content ? $s->content_type : '',
            $s->date || '',
            ( join "\n", grep { $_ } ( $s->canonicalized_amz_headers, $s->canonicalized_resource ) )
          );
    }
);

has 'canonicalized_amz_headers' => (
    is      => 'ro',
    isa     => 'Str',
    lazy    => 1,
    default => sub {
        my $s = shift;

        my @h   = @{ $s->headers };
        my %out = ();
        while ( my ( $k, $v ) = splice( @h, 0, 2 ) ) {
            $k = lc( $k );
            if ( exists $out{$k} ) {
                $out{$k} = [ $out{$k} ] unless ref( $out{$k} );
                push @{ $out{$k} }, $v;
            } else {

lib/AWS/S3/Signer.pm  view on Meta::CPAN

        }    # end while()

        return join "\n", @parts;
    }
);

has 'canonicalized_resource' => (
    is      => 'ro',
    isa     => 'Str',
    lazy    => 1,
    default => sub {
        my $s = shift;
        my $str = $s->bucket_name ? '/' . $s->bucket_name . $s->uri->path : $s->uri->path;

        if ( my ( $resource ) =
            ( $s->uri->query || '' ) =~ m{[&]*(acl|website|location|policy|delete|lifecycle)(?!\=)} )
        {
            $str .= '?' . $resource;
        }    # end if()
        return $str;
    }
);

has 'content_type' => (
    is       => 'ro',
    isa      => 'Str',
	lazy     => 1,
    default  => sub {
        my $s = shift;
        return '' if $s->method eq 'GET';
        return '' unless $s->content;
        return 'text/plain';
    }
);

has 'content_md5' => (
    is       => 'ro',
    isa      => 'Str',
    lazy     => 1,
    default  => sub {
        my $s = shift;
        return '' unless $s->content;
        return encode_base64( md5( ${ $s->content } ), '' );
    }
);

has 'content' => (
    is       => 'ro',
    isa      => 'Maybe[ScalarRef]',
);

has 'content_length' => (
    is       => 'ro',
    isa      => 'Int',
    lazy     => 1,
    default  => sub { length( ${ shift->content } ) }
);

has 'signature' => (
    is       => 'ro',
    isa      => 'Str',
    lazy     => 1,
    default  => sub {
        my $s    = shift;
        my $hmac = Digest::HMAC_SHA1->new( $s->s3->secret_access_key );
        $hmac->add( $s->string_to_sign() );
        return encode_base64( $hmac->digest, '' );
    }
);

sub auth_header {
    my $s = shift;

    return 'AWS ' . $s->s3->access_key_id . ':' . $s->signature;
}    # end auth_header()

sub _trim {
    my ( $value ) = @_;
    $value =~ s/^\s+//;
    $value =~ s/\s+$//;
    return $value;
}    # end _trim()

1;

lib/AWS/S3/Signer/V4.pm  view on Meta::CPAN


If the environment variables EC2_ACCESS_KEY and/or EC2_SECRET_KEY are
set, their contents are used as defaults for -access_key and
-secret_key.

If -service and/or -region is not provided, they are automtically determined
according to endpoint.

=cut

sub new {
    my $self = shift;
    my %args = @_;

    my ( $id, $secret, $token, $region, $service );
    if ( ref $args{-security_token}
        && $args{-security_token}->can('access_key_id') )
    {
        $id     = $args{-security_token}->accessKeyId;
        $secret = $args{-security_token}->secretAccessKey;
    }

lib/AWS/S3/Signer/V4.pm  view on Meta::CPAN

        service    => $args{-service},
        (
            defined( $args{-security_token} )
            ? ( security_token => $args{-security_token} )
            : ()
        ),
      },
      ref $self || $self;
}

sub access_key { shift->{access_key} }
sub secret_key { shift->{secret_key} }

=item $signer->sign($request [,$region] [,$payload_sha256_hex])

Given an HTTP::Request object, add the headers required by AWS and
then sign it with a version 4 signature by adding an "Authorization"
header.

The request must include a URL from which the AWS endpoint and service
can be derived, such as "ec2.us-east-1.amazonaws.com." In some cases
(e.g. S3 bucket operations) the endpoint does not indicate the

lib/AWS/S3/Signer/V4.pm  view on Meta::CPAN

The method returns a true value if successful. On errors, it will
throw an exception.

=item $url = $signer->signed_url($request)

This method will generate a signed GET URL for the request. The URL
will include everything needed to perform the request.

=cut

sub sign {
    my $self = shift;
    my ( $request, $region, $payload_sha256_hex ) = @_;
    $self->_add_date_header($request);
    $self->_sign( $request, $region, $payload_sha256_hex );
}

=item my $url $signer->signed_url($request_or_uri [,$expires] [,$verb])

Pass an HTTP::Request, a URI object, or just a plain URL string
containing the proper endpoint and parameters needed for an AWS REST

lib/AWS/S3/Signer/V4.pm  view on Meta::CPAN

e.g., accessing an object in a private S3 bucket.

Pass an optional $expires argument to indicate that the URL will only
be valid for a finite period of time. The value of the argument is in
seconds.

Pass an optional verb which is useful for HEAD requests, this defaults to GET.

=cut

sub signed_url {
    my $self = shift;
    my ( $arg1, $expires, $verb ) = @_;
    my ( $request, $uri );

    $verb ||= 'GET';
    $verb = uc($verb);

    my $incorrect_verbs = {
        POST => 1,
        PUT  => 1
    };

    if ( exists( $incorrect_verbs->{$verb} ) ) {
        die "Use AWS::S3::Signer::V4->sign sub for $verb method";
    }

    if ( ref $arg1 && UNIVERSAL::isa( $arg1, 'HTTP::Request' ) ) {
        $request = $arg1;
        $uri     = $request->uri;
        my $content = $request->content;
        $uri->query($content) if $content;
        if ( my $date =
            $request->header('X-Amz-Date') || $request->header('Date') )
        {

lib/AWS/S3/Signer/V4.pm  view on Meta::CPAN

        $self->_sign($request);
    }

    my ( $algorithm, $credential, $signedheaders, $signature ) =
      $request->header('Authorization') =~
      /^(\S+) Credential=(\S+), SignedHeaders=(\S+), Signature=(\S+)/;
    $uri->query_param_append( 'X-Amz-Signature' => $signature );
    return $uri;
}

sub _add_date_header {
    my $self    = shift;
    my $request = shift;
    my $datetime;
    unless ( $datetime = $request->header('x-amz-date') ) {
        $datetime = $self->_zulu_time($request);
        $request->header( 'x-amz-date' => $datetime );
    }
}

sub _scope {
    my $self = shift;
    my ( $request, $region ) = @_;
    my $host     = $request->uri->host;
    my $datetime = $self->_datetime($request);
    my ($date)   = $datetime =~ /^(\d+)T/;
    my $service;

    ( $service, $region ) = $self->parse_host( $host, $region );

    $service ||= $self->{service} || 's3';
    $region  ||= $self->{region}  || 'us-east-1';    # default
    return "$date/$region/$service/aws4_request";
}

sub parse_host {
    my $self = shift;
    my $host = shift;
    my $region = shift;

    # this entire thing should probably refactored into its own
    # distribution, a la https://github.com/zirkelc/amazon-s3-url

    # https://docs.aws.amazon.com/prescriptive-guidance/latest/defining-bucket-names-data-lakes/faq.html
    # Only lowercase letters, numbers, dashes, and dots are allowed in S3 bucket names.
    # Bucket names must be three to 63 characters in length,

lib/AWS/S3/Signer/V4.pm  view on Meta::CPAN

        $url_style = 'legacy path-style';
    }
    elsif ( exists PAAPI_REGION->{$host} ) {
        $service = 'ProductAdvertisingAPI';
        $region  = PAAPI_REGION->{$host};
    }

    return ( $service, $region, $url_style );
}

sub _parse_scope {
    my $self  = shift;
    my $scope = shift;
    return split '/', $scope;
}

sub _datetime {
    my $self    = shift;
    my $request = shift;
    return $request->header('x-amz-date') || $self->_zulu_time($request);
}

sub _algorithm { return 'AWS4-HMAC-SHA256' }

sub _sign {
    my $self = shift;
    my ( $request, $region, $payload_sha256_hex ) = @_;
    return if $request->header('Authorization');    # don't overwrite

    my $datetime = $self->_datetime($request);

    unless ( $request->header('host') ) {
        my $host = $request->uri->host;
        $request->header( host => $host );
    }

lib/AWS/S3/Signer/V4.pm  view on Meta::CPAN

    my $string_to_sign =
      $self->_string_to_sign( $datetime, $scope, $hashed_request );
    my $signature =
      $self->_calculate_signature( $secret_key, $service, $region, $date,
        $string_to_sign );
    $request->header( Authorization =>
"$algorithm Credential=$access_key/$scope, SignedHeaders=$signed_headers, Signature=$signature"
    );
}

sub _zulu_time {
    my $self     = shift;
    my $request  = shift;
    my $date     = $request->header('Date');
    my @datetime = $date ? gmtime( str2time($date) ) : gmtime();
    return strftime( '%Y%m%dT%H%M%SZ', @datetime );
}

sub _hash_canonical_request {
    my $self = shift;
    my ( $request, $hashed_payload ) =
      @_;    # (HTTP::Request,sha256_hex($content))
    my $method  = $request->method;
    my $uri     = $request->uri;
    my $path    = $uri->path || '/';
    my @params  = $uri->query_form;
    my $headers = $request->headers;
    $hashed_payload ||= sha256_hex( $request->content );

lib/AWS/S3/Signer/V4.pm  view on Meta::CPAN

    my $signed_headers = join ';', sort map { lc } keys %signed_fields;

    my $canonical_request = join( "\n",
        $method,            $path,           $canonical_query_string,
        $canonical_headers, $signed_headers, $hashed_payload );
    my $request_digest = sha256_hex($canonical_request);

    return ( $request_digest, $signed_headers );
}

sub _string_to_sign {
    my $self = shift;
    my ( $datetime, $credential_scope, $hashed_request ) = @_;
    return join( "\n",
        'AWS4-HMAC-SHA256', $datetime, $credential_scope, $hashed_request );
}

=item $signing_key = AWS::S3::Signer::V4->signing_key($secret_access_key,$service_name,$region,$date)

Return just the signing key in the event you wish to roll your own signature.

=cut

sub signing_key {
    my $self = shift;
    my ( $kSecret, $service, $region, $date ) = @_;
    my $kDate    = hmac_sha256( $date,          'AWS4' . $kSecret );
    my $kRegion  = hmac_sha256( $region,        $kDate );
    my $kService = hmac_sha256( $service,       $kRegion );
    my $kSigning = hmac_sha256( 'aws4_request', $kService );
    return $kSigning;
}

sub _calculate_signature {
    my $self = shift;
    my ( $kSecret, $service, $region, $date, $string_to_sign ) = @_;
    my $kSigning = $self->signing_key( $kSecret, $service, $region, $date );
    return hmac_sha256_hex( $string_to_sign, $kSigning );
}

1;

=back

t/001_compiles_pod.t  view on Meta::CPAN

use warnings;

use Test::More;
use File::Find;
use Moose;

if(($ENV{HARNESS_PERL_SWITCHES} || '') =~ /Devel::Cover/) {
  plan skip_all => 'HARNESS_PERL_SWITCHES =~ /Devel::Cover/';
}
if(!eval 'use Test::Pod; 1') {
  *Test::Pod::pod_file_ok = sub { SKIP: { skip "pod_file_ok(@_) (Test::Pod is required)", 1 } };
}
if(!eval 'use Test::Pod::Coverage; 1') {
  *Test::Pod::Coverage::pod_coverage_ok = sub { SKIP: { skip "pod_coverage_ok(@_) (Test::Pod::Coverage is required)", 1 } };
}

my @files;

find(
  {
    wanted => sub { /\.pm$/ and push @files, $File::Find::name },
    no_chdir => 1
  },
  -e 'blib' ? 'blib' : 'lib',
);

plan tests => @files * 3;

for my $file (@files) {
  my $module = $file; $module =~ s,\.pm$,,; $module =~ s,.*/?lib/,,; $module =~ s,/,::,g;
  ok eval "use $module; 1", "use $module" or diag $@;

t/010_basic.t  view on Meta::CPAN

  
  GET_FILE: {
    ok my $file = $bucket->file($filename), 'bucket.file(filename) works';
    is ${ $file->contents }, $test_str, 'file.contents is correct';
  };
  
  ADD_FILE_WITH_CODE: {
    my $text = "This is the content"x4;
    ok $bucket->add_file(
      key => 'code/test.txt',
      contents  => sub { return \$text }
    ), 'add file with code contents worked';
    ok my $file = $bucket->file('code/test.txt'), "got file back from bucket";
    is ${$file->contents}, $text, "file.contents on code is correct";
    $file->contents( sub { return \uc($text) } );
    is ${$file->contents}, uc($text), "file.contents on code is correct after update";
    
    $file->delete;
  };
  
  # Set contents:
  SET_CONTENTS: {
    my $new_contents = "This is the updated value"x10;
    ok my $file = $bucket->file($filename), 'bucket.file(filename) works';
    $file->contents( \$new_contents );

t/010_basic.t  view on Meta::CPAN

    }
  };
  
  # Cleanup:
  ok $bucket->delete, 'bucket.delete succeeds when bucket IS empty.';
}# end if()

cleanup();
done_testing();

sub cleanup
{
  warn "\nCleaning Up...\n";
  foreach my $bucket ( grep { $_->name =~ m{^(aws-s3-test\-\d+).+?foo$} } $s3->buckets )
  {
    warn "Bucket: ", $bucket->name, "\n";
    my $iter = $bucket->files( page_size => 100, page_number => 1 );
    while( my @files = $iter->next_page )
    {
$bucket->delete_multi( map { $_->key } @files );
#      foreach my $file ( @files )

t/aws/s3.t  view on Meta::CPAN

#!perl

use strict;
use warnings;

package Mocked::HTTP::Response;

use Moose;
extends 'HTTP::Response';

sub content { return shift->{_msg}; }

1;

package main;

use Test::More 'no_plan';
use Test::Deep;
use Test::Exception;
use Data::Section::Simple 'get_data_section';

t/aws/s3.t  view on Meta::CPAN

    $@,
    qr/Can't connect to aws-s3-test-.*?bad\.hostname/,
    'endpoint was used'
);

isa_ok(
	$s3->request( 'CreateBucket',bucket => 'foo' ),
	'AWS::S3::Request::CreateBucket'
);

subtest 'create bucket strange temporary redirect' => sub {
    plan tests => 8;    # make sure all tests in here get run

    my $i = 1;
    local *LWP::UserAgent::Determined::request = sub {
        my ( undef, $req ) = @_;

        if ( $i == 1 ) {

            # first PUT request, send a forward
            is( $req->method, 'PUT', 'bucket creation with PUT request' );
            is( $req->uri->as_string, 'http://bar.bad.hostname./', '... and with correct URI' );

            $i++;
            return HTTP::Response->new(

t/aws/s3.t  view on Meta::CPAN

    };

    my $bucket = $s3->add_bucket( name => 'bar' );
    isa_ok( $bucket, 'AWS::S3::Bucket' );
    is( $bucket->name, 'bar', '... and the right bucket got returned' );
};

# list all buckets and owner
{
    my $xml = get_data_section('ListAllMyBucketsResult.xml');
    local *LWP::UserAgent::Determined::request = sub {
        return Mocked::HTTP::Response->new( 200,$xml );
    };

    isa_ok( my $owner = $s3->owner,'AWS::S3::Owner' );
    is( $owner->id, 'bcaf1ffd86f41161ca5fb16fd081034f', '... and the owner id correct' );
    is( $owner->display_name, 'webfile', '... and the owner name is correct' );

    my @buckets = $s3->buckets;
    cmp_deeply( \@buckets,
        [ obj_isa('AWS::S3::Bucket'), obj_isa('AWS::S3::Bucket') ], '->buckets' );
    ok( ! $s3->bucket( 'does not exist' ),'!->bucket' );
    is( $s3->bucket( 'foo' )->name, 'foo', '->bucket' );
}

{
    my $xml = get_data_section('error.xml');

    local *LWP::UserAgent::Determined::request = sub {
        return Mocked::HTTP::Response->new( 400,$xml );
    };

    throws_ok { $s3->add_bucket( name => 'too many buckets', location => 'us-west-1' ) }
    qr/TooManyBuckets/, 'add_bucket throws an error';
}
__DATA__
@@ ListAllMyBucketsResult.xml
<?xml version="1.0" encoding="UTF-8"?>
<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">

t/aws/s3/bucket.t  view on Meta::CPAN

#!perl

use strict;
use warnings;

package Mocked::HTTP::Response;

use Moose;
extends 'HTTP::Response';

sub content        { shift->{_msg}; }
sub code           { 200 }
sub is_success     { 1 }
sub header         { $_[1] =~ /content-length/i ? 1 : 'header' }

1;

package main;
use Test::More;
use Test::Exception;
use FindBin qw/ $Script /;

use Carp 'confess';
$SIG{__DIE__} = \&confess;

t/aws/s3/bucket.t  view on Meta::CPAN

isa_ok(
	$bucket->files(
		page_size => 1,
		page_number => 1,
	),
	'AWS::S3::FileIterator'
);

no warnings 'once';
my $mocked_response = Mocked::HTTP::Response->new( 200,'bar' );
*LWP::UserAgent::Determined::request = sub { $mocked_response };

isa_ok( $bucket->file( 'foo' ),'AWS::S3::File' );
isa_ok(
	$bucket->add_file(
		key => 'foo',
		size => 1,
		contents => \"bar",
	),
	'AWS::S3::File'
);

t/aws/s3/file.t  view on Meta::CPAN

#!perl

use strict;
use warnings;

package Mocked::HTTP::Response;

use Moose;
extends 'HTTP::Response';

sub content        { shift->{_msg}; }
sub code           { 200 }
sub is_success     { 1 }
sub header         { $_[1] =~ /content-length/i ? 1 : 'header' }

1;

package main;

use Test::More;
use Test::Deep;
use URI::Escape qw/ uri_escape /;

use Carp 'confess';

t/aws/s3/file.t  view on Meta::CPAN

use_ok('AWS::S3::Request::SetFileContents');

monkey_patch_module();

my $path = '/path/to/';
my $key  = $ENV{AWS_TEST_KEY} // "my+image.jpg";

isa_ok(
    my $file = AWS::S3::File->new(
        key          => $path . $key,
        contents     => sub { 'test file contents' },
        is_encrypted => 0,
        bucket       => AWS::S3::Bucket->new(
            s3   => $s3,
            name => $ENV{AWS_TEST_BUCKET} // 'maibucket',
        ),
    ),
    'AWS::S3::File'
);

can_ok(

t/aws/s3/file.t  view on Meta::CPAN

ok( $file->update( contents => \'new contents' ),'update with args' );

like(
    $file->signed_url( 1406712744 ),
	qr/X-Amz-Algorithm.*X-Amz-Credential.*X-Amz-Date.*X-Amz-Signature/,
    'signed_url'
);

no warnings qw/ once redefine /;
my $mocked_response = Mocked::HTTP::Response->new( 200,'bar' );
*LWP::UserAgent::Determined::request = sub { $mocked_response };
$mocked_response->{_msg} = '';

ok( $file->delete,'->delete' );
ok( $file->_get_contents,'_get_contents' );
}

done_testing();

sub monkey_patch_module {
    # monkey patching for true(r) unit tests
    no warnings 'redefine';
    no warnings 'once';

    sub response { return shift; }
    sub header { return shift; }
    sub friendly_error { return; }

    *AWS::S3::Request::SetFileContents::request = sub {
        return bless( {},'main' );
    };
}

t/aws/s3/file_iterator.t  view on Meta::CPAN

#!perl

use strict;
use warnings;

package Mocked::HTTP::Response;

use Moose;
extends 'HTTP::Response';

sub content        { shift->{_msg}; }
sub code           { 200 }
sub is_success     { 1 }
sub header         { $_[1] =~ /content-length/i ? 1 : 'header' }

1;

package main;
use Test::More;
use Test::Deep;
use Test::Exception;
use FindBin qw/ $Script /;
use Data::Section::Simple 'get_data_section';

t/aws/s3/file_iterator.t  view on Meta::CPAN

            bucket      => $bucket,
            marker      => 'foo',
            pattern     => qr/\d/,
        );
        is( $iterator2->marker,'foo','marker passed');
        is( $iterator2->pattern,qr/\d/,'pattern passed');
        is( $iterator2->prefix,undef,'!prefix' );
    }

    my $mocked_response = Mocked::HTTP::Response->new( 200,get_data_section('ListBucketResult.xml') );
    local *LWP::UserAgent::Determined::request = sub { $mocked_response };
    
    my @pages = $iterator->next_page; # to check wantarray
    cmp_deeply( \@pages,[ obj_isa('AWS::S3::File') ],'next_page returns one ::File' );
    is( $pages[0]->key,'img/my image.jpg','... and it is the one expected' );
    is( $iterator->next_page->[0]->key,'img/my-third-image.jpg','next_page second item' );
    is( $iterator->next_page->[0]->key,'img/my image.jpg','next_page new request, first item' );

    $mocked_response = Mocked::HTTP::Response->new( 200,get_data_section('EmptyResult') );
    ok( $iterator->next_page,'next_page second item' );
    ok( ! $iterator->next_page,'no more items' );
}

subtest 'advance to page X before processing' => sub {
	my $iterator = AWS::S3::FileIterator->new(
		page_number => 5,
		page_size   => 1,
		bucket      => $bucket,
        pattern     => qr/\d+/,
	);

    my $number_of_request;
    my $xml = get_data_section('LongResult');
    my $mocked_response = Mocked::HTTP::Response->new( 200,$xml );
    local *LWP::UserAgent::Determined::request = sub { $number_of_request++; return $mocked_response };

    is( $iterator->next_page->[0]->key,5,'start at file 5' );
    is( $iterator->next_page->[0]->key,6,'... file 6' );
    is( $iterator->next_page->[0]->key,7,'... file 7' );
    is( $iterator->next_page->[0]->key,8,'... file 8' );
    is( $iterator->next_page->[0]->key,9,'... file 9' );
    is( $iterator->next_page->[0]->key,0,'do a new request and get file 0' );
    is( $number_of_request,2,'did two requests' );
};



( run in 1.490 second using v1.01-cache-2.11-cpan-a5abf4f5562 )