Apache-PAR
view release on metacpan or search on metacpan
package Apache::PAR;
use constant DEBUG => 0;
use 5.005;
use strict;
require mod_perl; # For version detection
# Since we don't use exporter, only export what we need
use vars qw($VERSION %PARFILE_LIST %MTIME_LIST %UNPACK_LIST);
$VERSION = '0.30';
unless ($mod_perl::VERSION < 1.99) {
require Apache::ServerUtil;
require APR::Table;
}
else {
require Apache;
require Apache::Server;
}
use Archive::Zip qw( AZ_OK );
Archive::Zip::setErrorHandler(sub {});
my @pardir = Apache->server->dir_config->get('PARDir');
my @parfiles = Apache->server->dir_config->get('PARFile');
my @parloc = Apache->server->dir_config->get('PARInclude');
sub handler {
my $r = shift;
foreach (keys(%MTIME_LIST)) {
my $mtime = (stat($_))[9];
if($mtime > $MTIME_LIST{$_}) {
PAR::reload_libs($_);
if($UNPACK_LIST{$_}) {
unpack_par(undef, $_, Apache->server->dir_config->get('PARTempDir'))
}
$MTIME_LIST{$_} = $mtime;
}
}
return 1;
}
sub import {
my $class = shift;
my @parentries = @_;
my $parext = Apache->server->dir_config('PARExt') || 'par';
my $conf_file = Apache->server->dir_config('PARConf') || 'web.conf';
my %parlist = ();
foreach my $parentry (@parentries) {
$parentry = Apache->server_root_relative($parentry);
$parentry =~ s/\/$//;
if(!(-e $parentry)) {
print STDERR "PAR: No such file or directory: $parentry\n";
next;
}
if(-f _) {
$parlist{$parentry} = 1;
}
elsif(-d _) {
opendir(DIR, $parentry);
my @files = readdir(DIR);
closedir(DIR);
foreach my $file (@files) {
next if($file !~ /\.$parext$/);
next if(!-f "$parentry/$file");
$parlist{"$parentry/$file"} = 1;
}
}
else {
print STDERR "PAR: Bad file type: $parentry\n";
}
}
my @pars = keys(%parlist);
eval 'require PAR; import PAR (@pars,keys(%PARFILE_LIST));';
die "Could not load PAR, $@\n" if $@;
foreach my $file (@pars) {
my $zip = Archive::Zip->new;
my $rv = $zip->read($file);
unless($rv == AZ_OK && defined($zip)) {
print STDERR "$file does not seem to be a valid PAR (Zip) file. Skipping.\n";
next;
}
my $mtime = (stat $file)[9];
$MTIME_LIST{$file} = $mtime;
my $conf_member = $zip->memberNamed($conf_file);
$err = Apache->server->add_config([split /\n/, $conf]);
} else
{
Apache->httpd_conf($conf);
}
die $err if $err;
}
map {$PARFILE_LIST{$_} = 1;} @pars;
}
sub unpack_par {
my $class = shift;
my($par, $tmpdir) = @_;
require File::Spec;
require File::Path;
require Digest::MD5;
warn "[PAR] unpacking '$par'\n" if DEBUG;
my $zip = Archive::Zip->new;
my $rv = $zip->read($par);
unless($rv == AZ_OK) {
print STDERR "Unable to read ZIP file '$par': error code $rv";
return undef;
}
$tmpdir ||= File::Spec->tmpdir;
unless($tmpdir) {
print STDERR "no temp directory specified";
return undef;
}
unless(-d $tmpdir) {
print STDERR "temp dir '$tmpdir' does not exist or is not a directory";
return undef;
}
my $tmppar = File::Spec->catdir($tmpdir, 'par', (File::Spec->splitpath($par))[1,2]);
# Instead of using full path, this will append MD5
# my $fh;
# unless(open $fh, '<', $par) {
# print STDERR "unable to read '$par': $!";
# return undef;
# }
# binmode($fh);
# my $md5 = Digest::MD5->new->addfile($fh)->hexdigest;
# my $tmppar = File::Spec->catdir($tmpdir, 'par', $md5);
# close($fh);
warn "[PAR] unpack dir is '$tmppar'\n" if DEBUG;
# mtime check for if MD5 approach isn't used
if (-d $tmppar) {
if (DEBUG) {
warn "[PAR] archive has been unpacked previously\n";
warn "[PAR] directory age: ", -M $tmppar, "\n";
warn "[PAR] archive age: ", -M $par, "\n";
}
return $tmppar if (stat($tmppar))[9] > (stat($par))[9];
warn "[PAR] removing old unpack dir '$tmppar'" if DEBUG;
File::Path::rmtree $tmppar;
}
# XXX Add signature check here
# unpack the files
foreach my $member ($zip->members) {
# $member->fileName is always in Unix format
# See also Archive::Zip::_asLocalName()
my $file = File::Spec->catfile($tmppar, split('/', $member->fileName));
warn "[PAR] extracting '$file'\n" if DEBUG;
my $rv = $member->extractToFileNamed($file);
unless($rv == AZ_OK) {
print STDERR "Error extracting '$file' from '$par': error code: $rv";
return undef;
}
if ($member->fileAttributeFormat == 3) { # unix permissions
my $perms = $member->unixFileAttributes & 0xFFF;
unless(chmod $perms, $file) {
print STDERR "chmod $perms, $file failed: $!";
return undef;
}
}
}
return $tmppar;
}
import(__PACKAGE__,@pardir,@parfiles,@parloc);
1;
__END__
=head1 NAME
Apache::PAR - Perl extension for including Perl ARchive files in a mod_perl (1.x or 2.x)
environment.
=head1 SYNOPSIS
Inside Apache configuration:
PerlSetVar PARInclude /path/to/par/archive/directory
...
PerlAddVar PARInclude /path/to/a/par/file.par
...
PerlModule Apache::PAR
In Apache/mod_perl 1.x environments on Win32 platforms, the following
should be used instead:
PerlSetVar PARInclude /path/to/par/archive/directory
...
PerlSetVar PARInclude /path/to/a/par/file.par
...
<PERL>
use Apache::PAR;
</PERL>
( run in 1.154 second using v1.01-cache-2.11-cpan-98e64b0badf )