ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/apache2-frontend/proxy_impl.pm
Revision: 1.11
Committed: Tue Nov 12 13:25:56 2024 UTC (22 months, 2 weeks ago) by root
Branch: MAIN
Changes since 1.10: +7 -1 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 package proxy_impl;
2
3 use overload;
4
5 #use 5.020; # recommended for $& without performance penalty
6
7 # regex syntax
8 # . literal dot (like "\." in normal regexes)
9 # · match one character (like "." in normal regexes)
10 # ** match anything (like ".*" in normal regexes)
11 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 sub err($) {
68 warn "$_[0]";
69 status 500;
70 }
71
72 # 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 # internal redirect, TODO
93 sub rint($) {
94 &rtemp
95 }
96
97 # serve some path, do not call directly
98 sub _rpathname($) {
99 my $path = shift;
100
101 my $finfo = eval { APR::Finfo::stat $path, APR::Const::FINFO_NORM, $req->pool }
102 or status Apache2::Const::NOT_FOUND;
103
104 # let mod_dir, mod_autoindex and the default-handler handle this
105 $req->filename ($path);
106 $req->finfo ($finfo);
107
108 if ($path =~ m%/.well-known/openid-configuration$%) {
109 $req->headers_out->set ("content-type" => "application/json");
110 $req->headers_out->set ("access-control-allow-origin" => "*");
111 }
112
113 # warn "serve <$path,",$req->handler,">\n";#d#
114 status Apache2::Const::OK;
115 }
116
117 # serve a regular file from disk
118 sub rfile($) {
119 $req->handler ("default-handler");
120 &_rpathname
121 }
122
123 # serve a directory from disk
124 sub rdir($) {
125 my $path = shift;
126
127 if ($req->uri !~ m,/$,) {
128 # redirect dir to dir/
129 rperm $req->construct_url (pesc $req->uri . "/");
130
131 } elsif (-e "$path/index.html") {
132 # mod_dir emulation, display index.html if any
133 $req->content_type ("text/html");
134 rfile "$path/index.html";
135 } elsif (-e "$path/index.xhtml") {
136 $req->content_type ("text/html"); # we assume to follow the compatibility guidelines
137 rfile "$path/index.xhtml";
138
139 } else {
140 # let mod_autoindex handle it later
141 $req->handler ("httpd/unix-directory");
142 _rpathname $path;
143 }
144 }
145
146 # like rpath, but assumes caller already stat'ed
147 sub rpath_nostat($) {
148 -d _ ? &rdir : &rfile
149 }
150
151 # serve a generic path, can be dir or file
152 sub rpath($) {
153 stat $_[0];
154 &rpath_nostat
155 }
156
157 # sets SCRIPT_NAME and PATH_INFO from previous regex match
158 # pathinfo must be specified via a regex match
159 # either
160 # - $1 has matched SCRIPT_NAME
161 # - OR $1 as above, and $2 matches PATH_INFO (for extra checking)
162 # - OR (?<name>...) and (?<path>...) exist and match SCRIPT_NAME and PATH_INFO.
163 sub _get_pathinfo() {
164 my ($name, $path);
165
166 if (exists $+{name} && exists $+{path}) {
167 $name = $+{name};
168 $path = $+{path};
169 } elsif (defined $1) {
170 $name = $1;
171 $path = defined $2 ? $2 : substr "$`$&$'", $+[1];
172 } else {
173 err "$uri: cannot set pathinfo";
174 }
175
176 if ($uri ne "$name$path") {
177 err "$uri: imperfect split into <$name> and <$path>";
178 }
179
180 ($name, $path)
181 }
182
183 sub _set_pathinfo() {
184 my ($name, $path) = _get_pathinfo;
185
186 $req->uri ("$name$path");
187 $req->path_info ($path);
188 }
189
190 # run a cgi script, first argument is script,
191 # also sets pathinfo, see _get_pathinfo
192 sub rcgi($) {
193 my ($path) = @_;
194
195 _set_pathinfo;
196
197 $req->handler ("cgi-script");
198 $req->notes->set ("alias-forced-type" => "cgi-script"); # leave a note for mod_cgi, so it ignores missing ExecCGI
199 _rpathname $path;
200 }
201
202 # simplest reverse proxy, target is target url for this request
203 sub rproxy($;$) {
204 my ($target, $preservehost) = @_;
205
206 $req->proxyreq (Apache2::Const::PROXYREQ_REVERSE);
207 $req->filename ("proxy:$target");
208 $req->handler ("proxy-server");
209 $req->subprocess_env->set ("proxy-sendchunked", 1);
210 #$notes->set ("proxy-nocanon", 1);
211 #$env->set ("proxy-initial-not-pooled", 1);
212
213 # disable compression, see http://www.apachetutor.org/admin/reverseproxies
214 # $req->headers_in->unset ("accept-encoding");
215 # alternatively recompress, SetOutputFilter INFLATE;DEFLATE
216
217 $req->add_config (["ProxyPreserveHost on"], ~0)
218 if $preservehost;
219
220 $req->headers_out->set ("x-forwarded-for", $req->connection->client_ip);
221 $req->headers_out->set ("x-forwarded-proto", "https"); # unable to find a way to access ap_http_scheme
222
223 status Apache2::Const::OK;
224 }
225
226 # host, port
227 # also sets pathinfo, see _get_pathinfo
228 sub rscgi($;$) {
229 my ($target) = @_;
230
231 _set_pathinfo;
232 rproxy $target;
233
234 # notes: uds_path
235 }
236
237 # reverse proxy
238 # path is local root uri
239 # target is target root url
240 # suffix is appended to target url
241 # @lines is extra config lines
242 sub rproxy_html($$$@) {
243 my ($path, $target, $suffix, @lines) = @_;
244
245 (my $cpath = $path) =~ s/([^A-Za-z0-9\/.\-_])/sprintf "\\x%02x", ord $1/ge;
246
247 push @lines, (
248 "ProxyPassReverse /",
249 "ProxyHTMLEnable on",
250 # "ProxyHTMLURLMap $target $cpath",
251 # "ProxyHTMLURLMap / $cpath/",
252 );
253
254 # warn "PROXY<$path,$target,$suffix>\n";#d#
255 # warn map "$_\n",@lines;
256
257 $req->add_config (\@lines, ~0, $path);
258
259 rproxy "$target$suffix";
260 }
261
262 my $rules;
263
264 sub load_rules {
265 open my $fh, "<:raw", Apache2::ServerUtil::server_root . "/rules"
266 or die Apache2::ServerUtil::server_root . "/rules: $!";
267 local $/;
268
269 $rules = <$fh>;
270
271 $rules = eval "use common::sense; BEGIN { uriregex }\nsub {\n#line 0 'rules'\n$rules\n}"
272 or die $@;
273
274 warn "proxy rules successfully loaded.\n";
275
276 Apache2::Const::OK
277 }
278
279 sub map_to_storage {
280 local $req = shift;
281 local ($host, $uri, $ip) = ($req->hostname, $req->uri, $req->connection->client_ip);
282
283 eval {
284 # the uri is pre-parsed and "protected" by apache
285 # the hostname is lowercased but otherwise completely unchecked,
286 # so better be safe than sorry
287 $host =~ /^[A-Za-z0-9\-.]+$/
288 or status 404;
289
290 # must have at least one dot
291 $host =~ /./
292 or status 404;
293
294 local $_ = "$host$uri";
295 $rules->($req);
296 };
297
298 if ($@) {
299 if (SCALAR:: eq ref $@) {
300 return ${$@};
301 } else {
302 die;
303 }
304 }
305
306 Apache2::Const::NOT_FOUND
307 }
308
309 load_rules;
310
311 warn "proxy loaded and iniitalised.\n";
312
313 1
314