ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/apache2-frontend/proxy_impl.pm
Revision: 1.17
Committed: Sun Sep 13 23:04:46 2026 UTC (9 days, 23 hours ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.16: +5 -0 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 root 1.11 # . literal dot (like "\." in normal regexes)
9 root 1.2 # · 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 root 1.12 s/(?<!\\)\$\z/\\z/;
19 root 1.1
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 root 1.12 #use Apache2::RequestIO ();
33 root 1.1 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 root 1.13 our $req; # Apache2::RequestRec
47 root 1.1 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 root 1.8 s/([^a-zA-z0-9\$\-_.+!*'(),;?&=\/])/sprintf "%%%02x", ord $1/ge;
65 root 1.1
66     $_
67     }
68    
69 root 1.4 sub err($) {
70     warn "$_[0]";
71     status 500;
72     }
73    
74 root 1.1 # 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 root 1.9 # internal redirect, TODO
95 root 1.5 sub rint($) {
96     &rtemp
97     }
98    
99 root 1.1 # 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 root 1.11
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 root 1.1 # 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 root 1.2 sub rpath_nostat($) {
150 root 1.1 -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 root 1.4 # 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 root 1.3 # run a cgi script, first argument is script,
193 root 1.4 # also sets pathinfo, see _get_pathinfo
194 root 1.3 sub rcgi($) {
195     my ($path) = @_;
196 root 1.1
197 root 1.4 _set_pathinfo;
198 root 1.1
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 root 1.17 sub rstatus() {
205     $req->handler ("server-status");
206     status Apache2::Const::OK;
207     }
208    
209 root 1.2 # simplest reverse proxy, target is target url for this request
210 root 1.10 sub rproxy($;$) {
211     my ($target, $preservehost) = @_;
212 root 1.1
213     $req->proxyreq (Apache2::Const::PROXYREQ_REVERSE);
214 root 1.2 $req->filename ("proxy:$target");
215 root 1.1 $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 root 1.10 $req->add_config (["ProxyPreserveHost on"], ~0)
225     if $preservehost;
226    
227 root 1.9 $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 root 1.2 status Apache2::Const::OK;
231     }
232    
233 root 1.3 # host, port
234 root 1.4 # also sets pathinfo, see _get_pathinfo
235 root 1.2 sub rscgi($;$) {
236 root 1.3 my ($target) = @_;
237 root 1.2
238 root 1.4 _set_pathinfo;
239 root 1.2 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 root 1.1 (my $cpath = $path) =~ s/([^A-Za-z0-9\/.\-_])/sprintf "\\x%02x", ord $1/ge;
253    
254     push @lines, (
255     "ProxyPassReverse /",
256     "ProxyHTMLEnable on",
257 root 1.6 # "ProxyHTMLURLMap $target $cpath",
258     # "ProxyHTMLURLMap / $cpath/",
259 root 1.1 );
260    
261     # warn "PROXY<$path,$target,$suffix>\n";#d#
262     # warn map "$_\n",@lines;
263    
264     $req->add_config (\@lines, ~0, $path);
265    
266 root 1.2 rproxy "$target$suffix";
267 root 1.1 }
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 root 1.2 $rules = eval "use common::sense; BEGIN { uriregex }\nsub {\n#line 0 'rules'\n$rules\n}"
279 root 1.1 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 root 1.15 my $ua = $req->headers_in->get ("User-agent");
302     my $chrome = 130 + (time - 1727740800) / 2629746;
303    
304     if (
305 root 1.16 $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 root 1.15 ) {
321 root 1.14 $req->subprocess_env->set ("suppress-logging", "");
322     rtemp "https://www.anthropic.com/$_/" . rand;
323     }
324    
325 root 1.1 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