<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package Plack::App::File;
use strict;
use warnings;
use parent qw/Plack::Component/;
use File::Spec::Unix;
use Cwd ();
use Plack::Util;
use Plack::MIME;
use HTTP::Date;

use Plack::Util::Accessor qw( root file content_type encoding );

sub should_handle {
    my($self, $file) = @_;
    return -f $file;
}

sub call {
    my $self = shift;
    my $env  = shift;

    my($file, $path_info) = $self-&gt;file || $self-&gt;locate_file($env);
    return $file if ref $file eq 'ARRAY';

    if ($path_info) {
        $env-&gt;{'plack.file.SCRIPT_NAME'} = $env-&gt;{SCRIPT_NAME} . $env-&gt;{PATH_INFO};
        $env-&gt;{'plack.file.SCRIPT_NAME'} =~ s/\Q$path_info\E$//;
        $env-&gt;{'plack.file.PATH_INFO'}   = $path_info;
    } else {
        $env-&gt;{'plack.file.SCRIPT_NAME'} = $env-&gt;{SCRIPT_NAME} . $env-&gt;{PATH_INFO};
        $env-&gt;{'plack.file.PATH_INFO'}   = '';
    }

    return $self-&gt;serve_path($env, $file);
}

sub locate_file {
    my($self, $env) = @_;

    my $path = $env-&gt;{PATH_INFO} || '';

    if ($path =~ /\0/) {
        return $self-&gt;return_400;
    }

    my $docroot = $self-&gt;root || ".";
    my @path = split '/', $path;
    if (@path) {
        shift @path if $path[0] eq '';
    } else {
        @path = ('.');
    }

    if (grep $_ eq '..', @path) {
        return $self-&gt;return_403;
    }

    my($file, @path_info);
    while (@path) {
        my $try = File::Spec::Unix-&gt;catfile($docroot, @path);
        if ($self-&gt;should_handle($try)) {
            $file = $try;
            last;
        } elsif (!$self-&gt;allow_path_info) {
            last;
        }
        unshift @path_info, pop @path;
    }

    if (!$file) {
        return $self-&gt;return_404;
    }

    if (!-r $file) {
        return $self-&gt;return_403;
    }

    return $file, join("/", "", @path_info);
}

sub allow_path_info { 0 }

sub serve_path {
    my($self, $env, $file) = @_;

    my $content_type = $self-&gt;content_type || Plack::MIME-&gt;mime_type($file)
                       || 'text/plain';

    if ($content_type =~ m!^text/!) {
        $content_type .= "; charset=" . ($self-&gt;encoding || "utf-8");
    }

    open my $fh, "&lt;:raw", $file
        or return $self-&gt;return_403;

    my @stat = stat $file;

    Plack::Util::set_io_path($fh, Cwd::realpath($file));

    return [
        200,
        [
            'Content-Type'   =&gt; $content_type,
            'Content-Length' =&gt; $stat[7],
            'Last-Modified'  =&gt; HTTP::Date::time2str( $stat[9] )
        ],
        $fh,
    ];
}

sub return_403 {
    my $self = shift;
    return [403, ['Content-Type' =&gt; 'text/plain', 'Content-Length' =&gt; 9], ['forbidden']];
}

sub return_400 {
    my $self = shift;
    return [400, ['Content-Type' =&gt; 'text/plain', 'Content-Length' =&gt; 11], ['Bad Request']];
}

# Hint: subclasses can override this to return undef to pass through 404
sub return_404 {
    my $self = shift;
    return [404, ['Content-Type' =&gt; 'text/plain', 'Content-Length' =&gt; 9], ['not found']];
}

1;
__END__

=head1 NAME

Plack::App::File - Serve static files from root directory

=head1 SYNOPSIS

  use Plack::App::File;
  my $app = Plack::App::File-&gt;new(root =&gt; "/path/to/htdocs")-&gt;to_app;

  # Or map the path to a specific file
  use Plack::Builder;
  builder {
      mount "/favicon.ico" =&gt; Plack::App::File-&gt;new(file =&gt; '/path/to/favicon.ico');
  };

=head1 DESCRIPTION

This is a static file server PSGI application, and internally used by
L&lt;Plack::Middleware::Static&gt;. This application serves file from
document root if the path matches with the local file. Use
L&lt;Plack::App::Directory&gt; if you want to list files in the directory
as well.

=head1 CONFIGURATION

=over 4

=item root

Document root directory. Defaults to C&lt;.&gt; (current directory)

=item file

The file path to create responses from. Optional.

If it's set the application would B&lt;ALWAYS&gt; create a response out of
the file and there will be no security check etc. (hence fast). If
it's not set, the application uses C&lt;root&gt; to find the matching file.

=item encoding

Set the file encoding for text files. Defaults to C&lt;utf-8&gt;.

=item content_type

Set the file content type. If not set L&lt;Plack::MIME&gt; will try to detect it
based on the file extension or fall back to C&lt;text/plain&gt;.

=back

=head1 AUTHOR

Tatsuhiko Miyagawa

=head1 SEE ALSO

L&lt;Plack::Middleware::Static&gt; L&lt;Plack::App::Directory&gt;

=cut


</pre></body></html>