package proxy_impl; use overload; #use 5.020; # recommended for $& without performance penalty # regex syntax # . literal dot (like "\." in normaql regexes) # · match one character (like "." in normal regexes) # ** match anything (like ".*" in normal regexes) sub uriregex { overload::constant qr => sub { local $_ = shift; s/\./\\./g; s/·/./g; s/\*\*/.*/g; $_ }; } BEGIN { uriregex } use Apache2::Const -compile => qw( OK DECLINED DONE NOT_FOUND SERVER_ERROR AUTH_REQUIRED FORBIDDEN PROXYREQ_REVERSE ); use Apache2::RequestUtil (); use Apache2::RequestRec (); use Apache2::Connection (); use Apache2::Util (); use Apache2::URI (); use APR::Const -compile => qw( FILETYPE_DIR FINFO_NORM ); use APR::Error (); use APR::Finfo (); use common::sense; # per-request values, cached here for performance, use them our $req; # Apache2::RquestRec our $host; # $req->hostname our $uri; # $req->uri, already resolved .. and protects against %xx, hopefully our $ip; # $req->connection->client_ip, alternative ->useragent_ip # finish immediately with given status sub status($) { die \(my $status = shift); } #sub escape_path($) { # Apache2::Util::escape_path shift, $req->pool #} # escapes everything not allowed in a url hpath sub pesc($) { local $_ = shift; s/([^a-zA-z0-9\$\-_.+!*'(),;?&=\/])/sprintf "%%%02x", ord $1/ge; $_ } sub err($) { warn "$_[0]"; status 500; } # external redirect with status code sub redirect($$) { my ($status, $location) = @_; # $location =~ s,^(http://[^/:]+)/,$1:34567/,;#d# $req->headers_out->set (Location => $location); status $status; } # permanent external redirect sub rperm($) { redirect 301, shift } # temporary redirect, could be internal, but never is sub rtemp($) { redirect 302, shift } # internal eedirect, TODO sub rint($) { &rtemp } # serve some path, do not call directly sub _rpathname($) { my $path = shift; my $finfo = eval { APR::Finfo::stat $path, APR::Const::FINFO_NORM, $req->pool } or status Apache2::Const::NOT_FOUND; # let mod_dir, mod_autoindex and the default-handler handle this $req->filename ($path); $req->finfo ($finfo); # warn "serve <$path,",$req->handler,">\n";#d# status Apache2::Const::OK; } # serve a regular file from disk sub rfile($) { $req->handler ("default-handler"); &_rpathname } # serve a directory from disk sub rdir($) { my $path = shift; if ($req->uri !~ m,/$,) { # redirect dir to dir/ rperm $req->construct_url (pesc $req->uri . "/"); } elsif (-e "$path/index.html") { # mod_dir emulation, display index.html if any $req->content_type ("text/html"); rfile "$path/index.html"; } elsif (-e "$path/index.xhtml") { $req->content_type ("text/html"); # we assume to follow the compatibility guidelines rfile "$path/index.xhtml"; } else { # let mod_autoindex handle it later $req->handler ("httpd/unix-directory"); _rpathname $path; } } # like rpath, but assumes caller already stat'ed sub rpath_nostat($) { -d _ ? &rdir : &rfile } # serve a generic path, can be dir or file sub rpath($) { stat $_[0]; &rpath_nostat } # sets SCRIPT_NAME and PATH_INFO from previous regex match # pathinfo must be specified via a regex match # either # - $1 has matched SCRIPT_NAME # - OR $1 as above, and $2 matches PATH_INFO (for extra checking) # - OR (?...) and (?...) exist and match SCRIPT_NAME and PATH_INFO. sub _get_pathinfo() { my ($name, $path); if (exists $+{name} && exists $+{path}) { $name = $+{name}; $path = $+{path}; } elsif (defined $1) { $name = $1; $path = defined $2 ? $2 : substr "$`$&$'", $+[1]; } else { err "$uri: cannot set pathinfo"; } if ($uri ne "$name$path") { err "$uri: imperfect split into <$name> and <$path>"; } ($name, $path) } sub _set_pathinfo() { my ($name, $path) = _get_pathinfo; $req->uri ("$name$path"); $req->path_info ($path); } # run a cgi script, first argument is script, # also sets pathinfo, see _get_pathinfo sub rcgi($) { my ($path) = @_; _set_pathinfo; $req->handler ("cgi-script"); $req->notes->set ("alias-forced-type" => "cgi-script"); # leave a note for mod_cgi, so it ignores missing ExecCGI _rpathname $path; } # simplest reverse proxy, target is target url for this request sub rproxy($) { my ($target) = @_; $req->proxyreq (Apache2::Const::PROXYREQ_REVERSE); $req->filename ("proxy:$target"); $req->handler ("proxy-server"); $req->subprocess_env->set ("proxy-sendchunked", 1); #$notes->set ("proxy-nocanon", 1); #$env->set ("proxy-initial-not-pooled", 1); # disable compression, see http://www.apachetutor.org/admin/reverseproxies # $req->headers_in->unset ("accept-encoding"); # alternatively recompress, SetOutputFilter INFLATE;DEFLATE status Apache2::Const::OK; } # host, port # also sets pathinfo, see _get_pathinfo sub rscgi($;$) { my ($target) = @_; _set_pathinfo; rproxy $target; # notes: uds_path } # reverse proxy # path is local root uri # target is target root url # suffix is appended to target url # @lines is extra config lines sub rproxy_html($$$@) { my ($path, $target, $suffix, @lines) = @_; (my $cpath = $path) =~ s/([^A-Za-z0-9\/.\-_])/sprintf "\\x%02x", ord $1/ge; push @lines, ( "ProxyPassReverse /", "ProxyHTMLEnable on", # "ProxyHTMLURLMap $target $cpath", # "ProxyHTMLURLMap / $cpath/", ); # warn "PROXY<$path,$target,$suffix>\n";#d# # warn map "$_\n",@lines; $req->add_config (\@lines, ~0, $path); rproxy "$target$suffix"; } my $rules; sub load_rules { open my $fh, "<:raw", Apache2::ServerUtil::server_root . "/rules" or die Apache2::ServerUtil::server_root . "/rules: $!"; local $/; $rules = <$fh>; $rules = eval "use common::sense; BEGIN { uriregex }\nsub {\n#line 0 'rules'\n$rules\n}" or die $@; warn "proxy rules successfully loaded.\n"; Apache2::Const::OK } sub map_to_storage { local $req = shift; local ($host, $uri, $ip) = ($req->hostname, $req->uri, $req->connection->client_ip); eval { # the uri is pre-parsed and "protected" by apache # the hostname is lowercased but otherwise completely unchecked, # so better be safe than sorry $host =~ /^[A-Za-z0-9\-.]+$/ or status 404; # must have at least one dot $host =~ /./ or status 404; local $_ = "$host$uri"; $rules->($req); }; if ($@) { if (SCALAR:: eq ref $@) { return ${$@}; } else { die; } } Apache2::Const::NOT_FOUND } load_rules; warn "proxy loaded and iniitalised.\n"; 1