CGI-Tiny
view release on metacpan or search on metacpan
};
subtest 'Multipart body' => sub {
local @ENV{@env_keys} = ('')x@env_keys;
local $ENV{PATH_INFO} = '/';
local $ENV{REQUEST_METHOD} = 'POST';
local $ENV{SCRIPT_NAME} = '/';
local $ENV{SERVER_PROTOCOL} = 'HTTP/1.0';
local $ENV{QUERY_STRING} = 'query=foo&snowman=snow';
my $utf8_snowman = encode 'UTF-8', 'â';
my $utf16le_snowman = encode 'UTF-16LE', "â...\n";
my $body_string = <<"EOB";
preamble\r
--delimiter\r
Content-Disposition: form-data; name="snowman"\r
\r
$utf8_snowman!\r
--delimiter\r
Content-Disposition: form-data; name=snowman\r
Content-Type: text/plain;charset=UTF-16LE\r
\r
$utf16le_snowman\r
--delimiter\r
Content-Disposition: form-data; name="newline\\\\\\""\r
\r
\r
--delimiter\r
Content-Disposition: form-data; name="empty"\r
\r
--delimiter\r
Content-Disposition: form-data; name="empty"\r
\r
\r
--delimiter\r
Content-Disposition: form-data; name="file"; filename="test.dat"\r
Content-Type: application/octet-stream\r
\r
00000000
11111111\0\r
--delimiter\r
Content-Disposition: form-data; name="file"; filename="test2.dat"\r
Content-Type: application/json\r
\r
{"test":42}\r
--delimiter\r
Content-Disposition: form-data; name="snowman"; filename="snowman\\\\\\".txt"\r
Content-Type: text/plain;charset=UTF-16LE\r
\r
$utf16le_snowman\r
--delimiter--\r
postamble
EOB
local $ENV{CONTENT_TYPE} = 'multipart/form-data; boundary=delimiter';
local $ENV{CONTENT_LENGTH} = length $body_string;
open my $in_fh, '<', \$body_string or die "failed to open handle for input: $!";
open my $out_fh, '>', \my $out_data or die "failed to open handle for output: $!";
my $parts;
my ($param_query, $params, $param_names, $param_snowman, $param_snowman_array);
my ($uploads, $upload_names, $upload_file, $upload_file_array, $upload_empty, $upload_empty_array);
cgi {
$_->set_input_handle($in_fh);
$_->set_output_handle($out_fh);
$_->set_multipart_form_options({discard_files => 0});
$_->set_multipart_form_charset('UTF-8');
$parts = $_->body_parts;
$param_query = $_->param('query');
$params = $_->body_params;
$param_names = $_->body_param_names;
$param_snowman = $_->body_param('snowman');
$param_snowman_array = $_->param_array('snowman');
$uploads = $_->uploads;
$upload_names = $_->upload_names;
$upload_file = $_->upload('file');
$upload_file_array = $_->upload_array('file');
$upload_empty = $_->upload('empty');
$upload_empty_array = $_->upload_array('empty');
$_->render;
};
ok length($out_data), 'response rendered';
my $response = _parse_response($out_data);
ok defined($response->{headers}{date}), 'Date set';
like $response->{status}, qr/^200\b/, '200 response status';
my @files;
foreach my $i (0..$#$parts) {
$files[$i] = delete $parts->[$i]{file};
if (defined $files[$i]) {
$parts->[$i]{file_contents} = do { local $/; readline $files[$i] };
}
}
is_deeply $parts, [
{headers => {'content-disposition' => 'form-data; name="snowman"'},
name => 'snowman', filename => undef, size => length($utf8_snowman) + 1, content => "$utf8_snowman!"},
{headers => {'content-disposition' => 'form-data; name=snowman', 'content-type' => 'text/plain;charset=UTF-16LE'},
name => 'snowman', filename => undef, size => length($utf16le_snowman), content => $utf16le_snowman},
{headers => {'content-disposition' => 'form-data; name="newline\\\\\\""'},
name => 'newline\"', filename => undef, size => 1, content => "\n"},
{headers => {'content-disposition' => 'form-data; name="empty"'},
name => 'empty', filename => undef, size => 0, content => ''},
{headers => {'content-disposition' => 'form-data; name="empty"'},
name => 'empty', filename => undef, size => 0, content => ''},
{headers => {'content-disposition' => 'form-data; name="file"; filename="test.dat"', 'content-type' => 'application/octet-stream'},
name => 'file', filename => 'test.dat', size => 18, file_contents => "00000000\n11111111\0"},
{headers => {'content-disposition' => 'form-data; name="file"; filename="test2.dat"', 'content-type' => 'application/json'},
name => 'file', filename => 'test2.dat', size => 11, file_contents => '{"test":42}'},
{headers => {'content-disposition' => 'form-data; name="snowman"; filename="snowman\\\\\\".txt"', 'content-type' => 'text/plain;charset=UTF-16LE'},
name => 'snowman', filename => 'snowman\".txt', size => length($utf16le_snowman), file_contents => $utf16le_snowman},
], 'right multipart body parts';
is $param_query, 'foo', 'right generic param';
is_deeply $params, [['snowman', 'â!'], ['snowman', "â...\n"], ['newline\"', "\n"], ['empty', ''], ['empty', '']], 'right multipart body params';
is_deeply $param_names, ['snowman', 'newline\"', 'empty'], 'right multipart body param names';
is $param_snowman, "â...\n", 'right multipart body param value';
is_deeply $param_snowman_array, ['snow', 'â!', "â...\n"], 'right multipart body param values';
is $uploads->[-1][0], 'snowman', 'right upload name';
my $upload_snowman = $uploads->[-1][1];
ok defined $upload_snowman, 'last upload';
is $upload_snowman->{filename}, 'snowman\".txt', 'right upload filename';
is $upload_snowman->{size}, length $utf16le_snowman, 'right upload size';
is $upload_snowman->{content_type}, 'text/plain;charset=UTF-16LE', 'right upload Content-Type';
is do { local $/; seek $upload_snowman->{file}, 0, 0; scalar readline $upload_snowman->{file} }, $utf16le_snowman, 'right upload contents';
is_deeply $upload_names, ['file', 'snowman'], 'right upload names';
is $upload_file->{filename}, 'test2.dat', 'right upload filename';
is $upload_file->{content_type}, 'application/json', 'right upload Content-Type';
is $upload_file_array->[0]{filename}, 'test.dat', 'right upload filename';
is $upload_file_array->[1]{filename}, 'test2.dat', 'right upload filename';
is $upload_empty, undef, 'missing upload';
is_deeply $upload_empty_array, [], 'missing upload array';
};
subtest 'Multipart body (discard files)' => sub {
local @ENV{@env_keys} = ('')x@env_keys;
local $ENV{PATH_INFO} = '/';
local $ENV{REQUEST_METHOD} = 'POST';
local $ENV{SCRIPT_NAME} = '/';
local $ENV{SERVER_PROTOCOL} = 'HTTP/1.0';
my $utf16le_snowman = encode 'UTF-16LE', "â...\n";
my $body_string = <<"EOB";
--delimiter\r
Content-Disposition: form-data; name=snowman\r
Content-Type: text/plain;charset=UTF-16LE\r
\r
$utf16le_snowman\r
--delimiter\r
Content-Disposition: form-data; name="file"; filename="test.dat"\r
Content-Type: application/octet-stream\r
\r
00000000
11111111\0\r
--delimiter--\r
postamble
EOB
local $ENV{CONTENT_TYPE} = 'multipart/form-data; boundary=delimiter';
local $ENV{CONTENT_LENGTH} = length $body_string;
open my $in_fh, '<', \$body_string or die "failed to open handle for input: $!";
open my $out_fh, '>', \my $out_data or die "failed to open handle for output: $!";
my $parts;
my ($params, $param_names, $param_snowman, $param_snowman_array);
my ($uploads, $upload_names, $upload_file, $upload_file_array);
cgi {
$_->set_input_handle($in_fh);
$_->set_output_handle($out_fh);
$_->set_multipart_form_options({discard_files => 1});
$_->set_multipart_form_charset('UTF-8');
$parts = $_->body_parts;
$params = $_->body_params;
$param_names = $_->body_param_names;
$param_snowman = $_->body_param('snowman');
$param_snowman_array = $_->body_param_array('snowman');
$uploads = $_->uploads;
$upload_names = $_->upload_names;
$upload_file = $_->upload('file');
$upload_file_array = $_->upload_array('file');
$_->render;
};
ok length($out_data), 'response rendered';
my $response = _parse_response($out_data);
ok defined($response->{headers}{date}), 'Date set';
like $response->{status}, qr/^200\b/, '200 response status';
is_deeply $parts, [
{headers => {'content-disposition' => 'form-data; name=snowman', 'content-type' => 'text/plain;charset=UTF-16LE'},
name => 'snowman', filename => undef, size => length($utf16le_snowman), content => $utf16le_snowman},
{headers => {'content-disposition' => 'form-data; name="file"; filename="test.dat"', 'content-type' => 'application/octet-stream'},
name => 'file', filename => 'test.dat', size => 18},
], 'right multipart body parts';
is_deeply $params, [['snowman', "â...\n"]], 'right multipart body params';
is_deeply $param_names, ['snowman'], 'right multipart body param names';
is $param_snowman, "â...\n", 'right multipart body param value';
is_deeply $param_snowman_array, ["â...\n"], 'right multipart body param values';
is $uploads->[-1][0], 'file', 'right upload name';
is_deeply $upload_names, ['file'], 'right upload names';
is $upload_file->{filename}, 'test.dat', 'right upload filename';
is $upload_file->{content_type}, 'application/octet-stream', 'right upload Content-Type';
is $upload_file_array->[0]{filename}, 'test.dat', 'right upload filename';
};
subtest 'Multipart body (parse all as files)' => sub {
local @ENV{@env_keys} = ('')x@env_keys;
local $ENV{PATH_INFO} = '/';
local $ENV{REQUEST_METHOD} = 'POST';
local $ENV{SCRIPT_NAME} = '/';
local $ENV{SERVER_PROTOCOL} = 'HTTP/1.0';
my $utf16le_snowman = encode 'UTF-16LE', "â...\n";
my $body_string = <<"EOB";
--delimiter\r
Content-Disposition: form-data; name=snowman\r
Content-Type: text/plain;charset=UTF-16LE\r
\r
$utf16le_snowman\r
--delimiter\r
Content-Disposition: form-data; name="file"; filename="test.dat"\r
Content-Type: application/octet-stream\r
\r
00000000
11111111\0\r
--delimiter--\r
postamble
EOB
local $ENV{CONTENT_TYPE} = 'multipart/form-data; boundary=delimiter';
local $ENV{CONTENT_LENGTH} = length $body_string;
open my $in_fh, '<', \$body_string or die "failed to open handle for input: $!";
open my $out_fh, '>', \my $out_data or die "failed to open handle for output: $!";
my $parts;
my ($params, $param_names, $param_snowman, $param_snowman_array);
my ($uploads, $upload_names, $upload_file, $upload_file_array);
cgi {
$_->set_input_handle($in_fh);
$_->set_output_handle($out_fh);
$_->set_multipart_form_options({parse_as_files => 1});
$_->set_multipart_form_charset('UTF-8');
$parts = $_->body_parts;
$params = $_->body_params;
$param_names = $_->body_param_names;
$param_snowman = $_->body_param('snowman');
$param_snowman_array = $_->body_param_array('snowman');
$uploads = $_->uploads;
$upload_names = $_->upload_names;
$upload_file = $_->upload('file');
$upload_file_array = $_->upload_array('file');
$_->render;
};
ok length($out_data), 'response rendered';
my $response = _parse_response($out_data);
ok defined($response->{headers}{date}), 'Date set';
like $response->{status}, qr/^200\b/, '200 response status';
my @files;
foreach my $i (0..$#$parts) {
$files[$i] = delete $parts->[$i]{file};
if (defined $files[$i]) {
$parts->[$i]{file_contents} = do { local $/; readline $files[$i] };
}
}
is_deeply $parts, [
{headers => {'content-disposition' => 'form-data; name=snowman', 'content-type' => 'text/plain;charset=UTF-16LE'},
name => 'snowman', filename => undef, size => length($utf16le_snowman), file_contents => $utf16le_snowman},
{headers => {'content-disposition' => 'form-data; name="file"; filename="test.dat"', 'content-type' => 'application/octet-stream'},
name => 'file', filename => 'test.dat', size => 18, file_contents => "00000000\n11111111\0"},
], 'right multipart body parts';
is_deeply $params, [['snowman', "â...\n"]], 'right multipart body params';
is_deeply $param_names, ['snowman'], 'right multipart body param names';
is $param_snowman, "â...\n", 'right multipart body param value';
is_deeply $param_snowman_array, ["â...\n"], 'right multipart body param values';
is $uploads->[-1][0], 'file', 'right upload name';
is_deeply $upload_names, ['file'], 'right upload names';
is $upload_file->{filename}, 'test.dat', 'right upload filename';
is $upload_file->{content_type}, 'application/octet-stream', 'right upload Content-Type';
is $upload_file_array->[0]{filename}, 'test.dat', 'right upload filename';
};
subtest 'Multipart body (parse none as files)' => sub {
local @ENV{@env_keys} = ('')x@env_keys;
local $ENV{PATH_INFO} = '/';
local $ENV{REQUEST_METHOD} = 'POST';
local $ENV{SCRIPT_NAME} = '/';
local $ENV{SERVER_PROTOCOL} = 'HTTP/1.0';
my $utf16le_snowman = encode 'UTF-16LE', "â...\n";
my $body_string = <<"EOB";
--delimiter\r
Content-Disposition: form-data; name=snowman\r
Content-Type: text/plain;charset=UTF-16LE\r
\r
$utf16le_snowman\r
--delimiter\r
Content-Disposition: form-data; name="file"; filename="test.dat"\r
Content-Type: application/octet-stream\r
\r
00000000
11111111\0\r
--delimiter--\r
postamble
EOB
local $ENV{CONTENT_TYPE} = 'multipart/form-data; boundary=delimiter';
local $ENV{CONTENT_LENGTH} = length $body_string;
open my $in_fh, '<', \$body_string or die "failed to open handle for input: $!";
open my $out_fh, '>', \my $out_data or die "failed to open handle for output: $!";
my $parts;
my ($params, $param_names, $param_snowman, $param_snowman_array);
my ($uploads, $upload_names, $upload_file, $upload_file_array);
cgi {
$_->set_input_handle($in_fh);
$_->set_output_handle($out_fh);
$_->set_multipart_form_options({parse_as_files => 0});
$_->set_multipart_form_charset('UTF-8');
$parts = $_->body_parts;
$params = $_->body_params;
$param_names = $_->body_param_names;
$param_snowman = $_->body_param('snowman');
$param_snowman_array = $_->body_param_array('snowman');
$uploads = $_->uploads;
$upload_names = $_->upload_names;
$upload_file = $_->upload('file');
$upload_file_array = $_->upload_array('file');
$_->render;
};
ok length($out_data), 'response rendered';
my $response = _parse_response($out_data);
ok defined($response->{headers}{date}), 'Date set';
like $response->{status}, qr/^200\b/, '200 response status';
is_deeply $parts, [
{headers => {'content-disposition' => 'form-data; name=snowman', 'content-type' => 'text/plain;charset=UTF-16LE'},
name => 'snowman', filename => undef, size => length($utf16le_snowman), content => $utf16le_snowman},
{headers => {'content-disposition' => 'form-data; name="file"; filename="test.dat"', 'content-type' => 'application/octet-stream'},
name => 'file', filename => 'test.dat', size => 18, content => "00000000\n11111111\0"},
], 'right multipart body parts';
is_deeply $params, [['snowman', "â...\n"]], 'right multipart body params';
is_deeply $param_names, ['snowman'], 'right multipart body param names';
is $param_snowman, "â...\n", 'right multipart body param value';
is_deeply $param_snowman_array, ["â...\n"], 'right multipart body param values';
is $uploads->[-1][0], 'file', 'right upload name';
is_deeply $upload_names, ['file'], 'right upload names';
is $upload_file->{filename}, 'test.dat', 'right upload filename';
is $upload_file->{content_type}, 'application/octet-stream', 'right upload Content-Type';
is $upload_file->{content}, "00000000\n11111111\0", 'upload contents in memory';
is $upload_file_array->[0]{filename}, 'test.dat', 'right upload filename';
};
subtest 'Multipart body (custom parsing)' => sub {
local @ENV{@env_keys} = ('')x@env_keys;
local $ENV{PATH_INFO} = '/';
local $ENV{REQUEST_METHOD} = 'POST';
local $ENV{SCRIPT_NAME} = '/';
local $ENV{SERVER_PROTOCOL} = 'HTTP/1.0';
my $utf16le_snowman = encode 'UTF-16LE', "â...\n";
my $body_string = <<"EOB";
--delimiter\r
Content-Disposition: form-data; name=snowman\r
Content-Type: text/plain;charset=UTF-16LE\r
\r
$utf16le_snowman\r
--delimiter\r
Content-Disposition: form-data; name="file"; filename="test.dat"\r
Content-Type: application/octet-stream\r
\r
00000000
11111111\0\r
--delimiter--\r
postamble
EOB
local $ENV{CONTENT_TYPE} = 'multipart/form-data; boundary=delimiter';
local $ENV{CONTENT_LENGTH} = length $body_string;
open my $in_fh, '<', \$body_string or die "failed to open handle for input: $!";
open my $out_fh, '>', \my $out_data or die "failed to open handle for output: $!";
my $file_bytes = '';
my $parts;
my ($params, $param_names, $param_snowman, $param_snowman_array);
my ($uploads, $upload_names, $upload_file, $upload_file_array);
cgi {
$_->set_input_handle($in_fh);
$_->set_output_handle($out_fh);
$_->set_multipart_form_options({on_file_buffer => sub { $file_bytes .= $_[0] }});
$_->set_multipart_form_charset('UTF-8');
$parts = $_->body_parts;
$params = $_->body_params;
$param_names = $_->body_param_names;
$param_snowman = $_->body_param('snowman');
$param_snowman_array = $_->body_param_array('snowman');
$uploads = $_->uploads;
$upload_names = $_->upload_names;
$upload_file = $_->upload('file');
$upload_file_array = $_->upload_array('file');
$_->render;
};
ok length($out_data), 'response rendered';
my $response = _parse_response($out_data);
ok defined($response->{headers}{date}), 'Date set';
like $response->{status}, qr/^200\b/, '200 response status';
is_deeply $parts, [
{headers => {'content-disposition' => 'form-data; name=snowman', 'content-type' => 'text/plain;charset=UTF-16LE'},
name => 'snowman', filename => undef, size => length($utf16le_snowman), content => $utf16le_snowman},
{headers => {'content-disposition' => 'form-data; name="file"; filename="test.dat"', 'content-type' => 'application/octet-stream'},
name => 'file', filename => 'test.dat', size => 18},
], 'right multipart body parts';
is_deeply $params, [['snowman', "â...\n"]], 'right multipart body params';
is_deeply $param_names, ['snowman'], 'right multipart body param names';
is $param_snowman, "â...\n", 'right multipart body param value';
is_deeply $param_snowman_array, ["â...\n"], 'right multipart body param values';
is $uploads->[-1][0], 'file', 'right upload name';
is_deeply $upload_names, ['file'], 'right upload names';
is $upload_file->{filename}, 'test.dat', 'right upload filename';
is $upload_file->{content_type}, 'application/octet-stream', 'right upload Content-Type';
is $upload_file->{file}, undef, 'no upload file';
is $upload_file->{content}, undef, 'no upload content';
is $upload_file_array->[0]{filename}, 'test.dat', 'right upload filename';
is $file_bytes, "00000000\n11111111\0", 'parsed file contents';
};
subtest 'Multipart body read into memory' => sub {
local @ENV{@env_keys} = ('')x@env_keys;
local $ENV{PATH_INFO} = '/';
local $ENV{REQUEST_METHOD} = 'POST';
local $ENV{SCRIPT_NAME} = '/';
local $ENV{SERVER_PROTOCOL} = 'HTTP/1.0';
my $utf8_snowman = encode 'UTF-8', 'â!';
my $body_string = <<"EOB";
--fffff\r
Content-Disposition: form-data; name="; filename=snowman\\\\"\r
\r
$utf8_snowman\r
--fffff\r
Content-Disposition: form-data; name="file"; filename="test.txt\\\\"\r
Content-Type: text/plain;charset=UTF-8\r
\r
$utf8_snowman
\r
--fffff--\r
EOB
local $ENV{CONTENT_TYPE} = 'multipart/form-data; boundary=fffff';
local $ENV{CONTENT_LENGTH} = length $body_string;
open my $in_fh, '<', \$body_string or die "failed to open handle for input: $!";
open my $out_fh, '>', \my $out_data or die "failed to open handle for output: $!";
my ($body, $parts, $params, $param_names, $param_snowman, $uploads, $upload_names, $upload_snowman);
cgi {
$_->set_input_handle($in_fh);
$_->set_output_handle($out_fh);
$body = $_->body;
$parts = $_->body_parts;
$params = $_->body_params;
$param_names = $_->body_param_names;
$param_snowman = $_->body_param('; filename=snowman\\');
$uploads = $_->uploads;
$upload_names = $_->upload_names;
$upload_snowman = $_->upload('file');
$_->render;
};
ok length($out_data), 'response rendered';
my $response = _parse_response($out_data);
ok defined($response->{headers}{date}), 'Date set';
like $response->{status}, qr/^200\b/, '200 response status';
is $body, $body_string, 'right body content bytes';
my @files;
foreach my $i (0..$#$parts) {
$files[$i] = delete $parts->[$i]{file};
if (defined $files[$i]) {
$parts->[$i]{file_contents} = do { local $/; readline $files[$i] };
}
}
is_deeply $parts, [
{headers => {'content-disposition' => 'form-data; name="; filename=snowman\\\\"'},
name => '; filename=snowman\\', filename => undef, size => length($utf8_snowman), content => $utf8_snowman},
{headers => {'content-disposition' => 'form-data; name="file"; filename="test.txt\\\\"', 'content-type' => 'text/plain;charset=UTF-8'},
name => 'file', filename => 'test.txt\\', size => length($utf8_snowman) + 1, file_contents => "$utf8_snowman\n"},
], 'right multipart body parts';
is_deeply $params, [['; filename=snowman\\', 'â!']], 'right multipart body params';
is_deeply $param_names, ['; filename=snowman\\'], 'right multipart body param names';
is $param_snowman, 'â!', 'right multipart body param value';
is $uploads->[0][0], 'file', 'right upload name';
is_deeply $upload_names, ['file'], 'right upload names';
is $upload_snowman->{filename}, 'test.txt\\', 'right upload filename';
is $upload_snowman->{content_type}, 'text/plain;charset=UTF-8', 'right upload Content-Type';
};
subtest 'Malformed multipart boundary' => sub {
local @ENV{@env_keys} = ('')x@env_keys;
local $ENV{PATH_INFO} = '/';
local $ENV{REQUEST_METHOD} = 'POST';
local $ENV{SCRIPT_NAME} = '/';
local $ENV{SERVER_PROTOCOL} = 'HTTP/1.0';
my $body_string = <<"EOB";
--\r
not a header\r
\r
----\r
EOB
local $ENV{CONTENT_TYPE} = 'multipart/form-data; boundary=""';
local $ENV{CONTENT_LENGTH} = length $body_string;
open my $in_fh, '<', \$body_string or die "failed to open handle for input: $!";
open my $out_fh, '>', \my $out_data or die "failed to open handle for output: $!";
my $error;
cgi {
$_->set_error_handler(sub { $error = $_[1] });
$_->set_input_handle($in_fh);
$_->set_output_handle($out_fh);
$_->body_parts;
$_->render;
};
ok defined($error), 'error logged';
ok length($out_data), 'response rendered';
my $response = _parse_response($out_data);
ok defined($response->{headers}{date}), 'Date set';
like $response->{status}, qr/^400\b/, '400 response status';
};
subtest 'Malformed multipart body' => sub {
local @ENV{@env_keys} = ('')x@env_keys;
local $ENV{PATH_INFO} = '/';
local $ENV{REQUEST_METHOD} = 'POST';
local $ENV{SCRIPT_NAME} = '/';
local $ENV{SERVER_PROTOCOL} = 'HTTP/1.0';
my $body_string = <<"EOB";
--fribble\r
not a header\r
\r
--fribble--\r
EOB
local $ENV{CONTENT_TYPE} = 'multipart/form-data; boundary="fribble"';
local $ENV{CONTENT_LENGTH} = length $body_string;
open my $in_fh, '<', \$body_string or die "failed to open handle for input: $!";
open my $out_fh, '>', \my $out_data or die "failed to open handle for output: $!";
my $error;
cgi {
$_->set_error_handler(sub { $error = $_[1] });
$_->set_input_handle($in_fh);
$_->set_output_handle($out_fh);
$_->body_parts;
$_->render;
};
( run in 1.050 second using v1.01-cache-2.11-cpan-b16cb0d3907 )