ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/apache2-frontend/proxy_impl.pm
Revision: 1.17
Committed: Sun Sep 13 23:04:46 2026 UTC (11 days ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.16: +5 -0 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 s/(?<!\\)\$\z/\\z/;
19
20 $_
21 };
22 }
23
24 BEGIN { uriregex }
25
26 use Apache2::Const -compile => qw(
27 OK DECLINED DONE NOT_FOUND SERVER_ERROR AUTH_REQUIRED FORBIDDEN
28 PROXYREQ_REVERSE
29 );
30 use Apache2::RequestUtil ();
31 use Apache2::RequestRec ();
32 #use Apache2::RequestIO ();
33 use Apache2::Connection ();
34 use Apache2::Util ();
35 use Apache2::URI ();
36
37 use APR::Const -compile => qw(
38 FILETYPE_DIR FINFO_NORM
39 );
40 use APR::Error ();
41 use APR::Finfo ();
42
43 use common::sense;
44
45 # per-request values, cached here for performance, use them
46 our $req; # Apache2::RequestRec
47 our $host; # $req->hostname
48 our $uri; # $req->uri, already resolved .. and protects against %xx, hopefully
49 our $ip; # $req->connection->client_ip, alternative ->useragent_ip
50
51 # finish immediately with given status
52 sub status($) {
53 die \(my $status = shift);
54 }
55
56 #sub escape_path($) {
57 # Apache2::Util::escape_path shift, $req->pool
58 #}
59
60 # escapes everything not allowed in a url hpath
61 sub pesc($) {
62 local $_ = shift;
63
64 s/([^a-zA-z0-9\$\-_.+!*'(),;?&=\/])/sprintf "%%%02x", ord $1/ge;
65
66 $_
67 }
68
69 sub err($) {
70 warn "$_[0]";
71 status 500;
72 }
73
74 # external redirect with status code
75 sub redirect($$) {
76 my ($status, $location) = @_;
77
78 # $location =~ s,^(http://[^/:]+)/,$1:34567/,;#d#
79
80 $req->headers_out->set (Location => $location);
81 status $status;
82 }
83
84 # permanent external redirect
85 sub rperm($) {
86 redirect 301, shift
87 }
88
89 # temporary redirect, could be internal, but never is
90 sub rtemp($) {
91 redirect 302, shift
92 }
93
94 # internal redirect, TODO
95 sub rint($) {
96 &rtemp
97 }
98
99 # serve some path, do not call directly
100 sub _rpathname($) {
101 my $path = shift;
102
103 my $finfo = eval { APR::Finfo::stat $path, APR::Const::FINFO_NORM, $req->pool }
104 or status Apache2::Const::NOT_FOUND;
105
106 # let mod_dir, mod_autoindex and the default-handler handle this
107 $req->filename ($path);
108 $req->finfo ($finfo);
109
110 if ($path =~ m%/.well-known/openid-configuration$%) {
111 $req->headers_out->set ("content-type" => "application/json");
112 $req->headers_out->set ("access-control-allow-origin" => "*");
113 }
114
115 # warn "serve <$path,",$req->handler,">\n";#d#
116 status Apache2::Const::OK;
117 }
118
119 # serve a regular file from disk
120 sub rfile($) {
121 $req->handler ("default-handler");
122 &_rpathname
123 }
124
125 # serve a directory from disk
126 sub rdir($) {
127 my $path = shift;
128
129 if ($req->uri !~ m,/$,) {
130 # redirect dir to dir/
131 rperm $req->construct_url (pesc $req->uri . "/");
132
133 } elsif (-e "$path/index.html") {
134 # mod_dir emulation, display index.html if any
135 $req->content_type ("text/html");
136 rfile "$path/index.html";
137 } elsif (-e "$path/index.xhtml") {
138 $req->content_type ("text/html"); # we assume to follow the compatibility guidelines
139 rfile "$path/index.xhtml";
140
141 } else {
142 # let mod_autoindex handle it later
143 $req->handler ("httpd/unix-directory");
144 _rpathname $path;
145 }
146 }
147
148 # like rpath, but assumes caller already stat'ed
149 sub rpath_nostat($) {
150 -d _ ? &rdir : &rfile
151 }
152
153 # serve a generic path, can be dir or file
154 sub rpath($) {
155 stat $_[0];
156 &rpath_nostat
157 }
158
159 # sets SCRIPT_NAME and PATH_INFO from previous regex match
160 # pathinfo must be specified via a regex match
161 # either
162 # - $1 has matched SCRIPT_NAME
163 # - OR $1 as above, and $2 matches PATH_INFO (for extra checking)
164 # - OR (?<name>...) and (?<path>...) exist and match SCRIPT_NAME and PATH_INFO.
165 sub _get_pathinfo() {
166 my ($name, $path);
167
168 if (exists $+{name} && exists $+{path}) {
169 $name = $+{name};
170 $path = $+{path};
171 } elsif (defined $1) {
172 $name = $1;
173 $path = defined $2 ? $2 : substr "$`$&$'", $+[1];
174 } else {
175 err "$uri: cannot set pathinfo";
176 }
177
178 if ($uri ne "$name$path") {
179 err "$uri: imperfect split into <$name> and <$path>";
180 }
181
182 ($name, $path)
183 }
184
185 sub _set_pathinfo() {
186 my ($name, $path) = _get_pathinfo;
187
188 $req->uri ("$name$path");
189 $req->path_info ($path);
190 }
191
192 # run a cgi script, first argument is script,
193 # also sets pathinfo, see _get_pathinfo
194 sub rcgi($) {
195 my ($path) = @_;
196
197 _set_pathinfo;
198
199 $req->handler ("cgi-script");
200 $req->notes->set ("alias-forced-type" => "cgi-script"); # leave a note for mod_cgi, so it ignores missing ExecCGI
201 _rpathname $path;
202 }
203
204 sub rstatus() {
205 $req->handler ("server-status");
206 status Apache2::Const::OK;
207 }
208
209 # simplest reverse proxy, target is target url for this request
210 sub rproxy($;$) {
211 my ($target, $preservehost) = @_;
212
213 $req->proxyreq (Apache2::Const::PROXYREQ_REVERSE);
214 $req->filename ("proxy:$target");
215 $req->handler ("proxy-server");
216 $req->subprocess_env->set ("proxy-sendchunked", 1);
217 #$notes->set ("proxy-nocanon", 1);
218 #$env->set ("proxy-initial-not-pooled", 1);
219
220 # disable compression, see http://www.apachetutor.org/admin/reverseproxies
221 # $req->headers_in->unset ("accept-encoding");
222 # alternatively recompress, SetOutputFilter INFLATE;DEFLATE
223
224 $req->add_config (["ProxyPreserveHost on"], ~0)
225 if $preservehost;
226
227 $req->headers_out->set ("x-forwarded-for", $req->connection->client_ip);
228 $req->headers_out->set ("x-forwarded-proto", "https"); # unable to find a way to access ap_http_scheme
229
230 status Apache2::Const::OK;
231 }
232
233 # host, port
234 # also sets pathinfo, see _get_pathinfo
235 sub rscgi($;$) {
236 my ($target) = @_;
237
238 _set_pathinfo;
239 rproxy $target;
240
241 # notes: uds_path
242 }
243
244 # reverse proxy
245 # path is local root uri
246 # target is target root url
247 # suffix is appended to target url
248 # @lines is extra config lines
249 sub rproxy_html($$$@) {
250 my ($path, $target, $suffix, @lines) = @_;
251
252 (my $cpath = $path) =~ s/([^A-Za-z0-9\/.\-_])/sprintf "\\x%02x", ord $1/ge;
253
254 push @lines, (
255 "ProxyPassReverse /",
256 "ProxyHTMLEnable on",
257 # "ProxyHTMLURLMap $target $cpath",
258 # "ProxyHTMLURLMap / $cpath/",
259 );
260
261 # warn "PROXY<$path,$target,$suffix>\n";#d#
262 # warn map "$_\n",@lines;
263
264 $req->add_config (\@lines, ~0, $path);
265
266 rproxy "$target$suffix";
267 }
268
269 my $rules;
270
271 sub load_rules {
272 open my $fh, "<:raw", Apache2::ServerUtil::server_root . "/rules"
273 or die Apache2::ServerUtil::server_root . "/rules: $!";
274 local $/;
275
276 $rules = <$fh>;
277
278 $rules = eval "use common::sense; BEGIN { uriregex }\nsub {\n#line 0 'rules'\n$rules\n}"
279 or die $@;
280
281 warn "proxy rules successfully loaded.\n";
282
283 Apache2::Const::OK
284 }
285
286 sub map_to_storage {
287 local $req = shift;
288 local ($host, $uri, $ip) = ($req->hostname, $req->uri, $req->connection->client_ip);
289
290 eval {
291 # the uri is pre-parsed and "protected" by apache
292 # the hostname is lowercased but otherwise completely unchecked,
293 # so better be safe than sorry
294 $host =~ /^[A-Za-z0-9\-.]+$/
295 or status 404;
296
297 # must have at least one dot
298 $host =~ /./
299 or status 404;
300
301 my $ua = $req->headers_in->get ("User-agent");
302 my $chrome = 130 + (time - 1727740800) / 2629746;
303
304 if (
305 $ua =~ /
306 SEOkicks
307 | facebookexternalhit
308 | AhrefsBot
309 | meta-externalagent
310 | www.semrush.com
311 | Bytespider
312 | Barkrowler
313 | BLEXBot
314 | SofyaBot
315 | Reflectionbot
316 | QlyzeBot
317 | PetalBot
318 /sx
319 or ($ua =~ m% Chrome/([0-9]+)(?:.[0-9\x2e]+)? Safari/% and $1 <= $chrome - 16) # was: 8
320 ) {
321 $req->subprocess_env->set ("suppress-logging", "");
322 rtemp "https://www.anthropic.com/$_/" . rand;
323 }
324
325 local $_ = "$host$uri";
326 $rules->($req);
327 };
328
329 if ($@) {
330 if (SCALAR:: eq ref $@) {
331 return ${$@};
332 } else {
333 die;
334 }
335 }
336
337 Apache2::Const::NOT_FOUND
338 }
339
340 load_rules;
341
342 warn "proxy loaded and iniitalised.\n";
343
344 1
345