ikiwiki/t/git-cgi.t

358 lines
11 KiB
Perl
Executable File

#!/usr/bin/perl
use utf8;
use warnings;
use strict;
use Encode;
use Test::More;
BEGIN {
my $git = `which git`;
chomp $git;
plan(skip_all => 'git not available') unless -x $git;
plan(skip_all => "CGI not available")
unless eval q{
use CGI qw();
1;
};
plan(skip_all => "IPC::Run not available")
unless eval q{
use IPC::Run qw(run);
1;
};
use_ok('IkiWiki');
use_ok('YAML::XS');
}
# We check for English error messages
$ENV{LC_ALL} = 'C';
use Cwd qw(getcwd);
use Errno qw(ENOENT);
my $installed = $ENV{INSTALLED_TESTS};
my @command;
if ($installed) {
@command = qw(ikiwiki --plugin inline);
}
else {
ok(! system("make -s ikiwiki.out"));
@command = ("perl", "-I".getcwd."/blib/lib", './ikiwiki.out',
'--underlaydir='.getcwd.'/underlays/basewiki',
'--set', 'underlaydirbase='.getcwd.'/underlays',
'--templatedir='.getcwd.'/templates');
}
sub write_old_file {
my $name = shift;
my $dir = shift;
my $content = shift;
writefile($name, $dir, $content);
ok(utime(333333333, 333333333, "$dir/$name"));
}
sub write_setup_file {
my %setup = (
wikiname => 'this is the name of my wiki',
srcdir => getcwd.'/t/tmp/in/doc',
destdir => getcwd.'/t/tmp/out',
url => 'http://example.com',
cgiurl => 'http://example.com/cgi-bin/ikiwiki.cgi',
cgi_wrapper => getcwd.'/t/tmp/ikiwiki.cgi',
cgi_wrappermode => '0751',
add_plugins => [qw(anonok attachment lockedit recentchanges)],
disable_plugins => [qw(emailauth openid passwordauth)],
anonok_pagespec => 'writable/*',
locked_pages => '!writable/*',
rcs => 'git',
git_wrapper => getcwd.'/t/tmp/in/.git/hooks/post-commit',
git_wrappermode => '0754',
gitorigin_branch => '',
);
unless ($installed) {
$setup{ENV} = { 'PERL5LIB' => getcwd.'/blib/lib' };
}
writefile("test.setup", "t/tmp",
"# IkiWiki::Setup::Yaml - YAML formatted setup file\n" .
Dump(\%setup));
}
sub thoroughly_rebuild {
ok(unlink("t/tmp/ikiwiki.cgi") || $!{ENOENT});
ok(unlink("t/tmp/in/.git/hooks/post-commit") || $!{ENOENT});
ok(! system(@command, qw(--setup t/tmp/test.setup --rebuild --wrappers)));
}
sub check_cgi_mode_bits {
my $mode;
(undef, undef, $mode, undef, undef,
undef, undef, undef, undef, undef,
undef, undef, undef) = stat('t/tmp/ikiwiki.cgi');
is ($mode & 07777, 0751);
(undef, undef, $mode, undef, undef,
undef, undef, undef, undef, undef,
undef, undef, undef) = stat('t/tmp/in/.git/hooks/post-commit');
is ($mode & 07777, 0754);
}
sub run_cgi {
my (%args) = @_;
my ($in, $out);
my $method = $args{method} || 'GET';
my $environ = $args{environ} || {};
my $params = $args{params} || { do => 'prefs' };
my %defaults = (
SCRIPT_NAME => '/cgi-bin/ikiwiki.cgi',
HTTP_HOST => 'example.com',
);
my $cgi = CGI->new($args{params});
my $query_string = $cgi->query_string();
diag $query_string;
if ($method eq 'POST') {
$defaults{REQUEST_METHOD} = 'POST';
$in = $query_string;
$defaults{CONTENT_LENGTH} = length $in;
} else {
$defaults{REQUEST_METHOD} = 'GET';
$defaults{QUERY_STRING} = $query_string;
}
my %envvars = (
%defaults,
%$environ,
);
run(["./t/tmp/ikiwiki.cgi"], \$in, \$out, init => sub {
map {
$ENV{$_} = $envvars{$_}
} keys(%envvars);
});
return decode_utf8($out);
}
sub run_git {
my (undef, $filename, $line) = caller;
my $args = shift;
my $desc = shift || join(' ', 'git', @$args);
my ($in, $out);
ok(run(['git', @$args], \$in, \$out, init => sub {
chdir 't/tmp/in' or die $!;
my $name = 'The IkiWiki Tests';
my $email = 'nobody@ikiwiki-tests.invalid';
if ($args->[0] eq 'commit') {
$ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
$ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
}
}), "$desc at $filename:$line");
return $out;
}
sub test {
my $content;
my $status;
ok(! system(qw(rm -rf t/tmp)));
ok(! system(qw(mkdir t/tmp)));
write_old_file('.gitignore', 't/tmp/in', "/doc/.ikiwiki/\n");
write_old_file('doc/writable/one.mdwn', 't/tmp/in', 'This is the first test page');
write_old_file('doc/writable/two.mdwn', 't/tmp/in', 'This is the second test page');
write_old_file('doc/writable/three.mdwn', 't/tmp/in', 'This is the third test page');
write_old_file('doc/writable/three.bin', 't/tmp/in', 'An attachment');
write_old_file('doc/writable/blog.mdwn', 't/tmp/in',
'[[!inline pages="writable/blog/*" actions=yes rootpage=writable/blog postform=yes show=0]]');
write_old_file('doc/writable/__172__blog.mdwn', 't/tmp/in',
'[[!inline pages="writable/¬blog/*" actions=yes rootpage="writable/¬blog" postform=yes show=0]]');
write_old_file('doc/writable/中文.mdwn', 't/tmp/in',
'[[!inline pages="writable/中文/*" actions=yes rootpage="writable/中文" postform=yes show=0]]');
unless ($installed) {
ok(! system(qw(cp -pRL doc/wikiicons t/tmp/in/doc/)));
ok(! system(qw(cp -pRL doc/recentchanges.mdwn t/tmp/in/doc/)));
}
run_git(['init']);
run_git(['add', '.']);
run_git(['commit', '-m', 'Initial commit']);
write_setup_file();
thoroughly_rebuild();
check_cgi_mode_bits();
ok(-e 't/tmp/out/writable/one/index.html');
$content = readfile('t/tmp/out/writable/one/index.html');
like($content, qr{This is the first test page});
my $orig_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
# We have to wait 1 second here so that new writes are guaranteed
# to have a strictly larger mtime.
sleep 1;
# Test the git hook, which accepts git commits
writefile('doc/writable/one.mdwn', 't/tmp/in',
'This is new content for the first test page');
run_git(['add', '.']);
run_git(['commit', '-m', 'Git commit']);
my $first_revertable_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
isnt($orig_sha1, $first_revertable_sha1);
ok(-e 't/tmp/out/writable/one/index.html');
$content = readfile('t/tmp/out/writable/one/index.html');
like($content, qr{This is new content for the first test page});
# Test a web commit
$content = run_cgi(method => 'POST',
params => {
do => 'edit',
page => 'writable/two',
type => 'mdwn',
editmessage => 'Web commit',
editcontent => 'Here is new content for the second page',
_submit => 'Save Page',
_submitted => '1',
},
);
like($content, qr{^Status:\s*302\s}m);
like($content, qr{^Location:\s*http://example\.com/writable/two/\?updated}m);
my $second_revertable_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
isnt($orig_sha1, $second_revertable_sha1);
isnt($first_revertable_sha1, $second_revertable_sha1);
ok(-e 't/tmp/out/writable/two/index.html');
$content = readfile('t/tmp/out/writable/two/index.html');
like($content, qr{Here is new content for the second page});
# Another edit
writefile('doc/writable/three.mdwn', 't/tmp/in',
'Also new content for the third page');
unlink('t/tmp/in/doc/writable/three.bin');
writefile('doc/writable/three.bin', 't/tmp/in',
'Changed attachment');
run_git(['add', '.']);
run_git(['commit', '-m', 'Git commit']);
ok(-e 't/tmp/out/writable/three/index.html');
$content = readfile('t/tmp/out/writable/three/index.html');
like($content, qr{Also new content for the third page});
$content = readfile('t/tmp/out/writable/three.bin');
like($content, qr{Changed attachment});
my $third_revertable_sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
isnt($orig_sha1, $third_revertable_sha1);
isnt($second_revertable_sha1, $third_revertable_sha1);
run_git(['mv', 'doc/writable/one.mdwn', 'doc/one.mdwn']);
run_git(['mv', 'doc/writable/two.mdwn', 'two.mdwn']);
run_git(['commit', '-m', 'Rename files to test CVE-2016-10026']);
ok(! -e 't/tmp/out/writable/two/index.html');
ok(! -e 't/tmp/out/writable/one/index.html');
ok(-e 't/tmp/out/one/index.html');
my $sha1_before_revert = run_git(['rev-list', '--max-count=1', 'HEAD']);
isnt($sha1_before_revert, $third_revertable_sha1);
$content = run_cgi(method => 'post',
params => {
do => 'revert',
revertmessage => 'CVE-2016-10026',
rev => $first_revertable_sha1,
_submit => 'Revert',
_submitted_revert => '1',
},
);
like($content, qr{is locked and cannot be edited});
# The tree is left clean
run_git(['diff', '--exit-code']);
run_git(['diff', '--cached', '--exit-code']);
my $sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
is($sha1, $sha1_before_revert);
ok(-e 't/tmp/out/one/index.html');
ok(! -e 't/tmp/in/doc/writable/one.mdwn');
ok(-e 't/tmp/in/doc/one.mdwn');
$content = readfile('t/tmp/out/one/index.html');
like($content, qr{This is new content for the first test page});
$content = run_cgi(method => 'post',
params => {
do => 'revert',
revertmessage => 'CVE-2016-10026',
rev => $second_revertable_sha1,
_submit => 'Revert',
_submitted_revert => '1',
},
);
like($content, qr{you are not allowed to change two\.mdwn});
run_git(['diff', '--exit-code']);
run_git(['diff', '--cached', '--exit-code']);
$sha1 = run_git(['rev-list', '--max-count=1', 'HEAD']);
is($sha1, $sha1_before_revert);
ok(! -e 't/tmp/out/writable/two/index.html');
ok(! -e 't/tmp/out/two/index.html');
ok(! -e 't/tmp/in/doc/writable/two.mdwn');
ok(-e 't/tmp/in/two.mdwn');
$content = readfile('t/tmp/in/two.mdwn');
like($content, qr{Here is new content for the second page});
# We have to wait 1 second here so that new writes are guaranteed
# to have a strictly larger mtime.
sleep 1;
# This one can legitimately be reverted
$content = run_cgi(method => 'post',
params => {
do => 'revert',
revertmessage => 'not CVE-2016-10026',
rev => $third_revertable_sha1,
_submit => 'Revert',
_submitted_revert => '1',
},
);
like($content, qr{^Status:\s*302\s}m);
like($content, qr{^Location:\s*http://example\.com/recentchanges/}m);
run_git(['diff', '--exit-code']);
run_git(['diff', '--cached', '--exit-code']);
ok(-e 't/tmp/out/writable/three/index.html');
$content = readfile('t/tmp/out/writable/three/index.html');
like($content, qr{This is the third test page});
$content = readfile('t/tmp/out/writable/three.bin');
like($content, qr{An attachment});
$content = readfile('t/tmp/out/writable/blog/index.html');
like($content, qr{<input type="hidden" name="from" value="writable/blog"});
$content = run_cgi(method => 'get',
params => {
do => 'blog',
from => 'writable/blog',
subpage => '1',
title => 'hello',
},
);
like($content, qr{<option selected="selected" value="writable/blog/hello">writable/blog/hello</option>});
# Regression test for a bug in which we couldn't use an
# alphanumeric, but non-ASCII, root page.
$content = readfile('t/tmp/out/writable/中文/index.html');
like($content, qr{<input type="hidden" name="from" value="writable/中文"});
$content = run_cgi(method => 'get',
params => {
do => 'blog',
from => 'writable/中文',
subpage => '1',
title => 'hello',
},
);
like($content, qr{<option selected="selected" value="writable/中文/hello">writable/中文/hello</option>});
unlike($content, qr{Error: bad page name});
}
test();
done_testing();