+# by default, this returns "index.html" if it exists for a given directory
+# It'll generate a directory listing, (autoindex).
+# May be disabled by setting autoindex => 0
+sub dir_response ($$$) {
+ my ($self, $env, $fs_path) = @_;
+ if (my $index = $self->{'index'}) { # serve index.html or similar
+ for my $html (@$index) {
+ my $p = $fs_path . $html;
+ my $res = response($env, [], $p);
+ return $res if $res->[0] != 404;
+ }
+ }
+ return r(404) unless $self->{autoindex};
+ opendir(my $dh, $fs_path) or do {
+ return r(404) if ($! == ENOENT || $! == ENOTDIR);
+ return r(403) if $! == EACCES;
+ return r(500);
+ };
+ my @entries = grep(!/\A\./, readdir($dh));
+ $dh = undef;
+ my (%dirs, %other, %want_gz);
+ my $path_info = $env->{PATH_INFO};
+ push @entries, '..' if $path_info ne '/';
+ for my $base (@entries) {
+ my $href = ascii_html(uri_escape_utf8($base));
+ my $name = ascii_html($base);
+ my @st = stat($fs_path . $base) or next; # unlikely
+ my ($gzipped, $uncompressed, $hsize);
+ my $entry = '';
+ my $mtime = $st[9];
+ if (-d _) {
+ $href .= '/';
+ $name .= '/';
+ $hsize = '-';
+ $dirs{"$base\0$mtime"} = \$entry;
+ } elsif (-f _) {
+ $other{"$base\0$mtime"} = \$entry;
+ if ($base !~ /\.gz\z/i) {
+ $want_gz{"$base.gz\0$mtime"} = undef;
+ }
+ $hsize = human_size($st[7]);
+ } else {
+ next;
+ }
+ # 54 = 80 - (SP length(strftime(%Y-%m-%d %k:%M)) SP human_size)
+ $hsize = sprintf('% 8s', $hsize);
+ my $pad = 54 - length($name);
+ $pad = 1 if $pad <= 0;
+ $entry .= qq(<a\nhref="$href">$name</a>) . (' ' x $pad);
+ $mtime = strftime('%Y-%m-%d %k:%M', gmtime($mtime));
+ $entry .= $mtime . $hsize;