| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Amazon::SQS::Simple::Base; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 2 |  |  | 2 |  | 6 | use strict; | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 42 |  | 
| 4 | 2 |  |  | 2 |  | 4 | use warnings; | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 39 |  | 
| 5 | 2 |  |  | 2 |  | 6 | use Carp qw( croak carp ); | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 76 |  | 
| 6 | 2 |  |  | 2 |  | 814 | use Digest::HMAC_SHA1; | 
|  | 2 |  |  |  |  | 8114 |  | 
|  | 2 |  |  |  |  | 82 |  | 
| 7 | 2 |  |  | 2 |  | 12 | use Digest::SHA qw(hmac_sha256 sha256); | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 78 |  | 
| 8 | 2 |  |  | 2 |  | 1086 | use LWP::UserAgent; | 
|  | 2 |  |  |  |  | 82539 |  | 
|  | 2 |  |  |  |  | 98 |  | 
| 9 | 2 |  |  | 2 |  | 1162 | use MIME::Base64; | 
|  | 2 |  |  |  |  | 1133 |  | 
|  | 2 |  |  |  |  | 126 |  | 
| 10 | 2 |  |  | 2 |  | 11 | use URI::Escape; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 82 |  | 
| 11 | 2 |  |  | 2 |  | 1332 | use XML::Simple; | 
|  | 2 |  |  |  |  | 17360 |  | 
|  | 2 |  |  |  |  | 18 |  | 
| 12 | 2 |  |  | 2 |  | 182 | use HTTP::Date; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 103 |  | 
| 13 | 2 |  |  | 2 |  | 1043 | use HTTP::Request::Common; | 
|  | 2 |  |  |  |  | 3290 |  | 
|  | 2 |  |  |  |  | 103 |  | 
| 14 | 2 |  |  | 2 |  | 854 | use AWS::Signature4; | 
|  | 2 |  |  |  |  | 29444 |  | 
|  | 2 |  |  |  |  | 81 |  | 
| 15 | 2 |  |  | 2 |  | 16 | use POSIX qw(strftime); | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 15 |  | 
| 16 | 2 |  |  | 2 |  | 1487 | use Encode qw(encode); | 
|  | 2 |  |  |  |  | 19721 |  | 
|  | 2 |  |  |  |  | 184 |  | 
| 17 | 2 |  |  | 2 |  | 1281 | use Data::Dumper; | 
|  | 2 |  |  |  |  | 11705 |  | 
|  | 2 |  |  |  |  | 156 |  | 
| 18 | 2 |  |  | 2 |  | 1256 | use VM::EC2::Security::CredentialCache; | 
|  | 2 |  |  |  |  | 915363 |  | 
|  | 2 |  |  |  |  | 65 |  | 
| 19 |  |  |  |  |  |  |  | 
| 20 | 2 |  |  | 2 |  | 13 | use base qw(Exporter); | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 182 |  | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | use constant ({ | 
| 23 | 2 |  |  |  |  | 2333 | SQS_VERSION_2012_11_05 => '2012-11-05', | 
| 24 |  |  |  |  |  |  | BASE_ENDPOINT          => 'http://sqs.us-east-1.amazonaws.com', | 
| 25 |  |  |  |  |  |  | DEF_MAX_GET_MSG_SIZE   => 4096, # Messages larger than this size will use a POST request. | 
| 26 |  |  |  |  |  |  | MAX_RETRIES            => 4, | 
| 27 | 2 |  |  | 2 |  | 8 | }); | 
|  | 2 |  |  |  |  | 3 |  | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | our $DEFAULT_SQS_VERSION = SQS_VERSION_2012_11_05; | 
| 31 |  |  |  |  |  |  | our @EXPORT = qw(SQS_VERSION_2012_11_05); | 
| 32 |  |  |  |  |  |  | our $URI_SAFE_CHARACTERS = '^A-Za-z0-9-_.~'; # defined by AWS, same as URI::Escape defaults | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | sub new { | 
| 35 | 3 |  |  | 3 | 0 | 1651 | my $class      = shift; | 
| 36 | 3 |  |  |  |  | 7 | my @args = @_; | 
| 37 | 3 | 100 | 66 |  |  | 16 | if (scalar(@args) >= 2 && $args[0] ne 'UseIAMRole') { | 
| 38 | 2 |  |  |  |  | 3 | my $access_key = shift @args; | 
| 39 | 2 |  |  |  |  | 3 | my $secret_key = shift @args; | 
| 40 | 2 |  |  |  |  | 6 | @args = (AWSAccessKeyId => $access_key, | 
| 41 |  |  |  |  |  |  | SecretKey => $secret_key, @args); | 
| 42 |  |  |  |  |  |  | } | 
| 43 | 3 |  |  |  |  | 13 | my $self = { | 
| 44 |  |  |  |  |  |  | Endpoint         => +BASE_ENDPOINT, | 
| 45 |  |  |  |  |  |  | SignatureVersion => 4, | 
| 46 |  |  |  |  |  |  | Version          => $DEFAULT_SQS_VERSION, | 
| 47 |  |  |  |  |  |  | @args | 
| 48 |  |  |  |  |  |  | }; | 
| 49 |  |  |  |  |  |  |  | 
| 50 | 3 | 50 |  |  |  | 11 | if (!defined($self->{UserAgent})) { | 
| 51 | 3 |  |  |  |  | 18 | $self->{UserAgent} = LWP::UserAgent->new(keep_alive => 4); | 
| 52 |  |  |  |  |  |  | } | 
| 53 |  |  |  |  |  |  |  | 
| 54 | 3 | 50 |  |  |  | 4161 | if (defined($self->{Timeout})) { | 
| 55 | 0 |  |  |  |  | 0 | $self->{UserAgent}->timeout($self->{Timeout}); | 
| 56 |  |  |  |  |  |  | } | 
| 57 |  |  |  |  |  |  |  | 
| 58 | 3 | 50 |  |  |  | 9 | if (!defined($self->{Region})) { | 
| 59 | 3 |  |  |  |  | 7 | $self->{Region} = 'us-east-1'; | 
| 60 |  |  |  |  |  |  | } | 
| 61 |  |  |  |  |  |  |  | 
| 62 | 3 |  |  |  |  | 10 | $self->{UserAgent}->env_proxy; | 
| 63 |  |  |  |  |  |  |  | 
| 64 | 3 | 100 | 66 |  |  | 3630 | if (!$self->{UseIAMRole} && (!$self->{AWSAccessKeyId} || !$self->{SecretKey})) { | 
|  |  |  | 33 |  |  |  |  | 
| 65 | 1 |  |  |  |  | 225 | croak "Missing AWSAccessKey or SecretKey"; | 
| 66 |  |  |  |  |  |  | } | 
| 67 |  |  |  |  |  |  |  | 
| 68 | 2 |  |  |  |  | 5 | $self = bless($self, $class); | 
| 69 | 2 |  |  |  |  | 7 | return $self; | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | sub _api_version { | 
| 73 | 1 |  |  | 1 |  | 25 | my $self = shift; | 
| 74 | 1 |  |  |  |  | 7 | return $self->{Version}; | 
| 75 |  |  |  |  |  |  | } | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | sub _dispatch { | 
| 78 | 0 |  |  | 0 |  |  | my $self         = shift; | 
| 79 | 0 |  | 0 |  |  |  | my $params       = shift || {}; | 
| 80 | 0 |  | 0 |  |  |  | my $force_array  = shift || []; | 
| 81 | 0 |  |  |  |  |  | my $url          = $self->{Endpoint}; | 
| 82 | 0 |  |  |  |  |  | my $response; | 
| 83 |  |  |  |  |  |  | my $post_body; | 
| 84 | 0 |  |  |  |  |  | my $post_request = 0; | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | $params = { | 
| 87 |  |  |  |  |  |  | Version             => $self->{Version}, | 
| 88 | 0 |  |  |  |  |  | %$params | 
| 89 |  |  |  |  |  |  | }; | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 0 | 0 | 0 |  |  |  | if (!$params->{Timestamp} && !$params->{Expires}) { | 
| 92 | 0 |  |  |  |  |  | $params->{Timestamp} = _timestamp(); | 
| 93 |  |  |  |  |  |  | } | 
| 94 |  |  |  |  |  |  |  | 
| 95 | 0 |  |  |  |  |  | foreach my $try (1..MAX_RETRIES) { | 
| 96 |  |  |  |  |  |  |  | 
| 97 | 0 |  |  |  |  |  | my $req = HTTP::Request->new(POST => $url); | 
| 98 | 0 |  |  |  |  |  | $req->header(host => URI->new($url)->host); | 
| 99 | 0 |  |  |  |  |  | my $now = time; | 
| 100 | 0 |  |  |  |  |  | my $http_date = strftime('%Y%m%dT%H%M%SZ', gmtime($now)); | 
| 101 | 0 |  |  |  |  |  | my $date = strftime('%Y%m%d', gmtime($now)); | 
| 102 |  |  |  |  |  |  |  | 
| 103 | 0 |  |  |  |  |  | $req->protocol('HTTP/1.1'); | 
| 104 | 0 |  |  |  |  |  | $req->header('Date' => $http_date); | 
| 105 | 0 |  |  |  |  |  | $req->header('x-amz-target', 'AmazonSQSv20121105.' . $params->{Action}); | 
| 106 | 0 |  |  |  |  |  | $req->header('content-type' => 'application/x-www-form-urlencoded;charset=utf-8'); | 
| 107 |  |  |  |  |  |  |  | 
| 108 | 0 | 0 |  |  |  |  | if ($self->{UseIAMRole}) { | 
| 109 | 0 |  |  |  |  |  | my $creds = VM::EC2::Security::CredentialCache->get(); | 
| 110 | 0 | 0 |  |  |  |  | defined($creds) || die("Unable to retrieve IAM role credentials"); | 
| 111 | 0 |  |  |  |  |  | $self->{AWSAccessKeyId} = $creds->accessKeyId; | 
| 112 | 0 |  |  |  |  |  | $self->{SecretKey} = $creds->secretAccessKey; | 
| 113 | 0 |  |  |  |  |  | $req->header('x-amz-security-token' => $creds->sessionToken); | 
| 114 |  |  |  |  |  |  | } | 
| 115 |  |  |  |  |  |  |  | 
| 116 | 0 |  |  |  |  |  | $params->{AWSAccessKeyId} = $self->{AWSAccessKeyId}; | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 0 |  |  |  |  |  | my $escaped_params = $self->_escape_params($params); | 
| 119 | 0 |  |  |  |  |  | my $payload = join('&', map { $_ . '=' . $escaped_params->{$_} } keys %$escaped_params); | 
|  | 0 |  |  |  |  |  |  | 
| 120 | 0 |  |  |  |  |  | $req->content($payload); | 
| 121 | 0 |  |  |  |  |  | $req->header('Content-Length', length($payload)); | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | my $signer = AWS::Signature4->new(-access_key => $self->{AWSAccessKeyId}, | 
| 124 | 0 |  |  |  |  |  | -secret_key => $self->{SecretKey}); | 
| 125 | 0 |  |  |  |  |  | $signer->sign($req); | 
| 126 |  |  |  |  |  |  |  | 
| 127 | 0 |  |  |  |  |  | $self->_debug_log($req->as_string()); | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 0 |  |  |  |  |  | $response = $self->{UserAgent}->request($req); | 
| 130 |  |  |  |  |  |  |  | 
| 131 | 0 | 0 |  |  |  |  | if ($response->is_success) { # note, 500 and 503 are NOT success :D | 
| 132 | 0 |  |  |  |  |  | $self->_debug_log($response->content); | 
| 133 | 0 |  |  |  |  |  | my $href = XMLin($response->content, ForceArray => $force_array, KeyAttr => {}); | 
| 134 | 0 |  |  |  |  |  | return $href; | 
| 135 |  |  |  |  |  |  | } else { | 
| 136 |  |  |  |  |  |  | # advice from internal AWS support - most client libraries try 3 times in the face | 
| 137 |  |  |  |  |  |  | # of 500 errors, so ours should too | 
| 138 |  |  |  |  |  |  | # use exponential backoff. | 
| 139 |  |  |  |  |  |  |  | 
| 140 | 0 | 0 | 0 |  |  |  | if ($response->code == 500 || $response->code == 503) { | 
| 141 | 0 |  |  |  |  |  | my $sleep_amount= 2 ** $try * 50 * 1000; | 
| 142 | 0 |  |  |  |  |  | $self->_debug_log("Doing sleep for: $sleep_amount"); | 
| 143 | 0 |  |  |  |  |  | Time::HiRes::usleep($sleep_amount); | 
| 144 | 0 |  |  |  |  |  | next; | 
| 145 |  |  |  |  |  |  | } | 
| 146 | 0 |  |  |  |  |  | die("Got an error: " . $response->as_string()); | 
| 147 |  |  |  |  |  |  | } | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | # if we fall out of the loop, then we have either a non-500 error or a persistent 500. | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 0 |  |  |  |  |  | my $msg; | 
| 153 | 0 |  |  |  |  |  | eval { | 
| 154 | 0 |  |  |  |  |  | my $href = XMLin($response->content); | 
| 155 | 0 |  |  |  |  |  | $msg = $href->{Error}{Message}; | 
| 156 |  |  |  |  |  |  | }; | 
| 157 |  |  |  |  |  |  |  | 
| 158 | 0 |  |  |  |  |  | my $error = "ERROR: On calling $params->{Action}: " . $response->status_line; | 
| 159 | 0 | 0 |  |  |  |  | $error .= " ($msg)" if $msg; | 
| 160 | 0 |  |  |  |  |  | croak $error; | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | sub _debug_log { | 
| 164 | 0 |  |  | 0 |  |  | my ($self, $msg) = @_; | 
| 165 | 0 | 0 |  |  |  |  | return unless $self->{_Debug}; | 
| 166 | 0 |  |  |  |  |  | chomp($msg); | 
| 167 | 0 |  |  |  |  |  | print {$self->{_Debug}} $msg . "\n\n"; | 
|  | 0 |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | } | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | sub _escape_params { | 
| 171 | 0 |  |  | 0 |  |  | my ($self, $params) = @_; | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | # Need to escape + characters in signature | 
| 174 |  |  |  |  |  |  | # see http://docs.amazonwebservices.com/AWSSimpleQueueService/2006-04-01/Query_QueryAuth.html | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | # Likewise, need to escape + characters in ReceiptHandle | 
| 177 |  |  |  |  |  |  | # Many characters are possible in MessageBody: | 
| 178 |  |  |  |  |  |  | #    #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] | 
| 179 |  |  |  |  |  |  | # probably should encode all keys and values for consistency and future-proofing | 
| 180 | 0 |  |  |  |  |  | my $to_escape = qr{^(?:Signature|MessageBody|ReceiptHandle)|\.\d+\.(?:MessageBody|ReceiptHandle)$}; | 
| 181 | 0 |  |  |  |  |  | foreach my $key (keys %$params) { | 
| 182 | 0 | 0 |  |  |  |  | next unless $key =~ m/$to_escape/; | 
| 183 | 0 |  |  |  |  |  | my $octets = encode('utf-8-strict', $params->{$key}); | 
| 184 | 0 |  |  |  |  |  | $params->{$key} = uri_escape($octets, $URI_SAFE_CHARACTERS); | 
| 185 |  |  |  |  |  |  | } | 
| 186 | 0 |  |  |  |  |  | return $params; | 
| 187 |  |  |  |  |  |  | } | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | sub _escape_param { | 
| 190 | 0 |  |  | 0 |  |  | my $params  = shift; | 
| 191 | 0 |  |  |  |  |  | my $single  = shift; | 
| 192 | 0 |  |  |  |  |  | my $multi_n = shift; | 
| 193 |  |  |  |  |  |  |  | 
| 194 | 0 | 0 |  |  |  |  | if ($params->{$single}) { | 
| 195 | 0 |  |  |  |  |  | $params->{$single} = uri_escape($params->{$single}); | 
| 196 |  |  |  |  |  |  | } else { | 
| 197 | 0 |  |  |  |  |  | foreach my $i (1..10) { | 
| 198 | 0 |  |  |  |  |  | my $multi = $multi_n; | 
| 199 | 0 |  |  |  |  |  | $multi =~ s/\.n\./\.$i\./; | 
| 200 | 0 | 0 |  |  |  |  | if ($params->{$multi}) { | 
| 201 | 0 |  |  |  |  |  | $params->{$multi} = uri_escape($params->{$multi}); | 
| 202 |  |  |  |  |  |  | } else { | 
| 203 | 0 |  |  |  |  |  | last; | 
| 204 |  |  |  |  |  |  | } | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | sub _max_get_msg_size { | 
| 210 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 211 |  |  |  |  |  |  | # a user-defined cut-off | 
| 212 | 0 | 0 |  |  |  |  | if (defined $self->{MAX_GET_MSG_SIZE}) { | 
| 213 | 0 |  |  |  |  |  | return $self->{MAX_GET_MSG_SIZE}; | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  | # the default cut-off | 
| 216 |  |  |  |  |  |  | else { | 
| 217 | 0 |  |  |  |  |  | return DEF_MAX_GET_MSG_SIZE; | 
| 218 |  |  |  |  |  |  | } | 
| 219 |  |  |  |  |  |  | } | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | sub _timestamp { | 
| 222 | 0 |  |  | 0 |  |  | my $t = shift; | 
| 223 | 0 | 0 |  |  |  |  | if (!defined($t)) { | 
| 224 | 0 |  |  |  |  |  | $t = time; | 
| 225 |  |  |  |  |  |  | } | 
| 226 | 0 |  |  |  |  |  | my $formatted_time = HTTP::Date::time2isoz($t); | 
| 227 | 0 |  |  |  |  |  | $formatted_time =~ s/ /T/; | 
| 228 | 0 |  |  |  |  |  | return $formatted_time; | 
| 229 |  |  |  |  |  |  | } | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | 1; | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | __END__ | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | =head1 NAME | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | Amazon::SQS::Simple::Base - No user-serviceable parts included | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | =head1 AUTHOR | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | Copyright 2007-2008 Simon Whitaker E<lt>swhitaker@cpan.orgE<gt> | 
| 242 |  |  |  |  |  |  | Copyright 2013-2017 Mike (no relation) Whitaker E<lt>penfold@cpan.orgE<gt> | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify it | 
| 245 |  |  |  |  |  |  | under the same terms as Perl itself. | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | =cut | 
| 248 |  |  |  |  |  |  |  |