2 # Copyright (C) all contributors <meta@public-inbox.org>
3 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
6 use PublicInbox::TestCommon;
7 use PublicInbox::IO qw(write_file);
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;
19 use PublicInbox::WwwStatic;
20 my \$ws = PublicInbox::WwwStatic->new(docroot => "$tmpdir" @_);
21 builder { sub { \$ws->call(shift) } }
23 { psgi_file => $ret, TMPDIR => "$tmpdir" };
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;
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 /');
57 rename "$tmpdir/index.html", "$tmpdir/dir/index.html";
59 $res = $cb->(GET('/dir/'));
60 is($res->code, 200, '200 with autoindex for dir/');
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");
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');
103 is($res->content, "hi", 'got compressed on mtime match');
106 $get->header('Accept-Encoding' => 'gzip');
108 my $in = $res->content;
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');
117 HTTP::Date->import('time2str');
118 $get->header('If-Modified-Since' => time2str(0));
120 is $res->code, 304, '304 on If-Modified-Since hit';
121 $get->header('If-Modified-Since' => time2str(1));
123 is $res->code, 200, '200 on If-Modified-Since miss';
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";
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';
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;