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 1.457 second using v1.01-cache-2.11-cpan-71847e10f99 )