From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

# this package emulates an Apache request object with a CGI backend
use strict;
no strict qw(refs);
use vars qw($StructsDefined @END);
$StructsDefined = 0;
sub do_self {
my $class = shift;
if(defined($class)) {
if(ref $class or $class =~ /Apache::ASP::CGI/) {
# we called this OO style
} else {
unshift(@_, $class);
$class = undef;
}
}
my %config = @_;
$class ||= 'Apache::ASP::CGI';
my $r = $class->init($0, @ARGV);
$r->dir_config->set('CgiDoSelf', 1);
$r->dir_config->set('NoState', 0);
# init passed in config
for(keys %config) {
$r->dir_config->set($_, $config{$_});
}
&Apache::ASP::handler($r);
$r;
}
sub init {
my($class, $filename, @args) = @_;
$filename ||= $0;
# for('Class/Struct.pm') {
# next if require $_;
# die("can't load the $_ library. please make sure you installed it");
# }
# we define structs here so modperl users don't incur a runtime / memory
unless($StructsDefined) {
$StructsDefined = 1;
&Class::Struct::struct( 'Apache::ASP::CGI::connection' =>
{
'remote_ip' => "\$",
'auth_type' => "\$",
'user' => "\$",
'aborted' => "\$",
'fileno' => "\$",
}
);
&Class::Struct::struct( 'Apache::ASP::CGI' =>
{
'connection'=> 'Apache::ASP::CGI::connection',
'content_type' => "\$",
'current_callback' => "\$",
'dir_config'=> "Apache::ASP::CGI::Table",
'env' => "\%",
'filename' => "\$",
'get_basic_auth_pw' => "\$",
'headers_in' => "Apache::ASP::CGI::Table",
'headers_out'=> "Apache::ASP::CGI::Table",
'err_headers_out' => "Apache::ASP::CGI::Table",
'subprocess_env' => "Apache::ASP::CGI::Table",
'method' => "\$",
'sent_header' => "\$",
'OUT' => "\$",
}
);
}
# create struct
my $self = new();
if(defined $ENV{GATEWAY_INTERFACE} and $ENV{GATEWAY_INTERFACE} =~ /^CGI/) {
# nothing, don't need CGI object anymore
} else {
# command line
my %args = @args;
$ENV{QUERY_STRING} = join('&', map { "$_=$args{$_}" } keys %args);
}
$self->connection(Apache::ASP::CGI::connection->new);
$self->dir_config(Apache::ASP::CGI::Table->new);
$self->err_headers_out(Apache::ASP::CGI::Table->new);
$self->headers_out(Apache::ASP::CGI::Table->new);
$self->headers_in(Apache::ASP::CGI::Table->new);
$self->subprocess_env(Apache::ASP::CGI::Table->new);
my $env = $self->subprocess_env;
%$env = %ENV;
$self->filename($filename);
$self->connection->remote_ip($ENV{REMOTE_HOST} || $ENV{REMOTE_ADDR} || '0.0.0.0');
$self->connection->aborted(0);
$self->current_callback('PerlHandler');
# $self->headers_in->set('Cookie', $ENV{HTTP_COOKIE});
for my $env_key ( sort keys %ENV ) {
if($env_key =~ /^HTTP_(.+)$/ or $env_key =~ /^(CONTENT_TYPE|CONTENT_LENGTH)$/) {
my $env_header_in = $1;
my $header_key = join('-', map { ucfirst(lc($_)) } split(/\_/, $env_header_in));
$self->headers_in->set($header_key, $ENV{$env_key});
}
}
# we kill the state for now stuff for now, as it's just leaving .state
# directories everywhere you run this stuff
defined($self->dir_config->get('NoState')) || $self->dir_config->set('NoState', 1);
$self->method($ENV{REQUEST_METHOD} || 'GET');
for my $env_key ( keys %ENV ) {
$self->env($env_key, $ENV{$env_key});
}
$self->env('SCRIPT_NAME') || $self->env('SCRIPT_NAME', $filename);
# fix truncated output in standalone CGI mode under Win32
binmode(STDOUT);
bless $self, $class;
}
sub init_dir_config {
my($self, %config) = @_;
my $dir_config = $self->dir_config;
%$dir_config = %config;
$dir_config;
}
sub status {
my($self, $status) = @_;
if(defined($status)) {
$self->headers_out->set('status', $status);
} else {
$self->headers_out->get('status');
}
}
sub cgi_env { %{$_[0]->env} ; }
sub send_http_header {
my($self) = @_;
my($k, $v, $header);
$self->sent_header(1);
$header = "Content-Type: " .$self->content_type()."\n";
for my $headers ($self->headers_out, $self->err_headers_out) {
while(($k, $v) = each %$headers) {
next if ($k =~ /^content\-type$/i);
if(ref $v) {
# if ref, then we have an array for cgi_header_out for cookies
for my $value (@$v) {
$value ||= '';
$header .= "$k: $value\n";
}
} else {
$v ||= '';
$header .= "$k: $v\n";
}
}
}
$header .= "\n";
$self->print($header);
}
sub send_cgi_header {
my($self, $header) = @_;
$self->sent_header(1);
my(@left);
for(split(/\n/, $header)) {
my($name, $value) = split(/\:\s*/, $_, 2);
if($name =~ /content-type/i) {
$self->content_type($value);
} else {
push(@left, $_);
}
}
$self->print(join("\n", @left, ''));
$self->send_http_header();
}
sub print {
shift;
local $| = 1;
print STDOUT map { ref($_) =~ /SCALAR/ ? $$_ : $_; } @_;
}
sub args {
my $self = shift;
if(wantarray) {
my $params = Apache::ASP::Request->ParseParams($ENV{QUERY_STRING});
%$params;
} else {
$ENV{QUERY_STRING};
}
}
*content = *args;
sub log_error {
my($self, @args) = @_;
print STDERR @args, "\n";
}
sub register_cleanup { push(@END, $_[1]); }
# gets called when the $r get's garbage collected
sub END {
for ( @END ) {
next unless $_;
if(ref($_) && /CODE/) {
my $rv = eval { &$_ };
if($@) {
Apache::ASP::CGI->log_error("[ERROR] error executing register_cleanup code $_: $@");
}
}
}
}
sub soft_timeout { 1; };
sub lookup_uri {
die('cannot call $Server->MapPath in CGI mode');
}
sub custom_response {
die('$Response->ErrorDocument not implemented for CGI mode');
}
1;