ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/apache2-frontend/proxy_impl.pm
Revision: 1.14
Committed: Wed Apr 16 12:47:02 2025 UTC (17 months, 1 week ago) by root
Branch: MAIN
Changes since 1.13: +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.2 # simplest reverse proxy, target is target url for this request
205 root 1.10 sub rproxy($;$) {
206     my ($target, $preservehost) = @_;
207 root 1.1
208     $req->proxyreq (Apache2::Const::PROXYREQ_REVERSE);
209 root 1.2 $req->filename ("proxy:$target");
210 root 1.1 $req->handler ("proxy-server");
211     $req->subprocess_env->set ("proxy-sendchunked", 1);
212     #$notes->set ("proxy-nocanon", 1);
213     #$env->set ("proxy-initial-not-pooled", 1);
214    
215     # disable compression, see http://www.apachetutor.org/admin/reverseproxies
216     # $req->headers_in->unset ("accept-encoding");
217     # alternatively recompress, SetOutputFilter INFLATE;DEFLATE
218    
219 root 1.10 $req->add_config (["ProxyPreserveHost on"], ~0)
220     if $preservehost;
221    
222 root 1.9 $req->headers_out->set ("x-forwarded-for", $req->connection->client_ip);
223     $req->headers_out->set ("x-forwarded-proto", "https"); # unable to find a way to access ap_http_scheme
224    
225 root 1.2 status Apache2::Const::OK;
226     }
227    
228 root 1.3 # host, port
229 root 1.4 # also sets pathinfo, see _get_pathinfo
230 root 1.2 sub rscgi($;$) {
231 root 1.3 my ($target) = @_;
232 root 1.2
233 root 1.4 _set_pathinfo;
234 root 1.2 rproxy $target;
235    
236     # notes: uds_path
237     }
238    
239     # reverse proxy
240     # path is local root uri
241     # target is target root url
242     # suffix is appended to target url
243     # @lines is extra config lines
244     sub rproxy_html($$$@) {
245     my ($path, $target, $suffix, @lines) = @_;
246    
247 root 1.1 (my $cpath = $path) =~ s/([^A-Za-z0-9\/.\-_])/sprintf "\\x%02x", ord $1/ge;
248    
249     push @lines, (
250     "ProxyPassReverse /",
251     "ProxyHTMLEnable on",
252 root 1.6 # "ProxyHTMLURLMap $target $cpath",
253     # "ProxyHTMLURLMap / $cpath/",
254 root 1.1 );
255    
256     # warn "PROXY<$path,$target,$suffix>\n";#d#
257     # warn map "$_\n",@lines;
258    
259     $req->add_config (\@lines, ~0, $path);
260    
261 root 1.2 rproxy "$target$suffix";
262 root 1.1 }
263    
264     my $rules;
265    
266     sub load_rules {
267     open my $fh, "<:raw", Apache2::ServerUtil::server_root . "/rules"
268     or die Apache2::ServerUtil::server_root . "/rules: $!";
269     local $/;
270    
271     $rules = <$fh>;
272    
273 root 1.2 $rules = eval "use common::sense; BEGIN { uriregex }\nsub {\n#line 0 'rules'\n$rules\n}"
274 root 1.1 or die $@;
275    
276     warn "proxy rules successfully loaded.\n";
277    
278     Apache2::Const::OK
279     }
280    
281     sub map_to_storage {
282     local $req = shift;
283     local ($host, $uri, $ip) = ($req->hostname, $req->uri, $req->connection->client_ip);
284    
285     eval {
286     # the uri is pre-parsed and "protected" by apache
287     # the hostname is lowercased but otherwise completely unchecked,
288     # so better be safe than sorry
289     $host =~ /^[A-Za-z0-9\-.]+$/
290     or status 404;
291    
292     # must have at least one dot
293     $host =~ /./
294     or status 404;
295    
296 root 1.14 if ($req->headers_in->get ("User-Agent") =~ /ClaudeBot|SEOkicks|facebookexternalhit|AhrefsBot|meta-externalagent|mazonbot|www.semrush.com|Bytespider|GPTBot|Barkrowler|BLEXBot|www.sbintuitions.co.jp/) {
297     $req->subprocess_env->set ("suppress-logging", "");
298     rtemp "https://www.anthropic.com/$_/" . rand;
299     }
300    
301 root 1.1 local $_ = "$host$uri";
302     $rules->($req);
303     };
304    
305     if ($@) {
306     if (SCALAR:: eq ref $@) {
307     return ${$@};
308     } else {
309     die;
310     }
311     }
312    
313     Apache2::Const::NOT_FOUND
314     }
315    
316     load_rules;
317    
318     warn "proxy loaded and iniitalised.\n";
319    
320     1
321