AWS-Signature-V4

 view release on metacpan or  search on metacpan

lib/AWS/Signature/V4.pm  view on Meta::CPAN

package AWS::Signature::V4;
{ our $VERSION = '0.004' }

use v5.24;
use Moo;
use experimental qw< signatures >;
use Digest::SHA qw< sha256_hex >;
use POSIX qw< strftime >;
use Scalar::Util qw< openhandle >;

use AWS::Signature::V4::Error qw< fail shown >;
use AWS::Signature::V4::Checksum ();
use AWS::Signature::V4::Chunker ();
use AWS::Signature::V4::Credentials ();
use AWS::Signature::V4::X509 ();

use constant {

   # Headers that intermediaries commonly rewrite or that should never be
   # signed
   AUTH_PARAM => +{
      map { lc($_) => $_ } qw<
         X-Amz-Algorithm
         X-Amz-Credential
         X-Amz-Date
         X-Amz-Expires
         X-Amz-Security-Token
         X-Amz-Signature
         X-Amz-SignedHeaders
         X-Amz-X509
         X-Amz-X509-Chain
      >
   },

   # Headers that intermediaries commonly rewrite or that should never be
   # signed
   UNSIGNED => +{
      map { lc($_) => 1 } qw<
         Authorization
         Connection
         Expect
         Keep-Alive
         Proxy-Authenticate
         Proxy-Authorization
         Proxy-Connection
         TE
         Trailer
         Transfer-Encoding
         Upgrade
         User-Agent
         X-Amzn-Trace-Id
      >
   },

   # the arguments of some methods; anything else is an error
   ALLOWED_ARGS => +{
      encoded_length => +{
         map { $_ => 1 } qw<
            checksum
            signed
            trailers
         >
      },

      sign => +{
         map { $_ => 1 } qw<
            body
            body_fh
            checksum
            decoded_content_length
            headers
            method
            payload_hash
            signed_headers
            streaming
            time
            trailers
            unsigned_payload
            url
         >
      },

      presign => +{
         map { $_ => 1 } qw<
            body
            body_fh
            expires
            headers
            method
            payload_hash
            signed_headers
            time
            unsigned_payload
            url
         >
      },

      payload => [ qw< body body_fh payload_hash unsigned_payload > ],
      streaming => [ qw< decoded_content_length checksum trailers > ],
   },

   MAX_EPOCH => 253402300799,    # 9999-12-31T23:59:59Z
};

sub _copy ($value) { ref $value eq 'HASH' ? {$value->%*} : $value }

sub _hash_or_undef ($name, $value) {
   fail 400, qq{"$name" must be a hash reference}
      unless !defined $value || ref $value eq 'HASH';
}

# arguments of sign/presign as a hash: name => value pairs or a hash
# reference; undefined values are like missing ones, unknown names an error
sub _args ($what, @args) {
   @args = $args[0]->%* if @args == 1 && ref $args[0] eq 'HASH';
   fail 400, "$what takes name => value pairs or a hash reference" if @args % 2;
   my %args = @args;
   defined $args{$_} or delete $args{$_} for keys %args;

   # go looking for argument names that are outside the allowed ones
   my $allowed = ALLOWED_ARGS->{$what}
      or die "ALLOWED_ARGS does not support <$what>";
   if (my @l = grep { ! $allowed->{$_} } keys(%args)) {
      fail 400, "unsupported for $what: " . _shown_list(@l);
   }
   return %args;
}

sub _check_url ($url) {
   defined $url or fail 400, 'missing parameter "url"';
   fail 400, 'url must be a string' if ref $url;
   fail 400, 'url must be ASCII: percent-encode non-ASCII characters'
      if $url =~ m{[^\x00-\x7F]};
   fail 400, 'url must not contain control characters: percent-encode them'
      if $url =~ m{[\x00-\x1F\x7F]};
   return $url;
}

# an HTTP token (RFC 9110), like header names and methods
sub _is_token ($s) {
   defined $s && !ref $s && $s =~ m{\A[!#\$%&'*+.^_`|~0-9A-Za-z-]+\z};
}

sub _method ($method) {
   fail 400, 'invalid method ' . shown($method) . ': it must be an HTTP token'
      unless _is_token($method);
   return uc $method;
}

sub _is_count ($n) {
   defined $n && !ref $n && $n =~ m{\A (?: 0 | [1-9][0-9]* ) \z}mxs;
}

# headers as a hash or array of pairs, to a hash with lowercase names;
# undefined values are skipped, like missing headers
sub _headers_hash ($in) {
   my @pairs;
   if (ref $in eq 'HASH') {
      @pairs = map { $_ => $in->{$_} } sort keys $in->%*;    # repeatable order
   }
   elsif (ref $in eq 'ARRAY') {
      fail 400, '"headers" as an array reference must hold name => value pairs'
         if $in->@* % 2;
      @pairs = $in->@*;
   }
   elsif (defined $in) {
      fail 400, '"headers" must be a hash or array reference';
   }
   my %h;
   while (my ($k, $v) = splice @pairs, 0, 2) {
      _check_header_name($k);
      my @values = grep { defined } ref $v eq 'ARRAY' ? $v->@* : $v;
      _check_header_value($k, $_) for @values;



( run in 1.802 second using v1.01-cache-2.11-cpan-10df4c37326 )