AWS-Signature-V4
view release on metacpan or search on metacpan
t/trailer.t view on Meta::CPAN
'final chunk, trailer and trailer signature';
}
# --- caller-supplied trailers
{
my $r = start($x, streaming => 'unsigned', trailers => ['X-Amz-Checksum-Crc64nvme']);
is $r->{headers}{'x-amz-trailer'}, 'x-amz-checksum-crc64nvme', 'declared, lowercased';
my $ck = $r->{chunker};
$ck->chunk('123456789');
is $ck->finish('x-amz-checksum-crc64nvme' => 'AAAAAAAAAAA='),
"0\r\nx-amz-checksum-crc64nvme:AAAAAAAAAAA=\r\n\r\n", 'value given at finish';
$r = start($s3, streaming => 1, checksum => 'sha1', trailers => ['x-foo', 'x-bar']);
is $r->{headers}{'x-amz-trailer'}, 'x-amz-checksum-sha1,x-foo,x-bar', 'built-in first, then declared';
$ck = $r->{chunker};
$ck->chunk('123456789');
my $sig1 = hmac_sha256_hex($sts->('PAYLOAD', $r->{signature}, sha256_hex(''), sha256_hex('123456789')), $key);
my $sig2 = hmac_sha256_hex($sts->('PAYLOAD', $sig1, sha256_hex(''), sha256_hex('')), $key);
my @lines = ("x-amz-checksum-sha1:$expect{sha1}", 'x-foo:1', 'x-bar:2');
my $tsig = hmac_sha256_hex($sts->('TRAILER', $sig2, sha256_hex(join '', map { "$_\n" } @lines)), $key);
is $ck->finish('x-foo' => '1', 'x-bar' => '2'),
"0;chunk-signature=$sig2\r\n" . join('', map { "$_\r\n" } @lines)
. "x-amz-trailer-signature:$tsig\r\n\r\n",
'all trailers, in order, signed';
my $mk = sub ($signer = $x) {
my $c = start($signer, streaming => $signer == $x ? 'unsigned' : 1, checksum => 'crc32',
trailers => ['X-Foo'])->{chunker};
$c->chunk('123456789');
$c;
};
is dies { $mk->()->finish }, bad(qr/missing value for trailer 'x-foo'/), 'missing value';
is dies { $mk->()->finish('x-foo' => 1, 'x-nope' => 2) }, bad(qr/undeclared trailers: x-nope/),
'undeclared value';
is dies { $mk->()->finish('x-foo' => 1, 'x-amz-checksum-crc32' => 'x') },
bad(qr/'x-amz-checksum-crc32' is computed/), 'computed value given';
is dies { $mk->()->finish('x-foo' => 1, 'X-FOO' => 2) }, bad(qr/'x-foo' given twice/),
'same trailer twice, in different cases';
is $mk->()->finish('X-Foo' => 1), "0\r\nx-amz-checksum-crc32:$expect{crc32}\r\nx-foo:1\r\n\r\n",
'names are not case sensitive';
# a bad value leaves the chunker as it was, signed or not
my %bad_value = ("a\nb" => 'newline', "a\rb" => 'carriage return', "a\0b" => 'NUL',
"\x{263a}" => 'wide character', 'ref' => 'reference');
for my $signer ($x, $s3) {
for my $value (sort keys %bad_value) {
my $ck = $mk->($signer);
is dies { $ck->finish('x-foo' => $value eq 'ref' ? {} : $value) },
bad(qr/invalid value for trailer 'x-foo'/),
($signer == $x ? 'unsigned' : 'signed') . ": $bad_value{$value} in value";
like $ck->finish('x-foo' => 'ok'), qr/\r\nx-foo:ok\r\n/, '... and the chunker can still finish';
}
}
my $upgraded = "\xe9";
utf8::upgrade($upgraded);
my $body = $mk->()->finish('x-foo' => $upgraded);
ok !utf8::is_utf8($body), 'a value stored as UTF-8 gives a byte string';
is $body, "0\r\nx-amz-checksum-crc32:$expect{crc32}\r\nx-foo:\xe9\r\n\r\n", '... with the same bytes';
}
# --- configuration errors
ok dies { start($s3, streaming => 'unsigned') }, 'unsigned without trailers';
ok dies { start($s3, streaming => 1, checksum => 'md5') }, 'unknown checksum';
ok dies { start($s3, streaming => 1, checksum => 'crc32', trailers => ['x-amz-checksum-crc32']) }, 'same trailer twice';
ok dies { start($s3, streaming => 1, trailers => ['bad name']) }, 'invalid trailer name';
ok dies { start($s3, checksum => 'crc32') }, 'checksum without streaming';
ok dies { start($s3, trailers => ['x-foo']) }, 'trailers without streaming';
ok dies { start($s3, streaming => 'sideways') }, 'unknown streaming mode';
ok dies { start($x, streaming => 'signed', checksum => 'crc32') }, 'signed chunks need credentials';
ok lives { start($x, streaming => 'unsigned', checksum => 'crc32') }, 'unsigned works with x509';
# --- encoded_length equals the real length
my @cases = (
[signed => 1, {}],
[signed => 1, {checksum => 'crc32c'}, {streaming => 1, checksum => 'crc32c'}],
[signed => 1, {trailers => {'x-amz-checksum-crc64nvme' => 12}}, {streaming => 1, trailers => ['x-amz-checksum-crc64nvme']}, {'x-amz-checksum-crc64nvme' => 'A' x 12}],
[signed => 0, {checksum => 'sha256'}, {streaming => 'unsigned', checksum => 'sha256'}],
[signed => 0, {checksum => 'sha1', trailers => {'x-foo' => 3}}, {streaming => 'unsigned', checksum => 'sha1', trailers => ['x-foo']}, {'x-foo' => 'abc'}],
);
for my $case (@cases) {
my ($label, $signed, $opts, $sign, $values) = @$case;
$sign //= {streaming => 1};
for my $size (0, 1, 15, 16, 17, 255, 256, 300, 5000) {
for my $chunk (16, 256, 1000) {
my $c = start($signed ? $s3 : $x, %$sign, decoded_content_length => $size)->{chunker};
my $data = 'z' x $size;
my $body = '';
for (my $off = 0; $off < $size; $off += $chunk) {
$body .= $c->chunk(substr($data, $off, $chunk));
}
$body .= $c->finish(%{$values // {}});
is length($body), AWS::Signature::V4->encoded_length($size, $chunk, signed => $signed, %$opts),
"encoded_length: " . join(',', $signed ? 'signed' : 'unsigned', sort keys %$opts) . " size $size chunk $chunk";
}
}
}
is(AWS::Signature::V4->encoded_length(66560, 65536), 66824, 'still the AWS docs value without trailers');
ok dies { AWS::Signature::V4->encoded_length(10, 5, checksum => 'nope') }, 'encoded_length: unknown checksum';
done_testing;
( run in 0.740 second using v1.01-cache-2.11-cpan-10df4c37326 )