Apache2-S3
view release on metacpan or search on metacpan
lib/Apache2/S3.pm view on Meta::CPAN
my $args = $r->args || "";
my $sub = $args =~ s/^(acl|logging|torrent)(?:&|$)// ? $1 : "";
local $CGI::USE_PARAM_SEMICOLONS = 0;
$args = CGI->new($r, $args);
if ($is_dir)
{
$args->param('delimiter', $args->param('delimiter') || '/');
$args->param('prefix', $uri) if $uri;
}
my %note = (
'id' => $keyId,
'secret' => $keySecret,
'path' => $path,
'sub' => $sub,
'stripped' => $stripped,
($is_dir ? ('prefix' => $uri) : ()),
(($args->param('raw') or not $is_dir or $sub) ? ('raw' => 1) : ()),
(($args->param('nocache') or $is_dir or $sub) ? ('nocache' => 1) : ()),
);
$r->notes->add(__PACKAGE__."::s3_$_" => $note{$_})
foreach keys %note;
$r->proxyreq(Apache2::Const::PROXYREQ_REVERSE);
$r->uri("http://s3.amazonaws.com$path");
$r->args(($sub ? "$sub&" : "").$args->query_string);
$r->filename("proxy:http://s3.amazonaws.com$path");
$r->handler('proxy-server');
# we delay adding the authorization header to give
# mod_auth* a chance to authenticate the users request
# which would use the same header
$r->set_handlers('PerlFixupHandler' => \&s3_auth_handler);
# we set up an output filter to translate XML responses
# for directory requests into "pretty" HTML
$r->add_output_filter(\&output_filter);
return Apache2::Const::OK;
}
return Apache2::Const::DECLINED;
}
sub s3_auth_handler
{
my $r = shift;
my $h = $r->headers_in;
my ($keyId, $keySecret, $path, $sub) =
map $r->notes->get(__PACKAGE__."::s3_$_"), qw(id secret path sub);
$h->{'Date'} = POSIX::strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime);
$h->{'Authorization'} = _signature $keyId, $keySecret, join "\n",
$r->method,
$h->{'Content-MD5'} || "",
$h->{'Content-Type'} || "",
$h->{'Date'},
uri_escape($path, $ESCAPE).($sub ? "?$sub" : "");
return Apache2::Const::OK;
}
sub _xml_get_tags
{
my ($tree, $tag, @tags) = @_;
my @ret;
for (my $i = @$tree % 2; $i < @$tree; $i += 2)
{
next unless $tree->[$i] eq $tag;
push @ret, $tree->[$i+1];
last unless wantarray;
}
return unless @ret;
return _xml_get_tags($ret[0], @tags) if @tags;
return wantarray ? @ret : $ret[0];
}
sub _reformat_directory
{
my ($f, $ctx) = @_;
my $stripped = $f->r->notes->get(__PACKAGE__.'::s3_stripped');
my $prefix = $f->r->notes->get(__PACKAGE__.'::s3_prefix');
my $tree = eval {
XML::Parser->new(Style => 'Tree')->parse($ctx->{text});
};
my $list = _xml_get_tags($tree, 'ListBucketResult')
or die $ctx->{text};
my $is_truncated = _xml_get_tags($list, 'IsTruncated', TEXT) =~ /^(?:false|)$/i ? 0 : 1;
my $next_marker = _xml_get_tags($list, 'NextMarker', TEXT);
my @dirs = map +{
Name => _xml_get_tags($_, 'Prefix', TEXT),
}, _xml_get_tags($list, 'CommonPrefixes');
my @files = map +{
Name => _xml_get_tags($_, 'Key', TEXT),
Size => _xml_get_tags($_, 'Size', TEXT),
LastModified => _xml_get_tags($_, 'LastModified', TEXT) =~
/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(?:\.\d+)?Z$/
? timegm($6, $5, $4, $3, $2-1, $1) : 0,
}, _xml_get_tags($list, 'Contents');
my $ret = "";
$ret .= qq|<html><body><pre>|;
$ret .= qq|<a href="|.("$stripped$prefix" =~ m|^(.*/)[^/]+/$| ? $1 : "/").qq|">Parent Directory</a>\n|;
$ret .= qq|<a href="?marker=|.(uri_escape $next_marker).qq|">Next Page</a>\n|
if $is_truncated and $next_marker;
$ret .= sprintf(qq|<a href="%s">%s</a>%s %-18s %s\n|,
$stripped.uri_escape($_->{Name}, $ESCAPE),
HTML::Entities::encode($_->{DisplayName}),
" "x(87 - length $_->{DisplayName}),
$_->{LastModified} ? strftime("%d-%b-%Y %H:%M", localtime($_->{LastModified})) : "-",
$_->{Size} ? APR::String::format_size($_->{Size}) : "")
foreach map {
$_->{DisplayName} = $_->{Name} =~ m|([^/]+)/?$| ? $1 : $_->{Name};
$_;
} @dirs, @files;
$ret .= qq|</pre></body></html>|;
$ret;
}
sub output_filter
{
my $f = shift;
my $ctx;
unless ($ctx = $f->ctx)
{
# disable caching layer if requested
if ($f->r->notes->get(__PACKAGE__.'::s3_nocache'))
{
my $next = $f;
while ($next)
{
$next->remove if $next->frec->name =~ /^cache_\w+$/i;
$next = $next->next;
}
}
else
{
# mark as public to allow mod_cache to save it even though it includes an Authorization header
$f->r->headers_out->{'Cache-Control'} = join(",", grep defined && length,
split(/\s*,\s*/, $f->r->headers_out->{'Cache-Control'} || ""), "public");
}
# don't process this output if requested
if ($f->r->notes->get(__PACKAGE__.'::s3_raw') or lc $f->r->content_type ne 'application/xml')
{
$f->remove;
unless ($f->r->content_type eq 'application/xml')
{
# S3 supports byte-range requests, but doesn't advertise it.
$f->r->headers_out->{'Accept-Ranges'} = 'bytes';
}
return Apache2::Const::DECLINED
}
$f->r->content_type('text/html');
$f->r->headers_out->unset('Content-Length');
$f->ctx($ctx = { text => "" })
}
$ctx->{text} .= $_
( run in 2.401 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )