1 # Copyright (C) 2019-2020 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
6 use PublicInbox::TestCommon;
7 my ($tmpdir, $for_destroy) = tmpdir();
8 my @mods = qw(HTTP::Request::Common Plack::Test URI::Escape);
9 require_mods(@mods, 'IO::Uncompress::Gunzip');
10 use_ok $_ foreach @mods;
11 use_ok 'PublicInbox::WwwStatic';
14 my $ws = PublicInbox::WwwStatic->new(docroot => $tmpdir, @_);
15 sub { $ws->call(shift) };
18 test_psgi($app->(), sub {
20 my $res = $cb->(GET('/'));
21 is($res->code, 404, '404 on "/" by default');
22 open my $fh, '>', "$tmpdir/index.html" or die;
23 print $fh 'hi' or die;
25 $res = $cb->(GET('/'));
26 is($res->code, 200, '200 with index.html');
27 is($res->content, 'hi', 'default index.html returned');
28 $res = $cb->(HEAD('/'));
29 is($res->code, 200, '200 on HEAD /');
30 is($res->content, '', 'no content');
31 is($res->header('Content-Length'), '2', 'content-length set');
32 like($res->header('Content-Type'), qr!^text/html\b!,
33 'content-type is html');
36 test_psgi($app->(autoindex => 1, index => []), sub {
38 my $res = $cb->(GET('/'));
39 my $updir = 'href="../">../</a>';
40 is($res->code, 200, '200 with autoindex default');
41 my $ls = $res->content;
42 like($ls, qr/index\.html/, 'got listing with index.html');
43 ok(index($ls, $updir) < 0, 'no updir at /');
44 mkdir("$tmpdir/dir") or die;
45 rename("$tmpdir/index.html", "$tmpdir/dir/index.html") or die;
47 $res = $cb->(GET('/dir/'));
48 is($res->code, 200, '200 with autoindex for dir/');
50 ok(index($ls, $updir) > 0, 'updir at /dir/');
52 for my $up (qw(/../ .. /dir/.. /dir/../)) {
53 is($cb->(GET($up))->code, 403, "`$up' traversal rejected");
56 $res = $cb->(GET('/dir'));
57 is($res->code, 302, '302 w/o slash');
58 like($res->header('Location'), qr!://[^/]+/dir/\z!,
59 'redirected w/ slash');
61 rename("$tmpdir/dir/index.html", "$tmpdir/dir/foo") or die;
62 link("$tmpdir/dir/foo", "$tmpdir/dir/foo.gz") or die;
63 $res = $cb->(GET('/dir/'));
64 unlike($res->content, qr/>foo\.gz</,
65 '.gz file hidden if mtime matches uncompressed');
66 like($res->content, qr/>foo</, 'uncompressed foo shown');
68 $res = $cb->(GET('/dir/foo/bar'));
69 is($res->code, 404, 'using file as dir fails');
71 unlink("$tmpdir/dir/foo") or die;
72 $res = $cb->(GET('/dir/'));
73 like($res->content, qr/>foo\.gz</,
74 '.gz shown when no uncompressed version exists');
76 open my $fh, '>', "$tmpdir/dir/foo" or die;
77 print $fh "uncompressed\n" or die;
79 utime(0, 0, "$tmpdir/dir/foo") or die;
80 $res = $cb->(GET('/dir/'));
81 my $html = $res->content;
82 like($html, qr/>foo</, 'uncompressed foo shown');
83 like($html, qr/>foo\.gz</, 'gzipped foo shown on mtime mismatch');
85 $res = $cb->(GET('/dir/foo'));
86 is($res->content, "uncompressed\n",
87 'got uncompressed on mtime mismatch');
89 utime(0, 0, "$tmpdir/dir/foo.gz") or die;
90 my $get = GET('/dir/foo');
91 $get->header('Accept-Encoding' => 'gzip');
93 is($res->content, "hi", 'got compressed on mtime match');
96 $get->header('Accept-Encoding' => 'gzip');
98 my $in = $res->content;
100 IO::Uncompress::Gunzip::gunzip(\$in => \$out);
101 like($out, qr/\A<html>/, 'got HTML start after gunzip');
102 like($out, qr{</html>$}, 'got HTML end after gunzip');