6 plan(skip_all => "IPC::Run not available")
17 # Black-box (ish) test for relative linking between CGI and static content
19 my $installed = $ENV{INSTALLED_TESTS};
23 @command = qw(ikiwiki);
26 ok(! system("make -s ikiwiki.out"));
27 @command = qw(perl -I. ./ikiwiki.out
28 --underlaydir=underlays/basewiki
29 --set underlaydirbase=underlays
30 --templatedir=templates);
33 sub parse_cgi_content {
36 if ($content =~ qr{<base href="([^"]+)" */>}) {
39 if ($content =~ qr{href="([^"]+/style.css)"}) {
40 $bits{stylehref} = $1;
42 if ($content =~ qr{class="parentlinks">\s+<a href="([^"]+)">this is the name of my wiki</a>/}s) {
45 if ($content =~ qr{<a[^>]+href="([^"]+)\?do=prefs"}) {
55 writefile($name, "t/tmp/in", $content);
56 ok(utime(333333333, 333333333, "t/tmp/in/$name"));
59 sub write_setup_file {
61 my $urlline = defined $args{url} ? "url: $args{url}" : "";
62 my $w3mmodeline = defined $args{w3mmode} ? "w3mmode: $args{w3mmode}" : "";
63 my $reverseproxyline = defined $args{reverse_proxy} ? "reverse_proxy: $args{reverse_proxy}" : "";
65 writefile("test.setup", "t/tmp", <<EOF
66 # IkiWiki::Setup::Yaml - YAML formatted setup file
67 wikiname: this is the name of my wiki
73 cgi_wrapper: t/tmp/ikiwiki.cgi
75 # make it easier to test previewing
80 ENV: { 'PERL5LIB': 'blib/lib:blib/arch' }
85 sub thoroughly_rebuild {
86 ok(unlink("t/tmp/ikiwiki.cgi") || $!{ENOENT});
87 ok(! system(@command, qw(--setup t/tmp/test.setup --rebuild --wrappers)));
90 sub check_cgi_mode_bits {
91 my (undef, undef, $mode, undef, undef,
92 undef, undef, undef, undef, undef,
93 undef, undef, undef) = stat("t/tmp/ikiwiki.cgi");
94 is($mode & 07777, 0754);
97 sub check_generated_content {
98 my $cgiurl_regex = shift;
99 ok(-e "t/tmp/out/a/b/c/index.html");
100 my $content = readfile("t/tmp/out/a/b/c/index.html");
101 # no <base> on static HTML
102 unlike($content, qr{<base\W});
103 like($content, $cgiurl_regex);
104 # cross-links between static pages are relative
105 like($content, qr{<li>A: <a href="../../">a</a></li>});
106 like($content, qr{<li>B: <a href="../">b</a></li>});
107 like($content, qr{<li>E: <a href="../../d/e/">e</a></li>});
113 my $is_preview = delete $args{is_preview};
114 my $is_https = delete $args{is_https};
116 SCRIPT_NAME => '/cgi-bin/ikiwiki.cgi',
117 HTTP_HOST => 'example.com',
119 if (defined $is_preview) {
120 $defaults{REQUEST_METHOD} = 'POST';
121 $in = 'do=edit&page=a/b/c&Preview';
122 $defaults{CONTENT_LENGTH} = length $in;
124 $defaults{REQUEST_METHOD} = 'GET';
125 $defaults{QUERY_STRING} = 'do=prefs';
127 if (defined $is_https) {
128 $defaults{SERVER_PORT} = '443';
129 $defaults{HTTPS} = 'on';
131 $defaults{SERVER_PORT} = '80';
137 run(["./t/tmp/ikiwiki.cgi"], \$in, \$out, init => sub {
139 $ENV{$_} = $envvars{$_}
147 ok(! system("rm -rf t/tmp"));
148 ok(! system("mkdir t/tmp"));
150 write_old_file("a.mdwn", "A");
151 write_old_file("a/b.mdwn", "B");
152 write_old_file("a/b/c.mdwn",
156 write_old_file("a/d.mdwn", "D");
157 write_old_file("a/d/e.mdwn", "E");
160 sub test_site1_perfectly_ordinary_ikiwiki {
162 url => "http://example.com/wiki/",
163 cgiurl => "http://example.com/cgi-bin/ikiwiki.cgi",
165 thoroughly_rebuild();
166 check_cgi_mode_bits();
167 # url and cgiurl are on the same host so the cgiurl is host-relative
168 check_generated_content(qr{<a[^>]+href="/cgi-bin/ikiwiki.cgi\?do=prefs"});
169 my %bits = parse_cgi_content(run_cgi());
170 like($bits{basehref}, qr{^(?:(?:http:)?//example\.com)?/wiki/$});
171 like($bits{stylehref}, qr{^(?:(?:http:)?//example.com)?/wiki/style.css$});
172 like($bits{tophref}, qr{^(?:/wiki|\.)/$});
173 like($bits{cgihref}, qr{^(?:(?:http:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
175 # when accessed via HTTPS, links are secure
176 %bits = parse_cgi_content(run_cgi(is_https => 1));
177 like($bits{basehref}, qr{^(?:(?:https:)?//example\.com)?/wiki/$});
178 like($bits{stylehref}, qr{^(?:(?:https:)?//example.com)?/wiki/style.css$});
179 like($bits{tophref}, qr{^(?:/wiki|\.)/$});
180 like($bits{cgihref}, qr{^(?:(?:https:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
182 # when accessed via a different hostname, links stay on that host
183 %bits = parse_cgi_content(run_cgi(HTTP_HOST => 'staging.example.net'));
184 like($bits{basehref}, qr{^(?:(?:http:)?//staging\.example\.net)?/wiki/$});
185 like($bits{stylehref}, qr{^(?:(?:http:)?//staging.example.net)?/wiki/style.css$});
186 like($bits{tophref}, qr{^(?:/wiki|\.)/$});
187 like($bits{cgihref}, qr{^(?:(?:http:)?//staging.example.net)?/cgi-bin/ikiwiki.cgi$});
190 %bits = parse_cgi_content(run_cgi(is_preview => 1));
191 like($bits{basehref}, qr{^(?:(?:http:)?//example\.com)?/wiki/a/b/c/$});
192 like($bits{stylehref}, qr{^(?:(?:http:)?//example.com)?/wiki/style.css$});
193 like($bits{tophref}, qr{^(?:/wiki|\.\./\.\./\.\.)/$});
194 like($bits{cgihref}, qr{^(?:(?:http:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
197 sub test_site2_static_content_and_cgi_on_different_servers {
199 url => "http://static.example.com/",
200 cgiurl => "http://cgi.example.com/ikiwiki.cgi",
202 thoroughly_rebuild();
203 check_cgi_mode_bits();
204 # url and cgiurl are not on the same host so the cgiurl has to be
205 # protocol-relative or absolute
206 check_generated_content(qr{<a[^>]+href="(?:http:)?//cgi.example.com/ikiwiki.cgi\?do=prefs"});
208 my %bits = parse_cgi_content(run_cgi(SCRIPT_NAME => '/ikiwiki.cgi', HTTP_HOST => 'cgi.example.com'));
209 like($bits{basehref}, qr{^(?:(?:http:)?//static.example.com)?/$});
210 like($bits{stylehref}, qr{^(?:(?:http:)?//static.example.com)?/style.css$});
211 like($bits{tophref}, qr{^(?:http:)?//static.example.com/$});
212 like($bits{cgihref}, qr{^(?:(?:http:)?//cgi.example.com)?/ikiwiki.cgi$});
214 # when accessed via HTTPS, links are secure
215 %bits = parse_cgi_content(run_cgi(is_https => 1, SCRIPT_NAME => '/ikiwiki.cgi', HTTP_HOST => 'cgi.example.com'));
216 like($bits{basehref}, qr{^(?:https:)?//static\.example\.com/$});
217 like($bits{stylehref}, qr{^(?:(?:https:)?//static.example.com)?/style.css$});
218 like($bits{tophref}, qr{^(?:https:)?//static.example.com/$});
219 like($bits{cgihref}, qr{^(?:(?:https:)?//cgi.example.com)?/ikiwiki.cgi$});
221 # when accessed via a different hostname, links to the CGI (only) should
223 %bits = parse_cgi_content(run_cgi(is_preview => 1, SCRIPT_NAME => '/ikiwiki.cgi', HTTP_HOST => 'staging.example.net'));
224 like($bits{basehref}, qr{^(?:http:)?//static\.example\.com/a/b/c/$});
225 like($bits{stylehref}, qr{^(?:(?:http:)?//static.example.com|\.\./\.\./\.\.)/style.css$});
226 like($bits{tophref}, qr{^(?:(?:http:)?//static.example.com|\.\./\.\./\.\.)/$});
227 like($bits{cgihref}, qr{^(?:(?:http:)?//(?:staging\.example\.net|cgi\.example\.com))?/ikiwiki.cgi$});
229 local $TODO = "use self-referential CGI URL?";
230 like($bits{cgihref}, qr{^(?:(?:http:)?//staging.example.net)?/ikiwiki.cgi$});
234 sub test_site3_we_specifically_want_everything_to_be_secure {
236 url => "https://example.com/wiki/",
237 cgiurl => "https://example.com/cgi-bin/ikiwiki.cgi",
239 thoroughly_rebuild();
240 check_cgi_mode_bits();
241 # url and cgiurl are on the same host so the cgiurl is host-relative
242 check_generated_content(qr{<a[^>]+href="/cgi-bin/ikiwiki.cgi\?do=prefs"});
244 # when accessed via HTTPS, links are secure
245 my %bits = parse_cgi_content(run_cgi(is_https => 1));
246 like($bits{basehref}, qr{^(?:(?:https:)?//example\.com)?/wiki/$});
247 like($bits{stylehref}, qr{^(?:(?:https:)?//example.com)?/wiki/style.css$});
248 like($bits{tophref}, qr{^(?:/wiki|\.)/$});
249 like($bits{cgihref}, qr{^(?:(?:https:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
251 # when not accessed via HTTPS, links should still be secure
252 # (but if this happens, that's a sign of web server misconfiguration)
253 %bits = parse_cgi_content(run_cgi());
254 like($bits{tophref}, qr{^(?:/wiki|\.)/$});
256 local $TODO = "treat https in configured url, cgiurl as required?";
257 is($bits{basehref}, "https://example.com/wiki/");
258 like($bits{stylehref}, qr{^(?:(?:https:)?//example.com)?/wiki/style.css$});
260 like($bits{cgihref}, qr{^(?:(?:https:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
262 # when accessed via a different hostname, links stay on that host
263 %bits = parse_cgi_content(run_cgi(is_https => 1, HTTP_HOST => 'staging.example.net'));
264 like($bits{basehref}, qr{^(?:(?:https:)?//staging\.example\.net)?/wiki/$});
265 like($bits{stylehref}, qr{^(?:(?:https:)?//staging.example.net)?/wiki/style.css$});
266 like($bits{tophref}, qr{^(?:/wiki|\.)/$});
267 like($bits{cgihref}, qr{^(?:(?:https:)?//staging.example.net)?/cgi-bin/ikiwiki.cgi$});
270 %bits = parse_cgi_content(run_cgi(is_preview => 1, is_https => 1));
271 like($bits{basehref}, qr{^(?:(?:https:)?//example\.com)?/wiki/a/b/c/$});
272 like($bits{stylehref}, qr{^(?:(?:https:)?//example.com)?/wiki/style.css$});
273 like($bits{tophref}, qr{^(?:/wiki|\.\./\.\./\.\.)/$});
274 like($bits{cgihref}, qr{^(?:(?:https:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
277 sub test_site4_cgi_is_secure_static_content_doesnt_have_to_be {
280 url => "http://example.com/wiki/",
281 cgiurl => "https://example.com/cgi-bin/ikiwiki.cgi",
283 thoroughly_rebuild();
284 check_cgi_mode_bits();
285 # url and cgiurl are on the same host but different schemes
286 check_generated_content(qr{<a[^>]+href="https://example.com/cgi-bin/ikiwiki.cgi\?do=prefs"});
288 # when accessed via HTTPS, links are secure (to avoid mixed-content)
289 my %bits = parse_cgi_content(run_cgi(is_https => 1));
290 like($bits{basehref}, qr{^(?:(?:https:)?//example\.com)?/wiki/$});
291 like($bits{stylehref}, qr{^(?:(?:https:)?//example.com)?/wiki/style.css$});
292 like($bits{tophref}, qr{^(?:/wiki|\.)/$});
293 like($bits{cgihref}, qr{^(?:(?:https:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
295 # FIXME: when not accessed via HTTPS, should the static content be
296 # forced to https anyway? For now we accept either
297 %bits = parse_cgi_content(run_cgi());
298 like($bits{basehref}, qr{^(?:(?:https?)?://example\.com)?/wiki/$});
299 like($bits{stylehref}, qr{^(?:(?:https?:)?//example.com)?/wiki/style.css$});
300 like($bits{tophref}, qr{^(?:(?:https?://example.com)?/wiki|\.)/$});
301 like($bits{cgihref}, qr{^(?:(?:https:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
303 # when accessed via a different hostname, links stay on that host
304 %bits = parse_cgi_content(run_cgi(is_https => 1, HTTP_HOST => 'staging.example.net'));
305 # because the static and dynamic stuff is on the same server, we assume that
306 # both are also on the staging server
307 like($bits{basehref}, qr{^(?:(?:https:)?//staging\.example\.net)?/wiki/$});
308 like($bits{stylehref}, qr{^(?:(?:https:)?//staging.example.net)?/wiki/style.css$});
309 like($bits{tophref}, qr{^(?:(?:(?:https:)?//staging.example.net)?/wiki|\.)/$});
310 like($bits{cgihref}, qr{^(?:(?:https:)?//(?:staging\.example\.net|example\.com))?/cgi-bin/ikiwiki.cgi$});
312 local $TODO = "this should really point back to itself but currently points to example.com";
313 like($bits{cgihref}, qr{^(?:(?:https:)?//staging.example.net)?/cgi-bin/ikiwiki.cgi$});
317 %bits = parse_cgi_content(run_cgi(is_preview => 1, is_https => 1));
318 like($bits{basehref}, qr{^(?:(?:https:)?//example\.com)?/wiki/a/b/c/$});
319 like($bits{stylehref}, qr{^(?:(?:https:)?//example.com)?/wiki/style.css$});
320 like($bits{tophref}, qr{^(?:/wiki|\.\./\.\./\.\.)/$});
321 like($bits{cgihref}, qr{^(?:(?:https:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
324 sub test_site5_w3mmode {
325 # as documented in [[w3mmode]]
328 cgiurl => "ikiwiki.cgi",
331 thoroughly_rebuild();
332 check_cgi_mode_bits();
333 # FIXME: does /$LIB/ikiwiki-w3m.cgi work under w3m?
334 check_generated_content(qr{<a[^>]+href="(?:file://)?/\$LIB/ikiwiki-w3m.cgi/ikiwiki.cgi\?do=prefs"});
336 my %bits = parse_cgi_content(run_cgi(PATH_INFO => '/ikiwiki.cgi', SCRIPT_NAME => '/cgi-bin/ikiwiki-w3m.cgi'));
338 like($bits{tophref}, qr{^(?:\Q$pwd\E/t/tmp/out|\.)/$});
339 like($bits{cgihref}, qr{^(?:file://)?/\$LIB/ikiwiki-w3m.cgi/ikiwiki.cgi$});
340 like($bits{basehref}, qr{^(?:(?:file:)?//)?\Q$pwd\E/t/tmp/out/$});
341 like($bits{stylehref}, qr{^(?:(?:(?:file:)?//)?\Q$pwd\E/t/tmp/out|\.)/style.css$});
344 sub test_site6_behind_reverse_proxy {
346 url => "https://example.com/wiki/",
347 cgiurl => "https://example.com/cgi-bin/ikiwiki.cgi",
350 thoroughly_rebuild();
351 check_cgi_mode_bits();
352 # url and cgiurl are on the same host so the cgiurl is host-relative
353 check_generated_content(qr{<a[^>]+href="/cgi-bin/ikiwiki.cgi\?do=prefs"});
355 # because we are behind a reverse-proxy we must assume that
356 # we're being accessed by the configured cgiurl
357 my %bits = parse_cgi_content(run_cgi(HTTP_HOST => 'localhost'));
358 like($bits{tophref}, qr{^(?:/wiki|\.)/$});
359 like($bits{cgihref}, qr{^(?:(?:https:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
360 like($bits{basehref}, qr{^(?:(?:https:)?//example\.com)?/wiki/$});
361 like($bits{stylehref}, qr{^(?:(?:https:)?//example.com)?/wiki/style.css$});
364 %bits = parse_cgi_content(run_cgi(is_preview => 1, HTTP_HOST => 'localhost'));
365 like($bits{tophref}, qr{^(?:/wiki|\.\./\.\./\.\.)/$});
366 like($bits{cgihref}, qr{^(?:(?:https:)?//example.com)?/cgi-bin/ikiwiki.cgi$});
367 like($bits{basehref}, qr{^(?:(?:https)?://example\.com)?/wiki/a/b/c/$});
368 like($bits{stylehref}, qr{^(?:(?:https:)?//example.com)?/wiki/style.css$});
373 test_site1_perfectly_ordinary_ikiwiki();
374 test_site2_static_content_and_cgi_on_different_servers();
375 test_site3_we_specifically_want_everything_to_be_secure();
376 test_site4_cgi_is_secure_static_content_doesnt_have_to_be();
377 test_site5_w3mmode();
378 test_site6_behind_reverse_proxy();