viewvcs: handle exceptions in on_destroy cb
[public-inbox.git] / t / www_static.t
blobe258be1cf892272d62f657d0070d7feffb544f15
1 #!perl -w
2 # Copyright (C) all contributors <meta@public-inbox.org>
3 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4 use v5.12;
5 use autodie;
6 use PublicInbox::TestCommon;
7 use PublicInbox::IO qw(write_file);
8 my $tmpdir = tmpdir();
9 require_mods qw(psgi);
10 require IO::Uncompress::Gunzip;
11 use File::Path qw(remove_tree);
12 use_ok 'PublicInbox::WwwStatic';
14 my $psgi_env = sub { # @_ is passed to WwwStatic->new
15         my $ret = "$tmpdir/www_static.psgi";
16         write_file '>', $ret, <<EOM;
17 use v5.12;
18 use Plack::Builder;
19 use PublicInbox::WwwStatic;
20 my \$ws = PublicInbox::WwwStatic->new(docroot => "$tmpdir" @_);
21 builder { sub { \$ws->call(shift) } }
22 EOM
23         { psgi_file => $ret, TMPDIR => "$tmpdir" };
26 my $client = sub {
27         my $cb = shift;
28         unlink "$tmpdir/index.html" if -f "$tmpdir/index.html";
29         my $res = $cb->(GET('/'));
30         is($res->code, 404, '404 on "/" by default');
31         write_file '>', "$tmpdir/index.html", 'hi';
32         $res = $cb->(GET('/'));
33         is($res->code, 200, '200 with index.html');
34         is($res->content, 'hi', 'default index.html returned');
35         $res = $cb->(HEAD('/'));
36         is($res->code, 200, '200 on HEAD /');
37         is($res->content, '', 'no content');
38         is($res->header('Content-Length'), '2', 'content-length set');
39         like($res->header('Content-Type'), qr!^text/html\b!,
40                 'content-type is html');
43 my $env = $psgi_env->();
44 test_psgi(Plack::Util::load_psgi($env->{psgi_file}), $client);
45 test_httpd $env, $client;
47 $client = sub {
48         my $cb = shift;
49         write_file '>', "$tmpdir/index.html", 'hi';
50         my $res = $cb->(GET('/'));
51         my $updir = 'href="../">../</a>';
52         is($res->code, 200, '200 with autoindex default');
53         my $ls = $res->content;
54         like($ls, qr/index\.html/, 'got listing with index.html');
55         ok(index($ls, $updir) < 0, 'no updir at /');
56         mkdir "$tmpdir/dir";
57         rename "$tmpdir/index.html", "$tmpdir/dir/index.html";
59         $res = $cb->(GET('/dir/'));
60         is($res->code, 200, '200 with autoindex for dir/');
61         $ls = $res->content;
62         ok(index($ls, $updir) > 0, 'updir at /dir/');
64         for my $up (qw(/../ .. /dir/.. /dir/../)) {
65                 is($cb->(GET($up))->code, 403, "`$up' traversal rejected");
66         }
68         $res = $cb->(GET('/dir'));
69         is($res->code, 302, '302 w/o slash');
70         like($res->header('Location'), qr!://[^/]+/dir/\z!,
71                 'redirected w/ slash');
73         rename "$tmpdir/dir/index.html", "$tmpdir/dir/foo";
74         link "$tmpdir/dir/foo", "$tmpdir/dir/foo.gz";
75         $res = $cb->(GET('/dir/'));
76         unlike($res->content, qr/>foo\.gz</,
77                 '.gz file hidden if mtime matches uncompressed');
78         like($res->content, qr/>foo</, 'uncompressed foo shown');
80         $res = $cb->(GET('/dir/foo/bar'));
81         is($res->code, 404, 'using file as dir fails');
83         unlink "$tmpdir/dir/foo";
84         $res = $cb->(GET('/dir/'));
85         like($res->content, qr/>foo\.gz</,
86                 '.gz shown when no uncompressed version exists');
88         write_file '>', "$tmpdir/dir/foo", "uncompressed\n";
89         utime 0, 0, "$tmpdir/dir/foo";
90         $res = $cb->(GET('/dir/'));
91         my $html = $res->content;
92         like($html, qr/>foo</, 'uncompressed foo shown');
93         like($html, qr/>foo\.gz</, 'gzipped foo shown on mtime mismatch');
95         $res = $cb->(GET('/dir/foo'));
96         is($res->content, "uncompressed\n",
97                 'got uncompressed on mtime mismatch');
99         utime 0, 0, "$tmpdir/dir/foo.gz";
100         my $get = GET('/dir/foo');
101         $get->header('Accept-Encoding' => 'gzip');
102         $res = $cb->($get);
103         is($res->content, "hi", 'got compressed on mtime match');
105         $get = GET('/dir/');
106         $get->header('Accept-Encoding' => 'gzip');
107         $res = $cb->($get);
108         my $in = $res->content;
109         my $out = '';
110         IO::Uncompress::Gunzip::gunzip(\$in => \$out);
111         like($out, qr/\A<html>/, 'got HTML start after gunzip');
112         like($out, qr{</html>$}, 'got HTML end after gunzip');
113         unlink "$tmpdir/dir/foo.gz";
114         $get = GET('/dir/foo');
116         require HTTP::Date;
117         HTTP::Date->import('time2str');
118         $get->header('If-Modified-Since' => time2str(0));
119         $res = $cb->($get);
120         is $res->code, 304, '304 on If-Modified-Since hit';
121         $get->header('If-Modified-Since' => time2str(1));
122         $res = $cb->($get);
123         is $res->code, 200, '200 on If-Modified-Since miss';
124 SKIP: {
125         # validating with curl ensures we didn't carry the same
126         # misunderstandings across both the code being tested
127         # and the test itself.
128         $ENV{TEST_EXPENSIVE} or
129                 skip 'TEST_EXPENSIVE unset for validation w/curl', 1;
130         my $uri = $ENV{PLACK_TEST_EXTERNALSERVER_URI} or
131                 skip 'curl test skipped w/o external server', 1;
132         my $dst = "$tmpdir/foo.dst";
133         my $dst2 = "$dst.2";
134         $uri .= '/dir/foo';
135         state $curl = require_cmd 'curl', 1;
136         xsys_e $curl, '-gsSfR', '-o', $dst, $uri;
137         xsys_e [ $curl, '-vgsSfR', '-o', $dst2, $uri, '-z', $dst ],
138                 undef, { 2 => \(my $curl_err) };
139         like $curl_err, qr! HTTP/1\.[012] 304 !sm,
140                 'got 304 response w/ If-Modified-Since';
141         is -s $dst2, undef, 'nothing downloaded on 304';
142         utime 1, 1, "$tmpdir/dir/foo";
143         xsys_e [ $curl, '-vgsSfR', '-o', $dst2, $uri, '-z', $dst ],
144                 undef, { 2 => \$curl_err };
145         is -s $dst2, -s $dst, 'got 200 on If-Modified-Since mismatch';
146         like $curl_err, qr! HTTP/1\.[012] 200 !sm,
147                 'got 200 response w/ If-Modified-Since';
148 } # SKIP
149         remove_tree "$tmpdir/dir";
152 $env = $psgi_env->(', autoindex => 1, index => []');
153 test_psgi(Plack::Util::load_psgi($env->{psgi_file}), $client);
154 test_httpd $env, $client;
156 done_testing();