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

# Stolen from rack/directory.rb
my $dir_file = "&lt;tr&gt;&lt;td class='name'&gt;&lt;a href='%s'&gt;%s&lt;/a&gt;&lt;/td&gt;&lt;td class='size'&gt;%s&lt;/td&gt;&lt;td class='type'&gt;%s&lt;/td&gt;&lt;td class='mtime'&gt;%s&lt;/td&gt;&lt;/tr&gt;";
my $dir_page = &lt;&lt;PAGE;
&lt;html&gt;&lt;head&gt;
  &lt;title&gt;%s&lt;/title&gt;
  &lt;meta http-equiv="content-type" content="text/html; charset=utf-8" /&gt;
  &lt;style type='text/css'&gt;
table { width:100%%; }
.name { text-align:left; }
.size, .mtime { text-align:right; }
.type { width:11em; }
.mtime { width:15em; }
  &lt;/style&gt;
&lt;/head&gt;&lt;body&gt;
&lt;h1&gt;%s&lt;/h1&gt;
&lt;hr /&gt;
&lt;table&gt;
  &lt;tr&gt;
    &lt;th class='name'&gt;Name&lt;/th&gt;
    &lt;th class='size'&gt;Size&lt;/th&gt;
    &lt;th class='type'&gt;Type&lt;/th&gt;
    &lt;th class='mtime'&gt;Last Modified&lt;/th&gt;
  &lt;/tr&gt;
%s
&lt;/table&gt;
&lt;hr /&gt;
&lt;/body&gt;&lt;/html&gt;
PAGE

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

sub return_dir_redirect {
    my ($self, $env) = @_;
    my $uri = Plack::Request-&gt;new($env)-&gt;uri;
    return [ 301,
        [
            'Location' =&gt; $uri . '/',
            'Content-Type' =&gt; 'text/plain',
            'Content-Length' =&gt; 8,
        ],
        [ 'Redirect' ],
    ];
}

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

    if (-f $dir) {
        return $self-&gt;SUPER::serve_path($env, $dir, $fullpath);
    }

    my $dir_url = $env-&gt;{SCRIPT_NAME} . $env-&gt;{PATH_INFO};

    if ($dir_url !~ m{/$}) {
        return $self-&gt;return_dir_redirect($env);
    }

    my @files = ([ "../", "Parent Directory", '', '', '' ]);

    my $dh = DirHandle-&gt;new($dir);
    my @children;
    while (defined(my $ent = $dh-&gt;read)) {
        next if $ent eq '.' or $ent eq '..';
        push @children, $ent;
    }

    for my $basename (sort { $a cmp $b } @children) {
        my $file = "$dir/$basename";
        my $url = $dir_url . $basename;

        my $is_dir = -d $file;
        my @stat = stat _;

        $url = join '/', map {uri_escape($_)} split m{/}, $url;

        if ($is_dir) {
            $basename .= "/";
            $url      .= "/";
        }

        my $mime_type = $is_dir ? 'directory' : ( Plack::MIME-&gt;mime_type($file) || 'text/plain' );
        push @files, [ $url, $basename, $stat[7], $mime_type, HTTP::Date::time2str($stat[9]) ];
    }

    my $path  = Plack::Util::encode_html("Index of $env-&gt;{PATH_INFO}");
    my $files = join "\n", map {
        my $f = $_;
        sprintf $dir_file, map Plack::Util::encode_html($_), @$f;
    } @files;
    my $page  = sprintf $dir_page, $path, $path, $files;

    return [ 200, ['Content-Type' =&gt; 'text/html; charset=utf-8'], [ $page ] ];
}

1;

__END__

=head1 NAME

Plack::App::Directory - Serve static files from document root with directory index

=head1 SYNOPSIS

  # app.psgi
  use Plack::App::Directory;
  my $app = Plack::App::Directory-&gt;new({ root =&gt; "/path/to/htdocs" })-&gt;to_app;

=head1 DESCRIPTION

This is a static file server PSGI application with directory index a la Apache's mod_autoindex.

=head1 CONFIGURATION

=over 4

=item root

Document root directory. Defaults to the current directory.

=back

=head1 AUTHOR

Tatsuhiko Miyagawa

=head1 SEE ALSO

L&lt;Plack::App::File&gt;

=cut
</pre></body></html>