ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/apache2-frontend/proxy_impl.pm
Revision: 1.4
Committed: Thu Jun 18 12:45:33 2015 UTC (11 years, 3 months ago) by root
Branch: MAIN
Changes since 1.3: +44 -6 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 package proxy_impl;
2    
3     use overload;
4    
5 root 1.4 #use 5.020; # recommended for $& without performance penalty
6    
7 root 1.2 # regex syntax
8     # . literal dot (like "\." in normaql regexes)
9     # · match one character (like "." in normal regexes)
10     # ** match anything (like ".*" in normal regexes)
11 root 1.1 sub uriregex {
12     overload::constant qr => sub {
13     local $_ = shift;
14    
15     s/\./\\./g;
16     s/·/./g;
17     s/\*\*/.*/g;
18    
19     $_
20     };
21     }
22    
23     BEGIN { uriregex }
24    
25     use Apache2::Const -compile => qw(
26     OK DECLINED DONE NOT_FOUND SERVER_ERROR AUTH_REQUIRED FORBIDDEN
27     PROXYREQ_REVERSE
28     );
29     use Apache2::RequestUtil ();
30     use Apache2::RequestRec ();
31     use Apache2::Connection ();
32     use Apache2::Util ();
33     use Apache2::URI ();
34    
35     use APR::Const -compile => qw(
36     FILETYPE_DIR FINFO_NORM
37     );
38     use APR::Error ();
39     use APR::Finfo ();
40    
41     use common::sense;
42    
43     # per-request values, cached here for performance, use them
44     our $req; # Apache2::RquestRec
45     our $host; # $req->hostname
46     our $uri; # $req->uri, already resolved .. and protects against %xx, hopefully
47     our $ip; # $req->connection->client_ip, alternative ->useragent_ip
48    
49     # finish immediately with given status
50     sub status($) {
51     die \(my $status = shift);
52     }
53    
54     #sub escape_path($) {
55     # Apache2::Util::escape_path shift, $req->pool
56     #}
57    
58     # escapes everything not allowed in a url hpath
59     sub pesc($) {
60     local $_ = shift;
61    
62     s/([^A-Za-z0-9\$\-_.+!*'(),;?&=]\/)/sprintf "%02x", ord $1/ge;
63    
64     $_
65     }
66    
67 root 1.4 sub err($) {
68     warn "$_[0]";
69     status 500;
70     }
71    
72 root 1.1 # external redirect with status code
73     sub redirect($$) {
74     my ($status, $location) = @_;
75    
76     # $location =~ s,^(http://[^/:]+)/,$1:34567/,;#d#
77    
78     $req->headers_out->set (Location => $location);
79     status $status;
80     }
81    
82     # permanent external redirect
83     sub rperm($) {
84     redirect 301, shift
85     }
86    
87     # temporary redirect, could be internal, but never is
88     sub rtemp($) {
89     redirect 302, shift
90     }
91    
92     # serve some path, do not call directly
93     sub _rpathname($) {
94     my $path = shift;
95    
96     my $finfo = eval { APR::Finfo::stat $path, APR::Const::FINFO_NORM, $req->pool }
97     or status Apache2::Const::NOT_FOUND;
98    
99     # let mod_dir, mod_autoindex and the default-handler handle this
100     $req->filename ($path);
101     $req->finfo ($finfo);
102     # warn "serve <$path,",$req->handler,">\n";#d#
103     status Apache2::Const::OK;
104     }
105    
106     # serve a regular file from disk
107     sub rfile($) {
108     $req->handler ("default-handler");
109     &_rpathname
110     }
111    
112     # serve a directory from disk
113     sub rdir($) {
114     my $path = shift;
115    
116     if ($req->uri !~ m,/$,) {
117     # redirect dir to dir/
118     rperm $req->construct_url (pesc $req->uri . "/");
119    
120     } elsif (-e "$path/index.html") {
121     # mod_dir emulation, display index.html if any
122     $req->content_type ("text/html");
123     rfile "$path/index.html";
124     } elsif (-e "$path/index.xhtml") {
125     $req->content_type ("text/html"); # we assume to follow the compatibility guidelines
126     rfile "$path/index.xhtml";
127    
128     } else {
129     # let mod_autoindex handle it later
130     $req->handler ("httpd/unix-directory");
131     _rpathname $path;
132     }
133     }
134    
135     # like rpath, but assumes caller already stat'ed
136 root 1.2 sub rpath_nostat($) {
137 root 1.1 -d _ ? &rdir : &rfile
138     }
139    
140     # serve a generic path, can be dir or file
141     sub rpath($) {
142     stat $_[0];
143     &rpath_nostat
144     }
145    
146 root 1.4 # sets SCRIPT_NAME and PATH_INFO from previous regex match
147     # pathinfo must be specified via a regex match
148     # either
149     # - $1 has matched SCRIPT_NAME
150     # - OR $1 as above, and $2 matches PATH_INFO (for extra checking)
151     # - OR (?<name>...) and (?<path>...) exist and match SCRIPT_NAME and PATH_INFO.
152     sub _get_pathinfo() {
153     my ($name, $path);
154    
155     if (exists $+{name} && exists $+{path}) {
156     $name = $+{name};
157     $path = $+{path};
158     } elsif (defined $1) {
159     $name = $1;
160     $path = defined $2 ? $2 : substr "$`$&$'", $+[1];
161     } else {
162     err "$uri: cannot set pathinfo";
163     }
164    
165     if ($uri ne "$name$path") {
166     err "$uri: imperfect split into <$name> and <$path>";
167     }
168    
169     ($name, $path)
170     }
171    
172     sub _set_pathinfo() {
173     my ($name, $path) = _get_pathinfo;
174    
175     $req->uri ("$name$path");
176     $req->path_info ($path);
177     }
178    
179 root 1.3 # run a cgi script, first argument is script,
180 root 1.4 # also sets pathinfo, see _get_pathinfo
181 root 1.3 sub rcgi($) {
182     my ($path) = @_;
183 root 1.1
184 root 1.4 _set_pathinfo;
185 root 1.1
186     $req->handler ("cgi-script");
187     $req->notes->set ("alias-forced-type" => "cgi-script"); # leave a note for mod_cgi, so it ignores missing ExecCGI
188     _rpathname $path;
189     }
190    
191 root 1.2 # simplest reverse proxy, target is target url for this request
192     sub rproxy($) {
193     my ($target) = @_;
194 root 1.1
195     $req->proxyreq (Apache2::Const::PROXYREQ_REVERSE);
196 root 1.2 $req->filename ("proxy:$target");
197 root 1.1 $req->handler ("proxy-server");
198     $req->subprocess_env->set ("proxy-sendchunked", 1);
199     #$notes->set ("proxy-nocanon", 1);
200     #$env->set ("proxy-initial-not-pooled", 1);
201    
202     # disable compression, see http://www.apachetutor.org/admin/reverseproxies
203     # $req->headers_in->unset ("accept-encoding");
204     # alternatively recompress, SetOutputFilter INFLATE;DEFLATE
205    
206 root 1.2 status Apache2::Const::OK;
207     }
208    
209 root 1.3 # host, port
210 root 1.4 # also sets pathinfo, see _get_pathinfo
211 root 1.2 sub rscgi($;$) {
212 root 1.3 my ($target) = @_;
213 root 1.2
214 root 1.4 _set_pathinfo;
215 root 1.2 rproxy $target;
216    
217     # notes: uds_path
218     }
219    
220     # reverse proxy
221     # path is local root uri
222     # target is target root url
223     # suffix is appended to target url
224     # @lines is extra config lines
225     sub rproxy_html($$$@) {
226     my ($path, $target, $suffix, @lines) = @_;
227    
228 root 1.1 (my $cpath = $path) =~ s/([^A-Za-z0-9\/.\-_])/sprintf "\\x%02x", ord $1/ge;
229    
230     push @lines, (
231     "ProxyPassReverse /",
232     "ProxyHTMLEnable on",
233     "ProxyHTMLURLMap $target $cpath",
234     "ProxyHTMLURLMap / $cpath/",
235     );
236    
237     # warn "PROXY<$path,$target,$suffix>\n";#d#
238     # warn map "$_\n",@lines;
239    
240     $req->add_config (\@lines, ~0, $path);
241    
242 root 1.2 rproxy "$target$suffix";
243 root 1.1 }
244    
245     my $rules;
246    
247     sub load_rules {
248     open my $fh, "<:raw", Apache2::ServerUtil::server_root . "/rules"
249     or die Apache2::ServerUtil::server_root . "/rules: $!";
250     local $/;
251    
252     $rules = <$fh>;
253    
254 root 1.2 $rules = eval "use common::sense; BEGIN { uriregex }\nsub {\n#line 0 'rules'\n$rules\n}"
255 root 1.1 or die $@;
256    
257     warn "proxy rules successfully loaded.\n";
258    
259     Apache2::Const::OK
260     }
261    
262     sub map_to_storage {
263     local $req = shift;
264     local ($host, $uri, $ip) = ($req->hostname, $req->uri, $req->connection->client_ip);
265    
266     eval {
267     # the uri is pre-parsed and "protected" by apache
268     # the hostname is lowercased but otherwise completely unchecked,
269     # so better be safe than sorry
270     $host =~ /^[A-Za-z0-9\-.]+$/
271     or status 404;
272    
273     # must have at least one dot
274     $host =~ /./
275     or status 404;
276    
277     local $_ = "$host$uri";
278     $rules->($req);
279     };
280    
281     if ($@) {
282     if (SCALAR:: eq ref $@) {
283     return ${$@};
284     } else {
285     die;
286     }
287     }
288    
289     Apache2::Const::NOT_FOUND
290     }
291    
292     load_rules;
293    
294     warn "proxy loaded and iniitalised.\n";
295    
296     1
297