PAGI
view release on metacpan or search on metacpan
lib/PAGI/Middleware/Static.pm view on Meta::CPAN
return;
}
# Use file response for efficient streaming (sendfile or worker pool)
# This also enables XSendfile middleware to intercept the response
if ($is_range) {
await $send->({
type => 'http.response.body',
file => $file_path,
offset => $start,
length => $body_size,
});
}
else {
await $send->({
type => 'http.response.body',
file => $file_path,
});
}
};
}
sub _resolve_path {
my ($self, $url_path) = @_;
# Path is already URL-decoded by the server, so no decoding here
my $decoded = $url_path;
# Remove query string
$decoded =~ s/\?.*//;
# Combine with root (use manual concat to preserve .. for security check)
my $root = $self->{root};
$root =~ s{/$}{}; # Remove trailing slash from root
return $root . $decoded;
}
sub _is_safe_path {
my ($self, $file_path) = @_;
my $root = $self->{root};
# Manually resolve the path to handle .. without requiring file to exist
my $abs_path = $self->_resolve_dots($file_path);
return 0 unless defined $abs_path;
# Normalize both paths
$abs_path =~ s{/+}{/}g;
$root =~ s{/+}{/}g;
$root =~ s{/$}{};
$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;
}
# If we can't go up, the path is invalid (would escape root)
} else {
push @resolved, $part;
}
}
# Reconstruct absolute path
return '/' . join('/', @resolved);
}
sub _find_index {
my ($self, $dir_path) = @_;
for my $index (@{$self->{index}}) {
my $index_path = File::Spec->catfile($dir_path, $index);
return $index_path if -f $index_path;
}
return;
}
sub _generate_etag {
my ($self, $file_path, $size, $mtime) = @_;
my $data = "$file_path:$size:$mtime";
return '"' . md5_hex($data) . '"';
}
sub _get_mime_type {
my ($self, $file_path) = @_;
my ($ext) = $file_path =~ /\.([^.]+)$/;
$ext = lc($ext // '');
return $MIME_TYPES{$ext} // 'application/octet-stream';
}
sub _get_header {
my ($self, $scope, $name) = @_;
$name = lc($name);
for my $h (@{$scope->{headers} // []}) {
return $h->[1] if lc($h->[0]) eq $name;
}
return;
}
sub _parse_range {
my ($self, $range_header, $size) = @_;
return (undef, undef, 0) unless $range_header;
( run in 0.570 second using v1.01-cache-2.11-cpan-71847e10f99 )