FU: Some FastCGI fixes; FU::Util: utf8_decode & URI escaping

This commit is contained in:
Yorhel 2025-02-18 10:27:58 +01:00
parent d9fba4e8d8
commit 90cfd66069
6 changed files with 146 additions and 33 deletions

39
FU.pm
View file

@ -163,13 +163,6 @@ sub _monitor {
}
sub _decode_utf8 {
return if !defined $_[0];
fu->error(400, 'Invalid UTF-8 in request') if !utf8::decode($_[0]);
# Disallow any control codes, except for x09 (tab), x0a (newline) and x0d (carriage return)
fu->error(400, 'Invalid control character in request') if $_[0] =~ /[\x00-\x08\x0b\x0c\x0e-\x1f]/;
}
our $hdrname_re = qr/[!#\$\%&'\*\+-\.^_`\|~0-9a-zA-Z]{1,127}/;
our $method_re = qr/(?:GET|POST|DELETE|OPTIONS|PUT|PATCH|QUERY)/;
@ -181,8 +174,7 @@ sub _read_req_http($sock, $req) {
fu->error(400, 'Client disconnect before request was read') if !defined $line;
fu->error(400, 'Invalid request') if $line !~ /^($method_re)\s+(\S+)\s+HTTP\/1\.[01]\r\n$/;
$req->{method} = $1;
($req->{path}, $req->{qs}) = split /\?/, $2 =~ s{^https?://[^/]+/}{/}r, 2;
$req->{path} =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
$req->{path} = $2 =~ s{^https?://[^/]+/}{/}r;
while (1) {
# Turns out header line folding has been officially deprecated, so I'm
@ -225,7 +217,7 @@ sub _read_req($c) {
: $r == -2 ? "I/O error while reading from FastCGI socket\n"
: $r == -3 ? "FastCGI protocol error\n"
: $r == -4 ? "Too long FastCGI parameter\n"
: $r == -5 ? "Too long request body\n" : undef;
: $r == -5 ? "Too long request body\n" : undef if $r != -7;
delete $c->{fcgi_obj};
fu->error(-1);
}
@ -236,7 +228,12 @@ sub _read_req($c) {
# The HTTP reader above and the FastCGI XS reader operate on bytes.
# Decode these into Unicode strings and check for special characters.
_decode_utf8 $_ for ($REQ->{path}, $REQ->{qs}, values $REQ->{hdr}->%*);
eval { FU::Util::utf8_decode($_); 1} || fu->err(400, $@)
for ($REQ->{path}, $REQ->{qs}, values $REQ->{hdr}->%*);
($REQ->{path}, my $qs) = split /\?/, $REQ->{path}//'', 2;
$REQ->{qs} //= $qs;
$REQ->{path} = FU::Util::uri_unescape($REQ->{path});
}
@ -418,7 +415,7 @@ sub _spawn {
if (!keys %c) {
%c = (
http => $ENV{FU_HTTP} // '127.0.0.1:3000',
http => $ENV{FU_HTTP},
fcgi => $ENV{FU_FCGI},
proc => $ENV{FU_PROC} // 1,
monitor => $ENV{FU_MONITOR} // 0,
@ -447,8 +444,17 @@ sub _spawn {
my $need_supervisor = !$c{supervisor_sock} && !$c{client_sock} && ($c{proc} > 1 || $c{monitor} || $c{max_reqs});
return if !@_ && !$need_supervisor;
if (!$c{http} && !$c{fcgi} && !$c{listen_sock}) {
# When spawned under FastCGI, stdin is our listen socket
local $_ = getpeername \*STDIN;
if ($!{ENOTCONN}) {
$c{listen_sock} = IO::Socket->new_from_fd(0, 'r');
$c{listen_proto} = 'fcgi';
}
};
$c{http} //= '127.0.0.1:3000';
if (!$c{listen_sock}) {
# TODO: check if stdin is a fastcgi sock
$c{listen_proto} //= $c{fcgi} ? 'fcgi' : 'http';
my $addr = $c{$c{listen_proto}};
$c{listen_sock} = IO::Socket->new(
@ -520,6 +526,7 @@ sub method { $FU::REQ->{method} }
sub header($, $h) { $FU::REQ->{hdr}{ lc $h } }
sub headers { $FU::REQ->{hdr} }
sub ip { $FU::REQ->{ip} }
sub query { $FU::REQ->{qs} } # TODO: parse & validate
@ -773,7 +780,7 @@ this option is set, it takes precedence over the HTTP option.
Nginx and Apache will, in their default configuration, use a separate
connection per request. If you have a more esoteric setup, you should probably
be aware of the following: this implementation does not support multiplexing or
pipelining. It does support keepalive, but this come with a few caveats:
pipelining. It does support keepalive, but this comes with a few caveats:
=over
@ -786,7 +793,7 @@ I<after> each request rather than before, so clients may get a response from
stale code.
=item * When worker processes shut down, either through C<--max-reqs> or in
response to a signal, there is the possibility that an incoming request on an
response to a signal, there is a possibility that an incoming request on an
existing connection gets interrupted.
=back
@ -822,6 +829,8 @@ Set the initial value for C<FU::debug>.
=item LISTEN_FD=num
=item LISTEN_PROTO=http/fcgi
Listen for incoming connections on the given file descriptor instead of
creating a new listen socket. This is mainly useful if you are using an
external process manager.