blob: b0ce3598b9229a83004e31509202d2eeceeb09b4 [file] [log] [blame]
package Apache::PerlRun;
use strict;
use vars qw($Debug);
use Apache::Constants qw(:common &OPT_EXECCGI);
BEGIN {
OPT_EXECCGI() if $ENV{MOD_PERL}; #preload, :common are alread pre-loaded
}
unless (defined $Apache::Registry::NameWithVirtualHost) {
$Apache::Registry::NameWithVirtualHost = 1;
}
unless (defined $Apache::Registry::MarkLine) {
$Apache::Registry::MarkLine = 1;
}
$Debug ||= 0;
my $Is_Win32 = $^O eq "MSWin32";
sub new {
my($class, $r) = @_;
my $filename = $r->filename;
$r->warn("Apache::PerlRun->new for $filename in process $$")
if $Debug && $Debug & 4;
return bless {r=>$r}, $class;
}
sub xlog_error {
my($r, $msg) = @_;
$r->log_error($msg);
$r->notes('error-notes', $msg);
}
sub can_compile {
my($pr) = @_;
my $r = $pr->{r};
my $filename = $r->filename;
#XXX reported problems with $r->finfo
# if (-r $r->finfo && -s _) {
if (-r $filename && -s _) {
if (!($r->allow_options & OPT_EXECCGI)) {
$r->log_reason("Options ExecCGI is off in this directory",
$filename);
return FORBIDDEN;
}
if (-d _) {
return DECLINED;
}
unless (-x _ or $Is_Win32) {
$r->log_reason("file permissions deny server execution",
$filename);
return FORBIDDEN;
}
$pr->{'mtime'} = -M _;
return wantarray ? (OK, $pr->{'mtime'}) : OK;
}
xlog_error($r, "$filename not found or unable to stat");
return NOT_FOUND;
}
sub mark_line {
my $filename = shift->{r}->filename;
return $Apache::Registry::MarkLine ?
"\n#line 1 $filename\n" : "";
}
sub sub_wrap {
my($pr, $code, $package) = @_;
$code ||= $pr->{'code'};
$package ||= $pr->{'namespace'};
my $line = $pr->mark_line;
my $sub = join(
'',
'package ',
$package,
';use Apache qw(exit);',
'sub handler {',
$line,
$$code,
"\n}", # last line comment without newline?
);
$pr->{'sub'} = \$sub;
}
sub cached {
my($pr) = @_;
exists $Apache::Registry->{$pr->namespace}{'mtime'};
}
sub should_compile {
my($pr, $package, $mtime) = @_;
$package ||= $pr->{'namespace'};
$mtime ||= $pr->{'mtime'};
!($pr->cached
&&
$Apache::Registry->{$package}{'mtime'} <= $mtime);
}
sub set_mtime {
my($pr, $mtime, $package) = @_;
$mtime ||= $pr->{'mtime'};
$package ||= $pr->{'namespace'};
$Apache::Registry->{$package}{'mtime'} = $mtime;
}
sub compile {
my($pr, $eval) = @_;
$eval ||= $pr->{'sub'};
# don't use $r, but something else, so the script won't use
# inherited $r by mistake
my $_r = $pr->{r};
$_r->clear_rgy_endav;
$_r->log_error("Apache::PerlRun->compile") if $Debug && $Debug & 4;
Apache->untaint($$eval);
{
no strict; #so eval'd code doesn't inherit our bits
eval $$eval;
}
$_r->stash_rgy_endav;
return $pr->error_check;
}
sub run {
my $pr = shift;
my $package = $pr->{'namespace'};
my $r = $pr->{r};
my $rc = OK;
my $cv = \&{"$package\::handler"};
my $oldwarn = $^W;
eval { $rc = &{$cv}($r, @_) } if $r->seqno;
$pr->{status} = $rc;
$^W = $oldwarn;
my $errsv = "";
if($@) {
$errsv = $@;
$@ = ''; #XXX fix me, if we don't do this Apache::exit() breaks
$@{$r->uri} = $errsv;
}
if($errsv) {
xlog_error($r, $errsv);
return SERVER_ERROR;
}
return wantarray ? (OK, $rc) : OK;
}
sub status {
shift->{r}->status;
}
sub namespace_from {
my($pr) = @_;
my $r = $pr->{r};
my $uri = $r->uri;
$r->log_error(sprintf "Apache::PerlRun->namespace escaping %s",
$uri) if $Debug && $Debug & 4;
my $path_info = $r->path_info;
my $script_name = $path_info && $uri =~ /$path_info$/ ?
substr($uri, 0, length($uri)-length($path_info)) :
$uri;
if ($Apache::Registry::NameWithVirtualHost && $r->server->is_virtual) {
my $name = $r->get_server_name;
$script_name = join "", $name, $script_name if $name;
}
$script_name =~ s:/+$:/__INDEX__:;
return $script_name;
}
sub namespace {
my($pr, $root) = @_;
return $pr->{'namespace'} if $pr->{'namespace'};
my $script_name = $pr->namespace_from;
# Escape everything into valid perl identifiers
$script_name =~ s/([^A-Za-z0-9_\/])/sprintf("_%2x",unpack("C",$1))/eg;
# second pass cares for slashes and words starting with a digit
$script_name =~ s{
(/+) # directory
(\d?) # package's first character
}[
"::" . (length $2 ? sprintf("_%2x",unpack("C",$2)) : "")
]egx;
$Apache::Registry::curstash = $script_name;
$root ||= "Apache::ROOT";
$pr->{r}->log_error("Apache::PerlRun->namespace: package $root$script_name")
if $Debug && $Debug & 4;
$pr->{'namespace'} = $root.$script_name;
return $pr->{'namespace'};
}
sub readscript {
my $pr = shift;
$pr->{'code'} = $pr->{r}->slurp_filename;
}
sub error_check {
my $pr = shift;
if ($@ and substr($@,0,4) ne " at ") {
$pr->{r}->log_error("PerlRun: `$@'");
$pr->{r}->notes('error-notes', $@);
$@{$pr->{r}->uri} = $@;
$@ = ''; #XXX fix me, if we don't do this Apache::exit() breaks
return SERVER_ERROR;
}
return OK;
}
#XXX not good enough yet
my(%switches) = (
'T' => sub {
Apache::warn("Apache::PerlRun: T switch ignored, ".
"enable with 'PerlTaintCheck On'\n")
unless $Apache::__T; "";
},
'w' => sub { 'BEGIN {$^W = 1;}; $^W = 1;' },
);
sub parse_cmdline {
my($pr, $code) = @_;
$code ||= $pr->{'code'};
my($line) = $$code =~ /^(.*)$/m;
my(@cmdline) = split /\s+/, $line;
return $code unless @cmdline;
return $code unless shift(@cmdline) =~ /^\#!/;
my($s, @s, $prepend);
$prepend = "";
for $s (@cmdline) {
next unless $s =~ s/^-//;
last if substr($s,0,1) eq "-";
for (split //, $s) {
next unless $switches{$_};
#print STDERR "parsed `$_' switch\n";
$prepend .= &{$switches{$_}};
}
}
$$code =~ s/^/$prepend/ if $prepend;
return $code;
}
sub chdir_file {
my($pr, $dir) = @_;
my $r = $pr->{r};
$r->chdir_file($dir ? $dir : $r->filename);
}
sub set_script_name {
*0 = \(shift->{r}->filename);
}
sub handler ($$) {
my($class, $r);
if (@_ >= 2) {
($class, $r) = (shift, shift);
}
else {
($class, $r) = (__PACKAGE__, shift);
}
my $pr = $class->new($r);
my $rc = $pr->can_compile;
return $rc unless $rc == OK;
my $package = $pr->namespace;
my $code = $pr->readscript;
$pr->parse_cmdline($code);
$pr->set_script_name;
$pr->chdir_file;
my $line = $pr->mark_line;
#make sure this hooks are restored to their original state
local $SIG{__DIE__} = $SIG{__DIE__};
local $SIG{__WARN__} = $SIG{__WARN__};
my %orig_inc = %INC;
my $eval = join '',
'package ',
$package,
';use Apache qw(exit);',
$line,
$$code,
"\n";
$rc = $pr->compile(\$eval);
$pr->chdir_file("$Apache::Server::CWD/");
#in case .pl files do not declare package ...;
for (keys %INC) {
next if $orig_inc{$_};
next if /\.pm$/;
delete $INC{$_};
}
if(my $opt = $r->dir_config("PerlRunOnce")) {
$r->child_terminate if lc($opt) eq "on";
}
$pr->flush_namespace($package);
return $rc;
}
BEGIN {
if ($] < 5.006) {
$INC{'warnings.pm'} = __FILE__;
*warnings::unimport = sub {};
}
}
sub flush_namespace {
my($self, $package) = @_;
$package ||= $self->namespace;
no strict 'refs';
my $tab = \%{$package.'::'};
for (keys %$tab) {
my $fullname = join '::', $package, $_;
#code/hash/array/scalar might be imported
#make sure the gv does not point elsewhere
#before undefing each
if (%$fullname) {
*{$fullname} = {};
undef %$fullname;
}
if (@$fullname) {
*{$fullname} = [];
undef @$fullname;
}
if ($$fullname) {
my $tmp; #argh, no such thing as an anonymous scalar
*{$fullname} = \$tmp;
undef $$fullname;
}
if (defined &$fullname) {
no warnings;
local $^W = 0;
if (defined(my $p = prototype $fullname)) {
*{$fullname} = eval "sub ($p) {}";
}
else {
*{$fullname} = sub {};
}
undef &$fullname;
}
if (*{$fullname}{IO}) {
if (fileno $fullname) {
close $fullname;
}
}
}
}
1;
__END__
=head1 NAME
Apache::PerlRun - Run unaltered CGI scripts under mod_perl
=head1 SYNOPSIS
#in httpd.conf
Alias /cgi-perl/ /perl/apache/scripts/
PerlModule Apache::PerlRun
<Location /cgi-perl>
SetHandler perl-script
PerlHandler Apache::PerlRun
Options +ExecCGI
#optional
PerlSendHeader On
...
</Location>
=head1 DESCRIPTION
This module's B<handler> emulates the CGI environment,
allowing programmers to write scripts that run under CGI or
mod_perl without change. Unlike B<Apache::Registry>, the
B<Apache::PerlRun> handler does not cache the script inside of a
subroutine. Scripts will be "compiled" every request. After the
script has run, it's namespace is flushed of all variables and
subroutines.
The B<Apache::Registry> handler is much faster than
B<Apache::PerlRun>. However, B<Apache::PerlRun> is much faster than
CGI as the fork is still avoided and scripts can use modules which
have been pre-loaded at server startup time. This module is meant for
"Dirty" CGI Perl scripts which relied on the single request lifetime
of CGI and cannot run under B<Apache::Registry> without cleanup.
=head1 CAVEATS
If your scripts still have problems running under the I<Apache::PerlRun>
handler, the I<PerlRunOnce> option can be used so that the process running
the script will be shutdown. Add this to your httpd.conf:
<Location ...>
PerlSetVar PerlRunOnce On
...
</Location>
=head1 SEE ALSO
perl(1), mod_perl(3), Apache::Registry(3)
=head1 AUTHOR
Doug MacEachern
=cut