]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blob - t/git-cgi.t
Add jmtd's permalink plugin.
[git.ikiwiki.info.git] / t / git-cgi.t
1 #!/usr/bin/perl
2 use utf8;
3 use warnings;
4 use strict;
6 use Encode;
7 use Test::More;
9 BEGIN {
10         my $git = `which git`;
11         chomp $git;
12         plan(skip_all => 'git not available') unless -x $git;
14         plan(skip_all => "CGI not available")
15                 unless eval q{
16                         use CGI qw();
17                         1;
18                 };
20         plan(skip_all => "IPC::Run not available")
21                 unless eval q{
22                         use IPC::Run qw(run);
23                         1;
24                 };
26         use_ok('IkiWiki');
27         use_ok('YAML::XS');
28 }
30 # We check for English error messages
31 $ENV{LC_ALL} = 'C';
33 use Cwd qw(getcwd);
34 use Errno qw(ENOENT);
36 my $installed = $ENV{INSTALLED_TESTS};
38 my @command;
39 if ($installed) {
40         @command = qw(ikiwiki --plugin inline);
41 }
42 else {
43         ok(! system("make -s ikiwiki.out"));
44         @command = ("perl", "-I".getcwd."/blib/lib", './ikiwiki.out',
45                 '--underlaydir='.getcwd.'/underlays/basewiki',
46                 '--set', 'underlaydirbase='.getcwd.'/underlays',
47                 '--templatedir='.getcwd.'/templates');
48 }
50 sub write_old_file {
51         my $name = shift;
52         my $dir = shift;
53         my $content = shift;
54         writefile($name, $dir, $content);
55         ok(utime(333333333, 333333333, "$dir/$name"));
56 }
58 sub write_setup_file {
59         my %setup = (
60                 wikiname => 'this is the name of my wiki',
61                 srcdir => getcwd.'/t/tmp/in/doc',
62                 destdir => getcwd.'/t/tmp/out',
63                 url => 'http://example.com',
64                 cgiurl => 'http://example.com/cgi-bin/ikiwiki.cgi',
65                 cgi_wrapper => getcwd.'/t/tmp/ikiwiki.cgi',
66                 cgi_wrappermode => '0751',
67                 add_plugins => [qw(anonok attachment lockedit recentchanges)],
68                 disable_plugins => [qw(emailauth openid passwordauth)],
69                 anonok_pagespec => 'writable/*',
70                 locked_pages => '!writable/*',
71                 rcs => 'git',
72                 git_wrapper => getcwd.'/t/tmp/in/.git/hooks/post-commit',
73                 git_wrappermode => '0754',
74                 gitorigin_branch => '',
75         );
76         unless ($installed) {
77                 $setup{ENV} = { 'PERL5LIB' => getcwd.'/blib/lib' };
78         }
79         writefile("test.setup", "t/tmp",
80                 "# IkiWiki::Setup::Yaml - YAML formatted setup file\n" .
81                 Dump(\%setup));
82 }
84 sub thoroughly_rebuild {
85         ok(unlink("t/tmp/ikiwiki.cgi") || $!{ENOENT});
86         ok(unlink("t/tmp/in/.git/hooks/post-commit") || $!{ENOENT});
87         ok(! system(@command, qw(--setup t/tmp/test.setup --rebuild --wrappers)));
88 }
90 sub check_cgi_mode_bits {
91         my $mode;
93         (undef, undef, $mode, undef, undef,
94                 undef, undef, undef, undef, undef,
95                 undef, undef, undef) = stat('t/tmp/ikiwiki.cgi');
96         is ($mode & 07777, 0751);
97         (undef, undef, $mode, undef, undef,
98                 undef, undef, undef, undef, undef,
99                 undef, undef, undef) = stat('t/tmp/in/.git/hooks/post-commit');
100         is ($mode & 07777, 0754);
103 sub run_cgi {
104         my (%args) = @_;
105         my ($in, $out);
106         my $method = $args{method} || 'GET';
107         my $environ = $args{environ} || {};
108         my $params = $args{params} || { do => 'prefs' };
110         my %defaults = (
111                 SCRIPT_NAME     => '/cgi-bin/ikiwiki.cgi',
112                 HTTP_HOST       => 'example.com',
113         );
115         my $cgi = CGI->new($args{params});
116         my $query_string = $cgi->query_string();
117         diag $query_string;
119         if ($method eq 'POST') {
120                 $defaults{REQUEST_METHOD} = 'POST';
121                 $in = $query_string;
122                 $defaults{CONTENT_LENGTH} = length $in;
123         } else {
124                 $defaults{REQUEST_METHOD} = 'GET';
125                 $defaults{QUERY_STRING} = $query_string;
126         }
128         my %envvars = (
129                 %defaults,
130                 %$environ,
131         );
132         run(["./t/tmp/ikiwiki.cgi"], \$in, \$out, init => sub {
133                 map {
134                         $ENV{$_} = $envvars{$_}
135                 } keys(%envvars);
136         });
138         return decode_utf8($out);
141 sub run_git {
142         my (undef, $filename, $line) = caller;
143         my $args = shift;
144         my $desc = shift || join(' ', 'git', @$args);
145         my ($in, $out);
146         ok(run(['git', @$args], \$in, \$out, init => sub {
147                 chdir 't/tmp/in' or die $!;
148                 my $name = 'The IkiWiki Tests';
149                 my $email = 'nobody@ikiwiki-tests.invalid';
150                 if ($args->[0] eq 'commit') {
151                         $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
152                         $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
153                 }
154         }), "$desc at $filename:$line");
155         return $out;
158 sub test {
159         my $content;
160         my $status;
162         ok(! system(qw(rm -rf t/tmp)));
163         ok(! system(qw(mkdir t/tmp)));
165         write_old_file('.gitignore', 't/tmp/in', "/doc/.ikiwiki/\n");
166         write_old_file('doc/writable/one.mdwn', 't/tmp/in', 'This is the first test page');
167         write_old_file('doc/writable/two.mdwn', 't/tmp/in', 'This is the second test page');
168         write_old_file('doc/writable/three.mdwn', 't/tmp/in', 'This is the third test page');
169         write_old_file('doc/writable/three.bin', 't/tmp/in', 'An attachment');
170         write_old_file('doc/writable/blog.mdwn', 't/tmp/in',
171                 '[[!inline pages="writable/blog/*" actions=yes rootpage=writable/blog postform=yes show=0]]');
172         write_old_file('doc/writable/__172__blog.mdwn', 't/tmp/in',
173                 '[[!inline pages="writable/¬blog/*" actions=yes rootpage="writable/¬blog" postform=yes show=0]]');
174         write_old_file('doc/writable/中文.mdwn', 't/tmp/in',
175                 '[[!inline pages="writable/中文/*" actions=yes rootpage="writable/中文" postform=yes show=0]]');
177         unless ($installed) {
178                 ok(! system(qw(cp -pRL doc/wikiicons t/tmp/in/doc/)));
179                 ok(! system(qw(cp -pRL doc/recentchanges.mdwn t/tmp/in/doc/)));
180         }
182         run_git(['init']);
183         run_git(['add', '.']);
184         run_git(['commit', '-m', 'Initial commit']);
186         write_setup_file();
187         thoroughly_rebuild();
188         check_cgi_mode_bits();
190         ok(-e 't/tmp/out/writable/one/index.html');
191         $content = readfile('t/tmp/out/writable/one/index.html');
192         like($content, qr{This is the first test page});
193         my $orig_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
195         # We have to wait 1 second here so that new writes are guaranteed
196         # to have a strictly larger mtime.
197         sleep 1;
199         # Test the git hook, which accepts git commits
200         writefile('doc/writable/one.mdwn', 't/tmp/in',
201                 'This is new content for the first test page');
202         run_git(['add', '.']);
203         run_git(['commit', '-m', 'Git commit']);
204         my $first_revertable_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
205         isnt($orig_sha1, $first_revertable_sha1);
207         ok(-e 't/tmp/out/writable/one/index.html');
208         $content = readfile('t/tmp/out/writable/one/index.html');
209         like($content, qr{This is new content for the first test page});
211         # Test a web commit
212         $content = run_cgi(method => 'POST',
213                 params => {
214                         do => 'edit',
215                         page => 'writable/two',
216                         type => 'mdwn',
217                         editmessage => 'Web commit',
218                         editcontent => 'Here is new content for the second page',
219                         _submit => 'Save Page',
220                         _submitted => '1',
221                 },
222         );
223         like($content, qr{^Status:\s*302\s}m);
224         like($content, qr{^Location:\s*http://example\.com/writable/two/\?updated}m);
225         my $second_revertable_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
226         isnt($orig_sha1, $second_revertable_sha1);
227         isnt($first_revertable_sha1, $second_revertable_sha1);
229         ok(-e 't/tmp/out/writable/two/index.html');
230         $content = readfile('t/tmp/out/writable/two/index.html');
231         like($content, qr{Here is new content for the second page});
233         # Another edit
234         writefile('doc/writable/three.mdwn', 't/tmp/in',
235                 'Also new content for the third page');
236         unlink('t/tmp/in/doc/writable/three.bin');
237         writefile('doc/writable/three.bin', 't/tmp/in',
238                 'Changed attachment');
239         run_git(['add', '.']);
240         run_git(['commit', '-m', 'Git commit']);
241         ok(-e 't/tmp/out/writable/three/index.html');
242         $content = readfile('t/tmp/out/writable/three/index.html');
243         like($content, qr{Also new content for the third page});
244         $content = readfile('t/tmp/out/writable/three.bin');
245         like($content, qr{Changed attachment});
246         my $third_revertable_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
247         isnt($orig_sha1, $third_revertable_sha1);
248         isnt($second_revertable_sha1, $third_revertable_sha1);
250         run_git(['mv', 'doc/writable/one.mdwn', 'doc/one.mdwn']);
251         run_git(['mv', 'doc/writable/two.mdwn', 'two.mdwn']);
252         run_git(['commit', '-m', 'Rename files to test CVE-2016-10026']);
253         ok(! -e 't/tmp/out/writable/two/index.html');
254         ok(! -e 't/tmp/out/writable/one/index.html');
255         ok(-e 't/tmp/out/one/index.html');
256         my $sha1_before_revert = run_git(['rev-list', '--max-count=1', 'HEAD']);
257         isnt($sha1_before_revert, $third_revertable_sha1);
259         $content = run_cgi(method => 'post',
260                 params => {
261                         do => 'revert',
262                         revertmessage => 'CVE-2016-10026',
263                         rev => $first_revertable_sha1,
264                         _submit => 'Revert',
265                         _submitted_revert => '1',
266                 },
267         );
268         like($content, qr{is locked and cannot be edited});
269         # The tree is left clean
270         run_git(['diff', '--exit-code']);
271         run_git(['diff', '--cached', '--exit-code']);
272         my $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
273         is($sha1, $sha1_before_revert);
275         ok(-e 't/tmp/out/one/index.html');
276         ok(! -e 't/tmp/in/doc/writable/one.mdwn');
277         ok(-e 't/tmp/in/doc/one.mdwn');
278         $content = readfile('t/tmp/out/one/index.html');
279         like($content, qr{This is new content for the first test page});
281         $content = run_cgi(method => 'post',
282                 params => {
283                         do => 'revert',
284                         revertmessage => 'CVE-2016-10026',
285                         rev => $second_revertable_sha1,
286                         _submit => 'Revert',
287                         _submitted_revert => '1',
288                 },
289         );
290         like($content, qr{you are not allowed to change two\.mdwn});
291         run_git(['diff', '--exit-code']);
292         run_git(['diff', '--cached', '--exit-code']);
293         $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
294         is($sha1, $sha1_before_revert);
296         ok(! -e 't/tmp/out/writable/two/index.html');
297         ok(! -e 't/tmp/out/two/index.html');
298         ok(! -e 't/tmp/in/doc/writable/two.mdwn');
299         ok(-e 't/tmp/in/two.mdwn');
300         $content = readfile('t/tmp/in/two.mdwn');
301         like($content, qr{Here is new content for the second page});
303         # We have to wait 1 second here so that new writes are guaranteed
304         # to have a strictly larger mtime.
305         sleep 1;
307         # This one can legitimately be reverted
308         $content = run_cgi(method => 'post',
309                 params => {
310                         do => 'revert',
311                         revertmessage => 'not CVE-2016-10026',
312                         rev => $third_revertable_sha1,
313                         _submit => 'Revert',
314                         _submitted_revert => '1',
315                 },
316         );
317         like($content, qr{^Status:\s*302\s}m);
318         like($content, qr{^Location:\s*http://example\.com/recentchanges/}m);
319         run_git(['diff', '--exit-code']);
320         run_git(['diff', '--cached', '--exit-code']);
321         ok(-e 't/tmp/out/writable/three/index.html');
322         $content = readfile('t/tmp/out/writable/three/index.html');
323         like($content, qr{This is the third test page});
324         $content = readfile('t/tmp/out/writable/three.bin');
325         like($content, qr{An attachment});
327         $content = readfile('t/tmp/out/writable/blog/index.html');
328         like($content, qr{<input type="hidden" name="from" value="writable/blog"});
329         $content = run_cgi(method => 'get',
330                 params => {
331                         do => 'blog',
332                         from => 'writable/blog',
333                         subpage => '1',
334                         title => 'hello',
335                 },
336         );
337         like($content, qr{<option selected="selected" value="writable/blog/hello">writable/blog/hello</option>});
339         # Regression test for a bug in which we couldn't use an
340         # alphanumeric, but non-ASCII, root page.
341         $content = readfile('t/tmp/out/writable/中文/index.html');
342         like($content, qr{<input type="hidden" name="from" value="writable/中文"});
343         $content = run_cgi(method => 'get',
344                 params => {
345                         do => 'blog',
346                         from => 'writable/中文',
347                         subpage => '1',
348                         title => 'hello',
349                 },
350         );
351         like($content, qr{<option selected="selected" value="writable/中文/hello">writable/中文/hello</option>});
352         unlike($content, qr{Error: bad page name});
355 test();
357 done_testing();