# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package Apache2::compat; use strict; use warnings FATAL => 'all'; no warnings 'redefine'; #1.xx compat layer #some of this will stay as-is #some will be implemented proper later on #there's enough here to get simple registry scripts working #add to startup.pl: #use Apache2::compat (); #use lib ...; #or something to find 1.xx Apache2::Registry #Alias /perl /path/to/perl/scripts # # Options +ExecCGI # SetHandler modperl # PerlResponseHandler Apache2::Registry # use Apache2::Connection (); use Apache2::ServerRec (); use Apache2::ServerUtil (); use Apache2::Access (); use Apache2::Module (); use Apache2::RequestRec (); use Apache2::RequestIO (); use Apache2::RequestUtil (); use Apache2::Response (); use Apache2::SubRequest (); use Apache2::Filter (); use Apache2::Util (); use Apache2::Log (); use Apache2::URI (); use APR::Date (); use APR::Table (); use APR::Pool (); use APR::URI (); use APR::Util (); use APR::Brigade (); use APR::Bucket (); use mod_perl2 (); use Symbol (); use File::Spec (); use APR::Const -compile => qw(FINFO_NORM); BEGIN { $INC{'Apache.pm'} = __FILE__; $INC{'Apache/Constants.pm'} = __FILE__; $INC{'Apache/File.pm'} = __FILE__; $INC{'Apache/Table.pm'} = __FILE__; } ($Apache::Server::Starting, $Apache::Server::ReStarting) = Apache2::ServerUtil::restart_count() == 1 ? (1, 0) : (0, 1); # api => "overriding code" # the overriding code, needs to "return" the original CODE reference # when eval'ed , so that it can be restored later my %overridable_mp2_api = ( 'Apache2::RequestRec::filename' => <<'EOI', { require Apache2::RequestRec; require APR::Finfo; my $orig_sub = *Apache2::RequestRec::filename{CODE}; *Apache2::RequestRec::filename = sub { my ($r, $newfile) = @_; my $old_filename; if (defined $newfile) { $old_filename = $r->$orig_sub($newfile); die "'$newfile' doesn't exist" unless -e $newfile; $r->finfo(APR::Finfo::stat($newfile, APR::Const::FINFO_NORM, $r->pool)); } else { $old_filename = $r->$orig_sub(); } return $old_filename; }; $orig_sub; } EOI 'Apache2::RequestRec::notes' => <<'EOI', { require Apache2::RequestRec; my $orig_sub = *Apache2::RequestRec::notes{CODE}; *Apache2::RequestRec::notes = sub { my $r = shift; return wantarray() ? ($r->table_get_set(scalar($r->$orig_sub), @_)) : scalar($r->table_get_set(scalar($r->$orig_sub), @_)); }; $orig_sub; } EOI 'Apache2::RequestRec::finfo' => <<'EOI', { require APR::Finfo; my $orig_sub = *APR::Finfo::finfo{CODE}; sub Apache2::RequestRec::finfo { my $r = shift; stat $r->filename; \*_; } $orig_sub; } EOI 'Apache2::Connection::local_addr' => <<'EOI', { require Apache2::Connection; require Socket; require APR::SockAddr; my $orig_sub = *Apache2::Connection::local_addr{CODE}; *Apache2::Connection::local_addr = sub { my $c = shift; Socket::pack_sockaddr_in($c->$orig_sub->port, Socket::inet_aton($c->$orig_sub->ip_get)); }; $orig_sub; } EOI 'Apache2::Connection::remote_addr' => <<'EOI', { require Apache2::Connection; require APR::SockAddr; require Socket; my $orig_sub = *Apache2::Connection::remote_addr{CODE}; *Apache2::Connection::remote_addr = sub { my $c = shift; if (@_) { my $addr_in = shift; my ($port, $addr) = Socket::unpack_sockaddr_in($addr_in); $c->$orig_sub->ip_set($addr); $c->$orig_sub->port_set($port); } else { Socket::pack_sockaddr_in($c->$orig_sub->port, Socket::inet_aton($c->$orig_sub->ip_get)); } }; $orig_sub; } EOI 'Apache2::Module::top_module' => <<'EOI', { require Apache2::Module; my $orig_sub = *Apache2::Module::top_module{CODE}; *Apache2::Module::top_module = sub { shift; $orig_sub->(@_); }; $orig_sub; } EOI 'Apache2::Module::get_config' => <<'EOI', { require Apache2::Module; my $orig_sub = *Apache2::Module::get_config{CODE}; *Apache2::Module::get_config = sub { shift; $orig_sub->(@_); }; $orig_sub; } EOI 'APR::URI::unparse' => <<'EOI', { require APR::URI; my $orig_sub = *APR::URI::unparse{CODE}; *APR::URI::unparse = sub { my ($uri, $flags) = @_; if (defined $uri->hostname && !defined $uri->scheme) { # we do this only for back compat, the new APR::URI is # protocol-agnostic and doesn't fallback to 'http' when the # scheme is not provided $uri->scheme('http'); } $orig_sub->(@_); }; $orig_sub; } EOI 'Apache2::Util::ht_time' => <<'EOI', { require Apache2::Util; my $orig_sub = *Apache2::Util::ht_time{CODE}; *Apache2::Util::ht_time = sub { my $r = Apache2::compat::request('Apache2::Util::ht_time'); return $orig_sub->($r->pool, @_); }; $orig_sub; } EOI ); my %overridden_mp2_api = (); # this function enables back-compatible APIs which can't coexist with # mod_perl 2.0 APIs with the same name and therefore it should be # avoided if possible. # # it expects a list of fully qualified functions, like # "Apache2::RequestRec::finfo" sub override_mp2_api { my (@subs) = @_; for my $sub (@subs) { unless (exists $overridable_mp2_api{$sub}) { die __PACKAGE__ . ": $sub is not overridable"; } if (exists $overridden_mp2_api{$sub}) { warn __PACKAGE__ . ": $sub has been already overridden"; next; } $overridden_mp2_api{$sub} = eval $overridable_mp2_api{$sub}; unless (exists $overridden_mp2_api{$sub} && ref($overridden_mp2_api{$sub}) eq 'CODE') { die "overriding $sub didn't return a CODE ref"; } } } # restore_mp2_api does the opposite of override_mp2_api(), it removes # the overriden API and restores the original mod_perl 2.0 API sub restore_mp2_api { my (@subs) = @_; for my $sub (@subs) { unless (exists $overridable_mp2_api{$sub}) { die __PACKAGE__ . ": $sub is not overridable"; } unless (exists $overridden_mp2_api{$sub}) { warn __PACKAGE__ . ": can't restore $sub, " . "as it has not been overridden"; next; } # XXX: 5.8.2+ can't delete and assign at once - gives: # Attempt to free unreferenced scalar # after perl_clone. the 2 step works ok. to reproduce: # t/TEST -maxclients 1 perl/ithreads2.t compat/request.t my $original_sub = $overridden_mp2_api{$sub}; delete $overridden_mp2_api{$sub}; no warnings 'redefine'; no strict 'refs'; *$sub = $original_sub; } } sub request { my $what = shift; my $r = Apache2::RequestUtil->request; unless ($r) { die "cannot use $what ", "without 'SetHandler perl-script' ", "or 'PerlOptions +GlobalRequest'"; } $r; } { my $orig_sub = *Apache2::Module::top_module{CODE}; *Apache2::Module::top_module = sub { $orig_sub->(); }; } { my $orig_sub = *Apache2::Module::get_config{CODE}; *Apache2::Module::get_config = sub { shift if $_[0] eq 'Apache2::Module'; $orig_sub->(@_); }; } package Apache::Server; # XXX: is that good enough? see modperl/src/modules/perl/mod_perl.c:367 our $CWD = Apache2::ServerUtil::server_root(); our $AddPerlVersion = 1; sub warn { shift if @_ and $_[0] eq 'Apache::Server'; Apache2::ServerRec::warn(@_); } package Apache; sub request { return Apache2::compat::request(@_); } sub unescape_url_info { my ($class, $string) = @_; Apache2::URI::unescape_url($string); $string =~ tr/+/ /; $string; } #sorry, have to use $r->Apache2::args at the moment #for list context splitting sub args { my $r = shift; my $args = $r->args; return $args unless wantarray; return $r->parse_args($args); } sub server_root_relative { my $class = shift; if (@_ && defined($_[0]) && File::Spec->file_name_is_absolute($_[0])) { return File::Spec->catfile(@_); } else { File::Spec->catfile(Apache2::ServerUtil::server_root(), @_); } } sub exit { require ModPerl::Util; my $status = 0; my $nargs = @_; if ($nargs == 2) { $status = $_[1]; } elsif ($nargs == 1 and $_[0] =~ /^\d+$/) { $status = $_[0]; } ModPerl::Util::exit($status); } #XXX: warn sub import { } sub untaint { shift; require ModPerl::Util; ModPerl::Util::untaint(@_); } sub module { require Apache2::Module; die 'Usage: Apache2->module($name)' if @_ != 2; return Apache2::Module::loaded($_[1]); } sub gensym { return Symbol::gensym(); } sub define { shift if @_ == 2; Apache2::ServerUtil::exists_config_define(@_); } sub log_error { Apache2::ServerUtil->server->log_error(@_); } sub warn { shift if @_ and $_[0] eq 'Apache'; Apache2::ServerRec::warn(@_); } sub httpd_conf { shift; my $obj; eval { $obj = Apache2::RequestUtil->request }; $obj = Apache2::ServerUtil->server if $@; my $err = $obj->add_config([split /\n/, join '', @_]); die $err if $err; } # mp2 always can stack handlers sub can_stack_handlers { 1; } sub push_handlers { shift; Apache2::ServerUtil->server->push_handlers(@_); } sub set_handlers { shift; Apache2::ServerUtil->server->set_handlers(@_); } sub get_handlers { shift; Apache2::ServerUtil->server->get_handlers(@_); } package Apache::Constants; use Apache2::Const (); sub import { my $class = shift; my $package = scalar caller; my @args = @_; # treat :response as :common - it's not perfect # but simple and close enough for the majority my %args = map { s/^:response$/:common/; $_ => 1 } @args; Apache2::Const->compile($package => keys %args); } #no need to support in 2.0 sub export {} sub SERVER_VERSION { Apache2::ServerUtil::get_server_version() } package Apache2::RequestRec; use Apache2::Const -compile => qw(REMOTE_NAME); #no longer exist in 2.0 sub soft_timeout {} sub hard_timeout {} sub kill_timeout {} sub reset_timeout {} # this function is from mp1's Apache2::SubProcess 3rd party module # which is now a part of mp2 API. this function doesn't exist in 2.0. sub cleanup_for_exec {} sub current_callback { require ModPerl::Util; return ModPerl::Util::current_callback(); } sub send_http_header { my ($r, $type) = @_; # since send_http_header() in mp1 was telling mod_perl not to # parse headers and in mp2 one must call $r->content_type($type) to # perform the same, we make sure that this happens $type = $r->content_type || 'text/html' unless defined $type; $r->content_type($type); } #we support Apache2::RequestUtil->request; this is needed to support $r->request #XXX: seems sorta backwards *request = \&Apache2::request; sub table_get_set { my ($r, $table) = (shift, shift); my ($key, $value) = @_; if (1 == @_) { return wantarray() ? ($table->get($key)) : scalar($table->get($key)); } elsif (2 == @_) { if (defined $value) { return wantarray() ? ($table->set($key, $value)) : scalar($table->set($key, $value)); } else { return wantarray() ? ($table->unset($key)) : scalar($table->unset($key)); } } elsif (0 == @_) { return $table; } else { my $name = (caller(1))[3]; $r->warn("Usage: \$r->$name([key [,val]])"); } } sub header_out { my $r = shift; return wantarray() ? ($r->table_get_set(scalar($r->headers_out), @_)) : scalar($r->table_get_set(scalar($r->headers_out), @_)); } sub header_in { my $r = shift; return wantarray() ? ($r->table_get_set(scalar($r->headers_in), @_)) : scalar($r->table_get_set(scalar($r->headers_in), @_)); } sub err_header_out { my $r = shift; return wantarray() ? ($r->table_get_set(scalar($r->err_headers_out), @_)) : scalar($r->table_get_set(scalar($r->err_headers_out), @_)); } sub register_cleanup { shift->pool->cleanup_register(@_); } *post_connection = \®ister_cleanup; sub get_remote_host { my ($r, $type) = @_; $type = Apache2::Const::REMOTE_NAME unless defined $type; $r->connection->get_remote_host($type, $r->per_dir_config); } sub parse_args { my ($r, $string) = @_; return () unless defined $string and $string; return map { tr/+/ /; s/%([0-9a-fA-F]{2})/pack("C",hex($1))/ge; $_; } split /[=&;]/, $string, -1; } use Apache2::Const -compile => qw(MODE_READBYTES); use APR::Const -compile => qw(SUCCESS BLOCK_READ); use constant IOBUFSIZE => 8192; sub content { my $r = shift; my $bb = APR::Brigade->new($r->pool, $r->connection->bucket_alloc); my $data = ''; my $seen_eos = 0; do { $r->input_filters->get_brigade($bb, Apache2::Const::MODE_READBYTES, APR::Const::BLOCK_READ, IOBUFSIZE); while (!$bb->is_empty) { my $b = $bb->first; if ($b->is_eos) { $seen_eos++; last; } if ($b->read(my $buf)) { $data .= $buf; } $b->delete; } } while (!$seen_eos); $bb->destroy; return $data unless wantarray; return $r->parse_args($data); } sub server_root_relative { my $r = shift; File::Spec->catfile(Apache2::ServerUtil::server_root(), @_); } sub clear_rgy_endav { my ($r, $script_name) = @_; require ModPerl::Global; my $package = 'Apache2::ROOT' . $script_name; ModPerl::Global::special_list_clear(END => $package); } sub stash_rgy_endav { #see run_rgy_endav } #if somebody really wants to have END subroutine support #with the 1.x Apache2::Registry they will need to configure: # PerlHandler Apache2::Registry Apache2::compat::run_rgy_endav sub Apache2::compat::run_rgy_endav { my $r = shift; require ModPerl::Global; require Apache2::PerlRun; #1.x's my $package = Apache2::PerlRun->new($r)->namespace; ModPerl::Global::special_list_call(END => $package); } sub seqno { 1; } sub chdir_file { #XXX resolve '.' in @INC to basename $r->filename } #XXX: would like to have a proper implementation #that reads line-by-line as defined by $/ #the best way will probably be to use perlio in 5.8.0 #anything else would be more effort than it is worth sub READLINE { my $r = shift; my $line; $r->read($line, $r->headers_in->get('Content-length')); $line ? $line : undef; } #XXX: howto convert PerlIO to apr_file_t #so we can use the real ap_send_fd function #2.0 ap_send_fd() also has an additional offset parameter sub send_fd_length { my ($r, $fh, $length) = @_; my $buff; my $total_bytes_sent = 0; my $len; return 0 if $length == 0; if (($length > 0) && ($total_bytes_sent + IOBUFSIZE) > $length) { $len = $length - $total_bytes_sent; } else { $len = IOBUFSIZE; } binmode $fh; while (CORE::read($fh, $buff, $len)) { $total_bytes_sent += $r->puts($buff); } $total_bytes_sent; } sub send_fd { my ($r, $fh) = @_; $r->send_fd_length($fh, -1); } sub is_main { !shift->main } # really old back-compat methods, they shouldn't be used in mp1 *cgi_var = *cgi_env = \&Apache2::RequestRec::subprocess_env; package Apache::File; use Fcntl (); use Symbol (); use Carp (); sub new { my ($class) = shift; my $fh = Symbol::gensym; my $self = bless $fh, ref($class)||$class; if (@_) { return $self->open(@_) ? $self : undef; } else { return $self; } } sub open { my ($self) = shift; Carp::croak("no Apache2::File object passed") unless $self && ref($self); # cannot forward @_ to open() because of its prototype if (@_ > 1) { my ($mode, $file) = @_; CORE::open $self, $mode, $file; } else { my $file = shift; CORE::open $self, $file; } } sub close { my ($self) = shift; CORE::close $self; } my $TMPNAM = 'aaaaaa'; my $TMPDIR = $ENV{'TMPDIR'} || $ENV{'TEMP'} || '/tmp'; ($TMPDIR) = $TMPDIR =~ /^([^<>|;*]+)$/; #untaint my $Mode = Fcntl::O_RDWR()|Fcntl::O_EXCL()|Fcntl::O_CREAT(); my $Perms = 0600; sub tmpfile { my $class = shift; my $limit = 100; my $r = Apache2::compat::request('Apache::File->tmpfile'); while ($limit--) { my $tmpfile = "$TMPDIR/${$}" . $TMPNAM++; my $fh = $class->new; sysopen $fh, $tmpfile, $Mode, $Perms or die "failed to open $tmpfile: $!"; $r->pool->cleanup_register(sub { unlink $tmpfile }); if ($fh) { return wantarray ? ($tmpfile, $fh) : $fh; } } } # the following functions now live in Apache2::RequestIO # * discard_request_body # the following functions now live in Apache2::Response # * meets_conditions # * set_content_length # * set_etag # * set_last_modified # * update_mtime # the following functions now live in Apache2::RequestRec # * mtime package Apache::Util; sub size_string { my ($size) = @_; if (!$size) { $size = " 0k"; } elsif ($size == -1) { $size = " -"; } elsif ($size < 1024) { $size = " 1k"; } elsif ($size < 1048576) { $size = sprintf "%4dk", ($size + 512) / 1024; } elsif ($size < 103809024) { $size = sprintf "%4.1fM", $size / 1048576.0; } else { $size = sprintf "%4dM", ($size + 524288) / 1048576; } return $size; } *unescape_uri = \&Apache2::URI::unescape_url; *escape_path = \&Apache2::Util::escape_path; sub escape_uri { my $path = shift; my $r = Apache2::compat::request('Apache2::Util::escape_uri'); Apache2::Util::escape_path($path, $r->pool); } #tmp compat until ap_escape_html is reworked to not require a pool my %html_escapes = ( '<' => 'lt', '>' => 'gt', '&' => 'amp', '"' => 'quot', ); %html_escapes = map { $_, "&$html_escapes{$_};" } keys %html_escapes; my $html_escape = join '|', keys %html_escapes; sub escape_html { my $html = shift; $html =~ s/($html_escape)/$html_escapes{$1}/go; $html; } *parsedate = \&APR::Date::parse_http; *validate_password = \&APR::Util::password_validate; sub Apache2::URI::parse { my ($class, $r, $uri) = @_; $uri ||= $r->construct_url; APR::URI->parse($r->pool, $uri); } package Apache::Table; sub new { my ($class, $r, $nelts) = @_; $nelts ||= 10; APR::Table::make($r->pool, $nelts); } package Apache::SIG; use Apache2::Const -compile => 'DECLINED'; sub handler { # don't set the SIGPIPE return Apache2::Const::DECLINED; } package Apache2::Connection; # auth_type and user records don't exist in 2.0 conn_rec struct # 'PerlOptions +GlobalRequest' is required sub auth_type { shift; Apache2::RequestUtil->request->ap_auth_type(@_) } sub user { shift; Apache2::RequestUtil->request->user(@_) } 1; __END__