PAGI
view release on metacpan or search on metacpan
lib/PAGI/App/File.pm view on Meta::CPAN
if ($path =~ /\0/) {
await $self->_send_error($send, 400, 'Bad Request');
return;
}
# Security: Normalize backslashes to forward slashes
$path =~ s{\\}{/}g;
# Security: Split path and validate each component
# Use -1 limit to preserve trailing empty strings
my @components = split m{/}, $path, -1;
for my $component (@components) {
# Block components with 2+ dots (.. , ..., ....)
if ($component =~ /^\.{2,}$/) {
await $self->_send_error($send, 403, 'Forbidden');
return;
}
# Block hidden files (dotfiles) - components starting with .
if ($component =~ /^\./ && $component ne '') {
await $self->_send_error($send, 403, 'Forbidden');
return;
lib/PAGI/Middleware/Maintenance.pm view on Meta::CPAN
return 1 if $client_ip eq $bypass_ip;
}
}
return 0;
}
sub _ip_in_cidr {
my ($self, $ip, $cidr) = @_;
my ($network, $bits) = split m{/}, $cidr;
# Simple IPv4 check
return 0 unless $ip =~ /^[\d.]+$/ && $network =~ /^[\d.]+$/;
my $ip_num = $self->_ip_to_num($ip);
my $net_num = $self->_ip_to_num($network);
return 0 unless defined $ip_num && defined $net_num;
my $mask = ~((1 << (32 - $bits)) - 1) & 0xFFFFFFFF;
lib/PAGI/Middleware/ReverseProxy.pm view on Meta::CPAN
# Exact match
return 1 if $ip eq $trusted;
}
}
return 0;
}
sub _ip_in_cidr {
my ($self, $ip, $cidr) = @_;
my ($network, $bits) = split m{/}, $cidr;
# Simple IPv4 check
return 0 unless $ip =~ /^[\d.]+$/ && $network =~ /^[\d.]+$/;
my $ip_num = $self->_ip_to_num($ip);
my $net_num = $self->_ip_to_num($network);
return 0 unless defined $ip_num && defined $net_num;
my $mask = ~((1 << (32 - $bits)) - 1) & 0xFFFFFFFF;
lib/PAGI/Middleware/Static.pm view on Meta::CPAN
$abs_path =~ s{/$}{};
# Path must start with root
return $abs_path =~ m{^\Q$root\E(?:/|$)};
}
sub _resolve_dots {
my ($self, $path) = @_;
# Split path into components
my @parts = split m{/}, $path;
my @resolved;
for my $part (@parts) {
if ($part eq '' || $part eq '.') {
# Skip empty and current dir
next;
} elsif ($part eq '..') {
# Go up one directory
if (@resolved) {
pop @resolved;
t/request-body-stream.t view on Meta::CPAN
my $stream = PAGI::Request::BodyStream->new(receive => $receive);
my $chunk1 = $stream->next_chunk->get;
is $chunk1, 'Hello', 'first chunk';
my $chunk2 = $stream->next_chunk->get;
is $chunk2, undef, 'undef on disconnect';
ok $stream->is_done, 'done after disconnect';
};
subtest 'UTF-8 boundary handling - split multi-byte sequence' => sub {
# Split café (caf\xc3\xa9) across chunks: "caf\xc3" and "\xa9"
my $receive = mock_receive(
{ type => 'http.request', body => "caf\xc3", more => 1 },
{ type => 'http.request', body => "\xa9", more => 0 },
);
my $stream = PAGI::Request::BodyStream->new(
receive => $receive,
decode => 'UTF-8',
);
( run in 0.583 second using v1.01-cache-2.11-cpan-71847e10f99 )