]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/commitdiff
Merge branch 'debian-jessie' into debian-jessie-security
authorSimon McVittie <smcv@debian.org>
Wed, 11 Jan 2017 18:04:28 +0000 (18:04 +0000)
committerSimon McVittie <smcv@debian.org>
Wed, 11 Jan 2017 18:04:28 +0000 (18:04 +0000)
33 files changed:
IkiWiki/CGI.pm
IkiWiki/Plugin/attachment.pm
IkiWiki/Plugin/comments.pm
IkiWiki/Plugin/editpage.pm
IkiWiki/Plugin/git.pm
IkiWiki/Plugin/notifyemail.pm
IkiWiki/Plugin/passwordauth.pm
IkiWiki/Plugin/po.pm
IkiWiki/Plugin/rename.pm
debian/changelog
debian/control
debian/tests/control [new file with mode: 0644]
debian/tests/pkg-perl/smoke-env [new file with mode: 0644]
debian/tests/pkg-perl/syntax-skip [new file with mode: 0644]
debian/tests/pkg-perl/use-name [new file with mode: 0644]
ikiwiki-makerepo
t/basewiki_brokenlinks.t
t/comments.t
t/conflicts.t
t/cvs.t
t/git-cgi.t [new file with mode: 0755]
t/git.t
t/html.t
t/img.t
t/inline.t
t/passwordauth.t [new file with mode: 0755]
t/permalink.t
t/podcast.t
t/relativity.t
t/syntax.t
t/template_syntax.t
t/templates_documented.t
t/trail.t

index a6c0c271258b2f9dca1692c89ae3ce6b5d20631e..9c80c05c14ad16c91ab22f9ed9afb20989117a09 100644 (file)
@@ -307,8 +307,9 @@ sub cgi_prefs ($$) {
                return;
        }
        elsif ($form->submitted eq 'Save Preferences' && $form->validate) {
-               if (defined $form->field('email')) {
-                       userinfo_set($user_name, 'email', $form->field('email')) ||
+               my $email = $form->field('email');
+               if (defined $email) {
+                       userinfo_set($user_name, 'email', $email) ||
                                error("failed to set email");
                }
 
index 9bac96fc6e0fe59363200b90bc49dd39eccd8dcf..0d6f81c4f941f59f6532bf401f85fb79ade2bab9 100644 (file)
@@ -156,14 +156,15 @@ sub formbuilder (@) {
                        }
                        $add.="\n";
                }
+               my $content = $form->field('editcontent');
                $form->field(name => 'editcontent',
-                       value => $form->field('editcontent')."\n\n".$add,
+                       value => $content."\n\n".$add,
                        force => 1) if length $add;
        }
        
        # Generate the attachment list only after having added any new
        # attachments.
-       $form->tmpl_param("attachment_list" => [attachment_list($form->field('page'))]);
+       $form->tmpl_param("attachment_list" => [attachment_list(scalar $form->field('page'))]);
 }
 
 sub attachment_holding_location {
@@ -213,12 +214,12 @@ sub attachment_store {
        $filename=IkiWiki::basename($filename);
        $filename=~s/.*\\+(.+)/$1/; # hello, windows
        $filename=IkiWiki::possibly_foolish_untaint(linkpage($filename));
-       my $dest=attachment_holding_location($form->field('page'));
+       my $dest=attachment_holding_location(scalar $form->field('page'));
        
        # Check that the user is allowed to edit the attachment.
        my $final_filename=
                linkpage(IkiWiki::possibly_foolish_untaint(
-                       attachment_location($form->field('page')))).
+                       attachment_location(scalar $form->field('page')))).
                $filename;
        eval {
                if (IkiWiki::file_pruned($final_filename)) {
@@ -272,12 +273,12 @@ sub attachments_save {
 
        # Move attachments out of holding directory.
        my @attachments;
-       my $dir=attachment_holding_location($form->field('page'));
+       my $dir=attachment_holding_location(scalar $form->field('page'));
        foreach my $filename (glob("$dir/*")) {
                $filename=Encode::decode_utf8($filename);
                next unless -f $filename;
                my $destdir=linkpage(IkiWiki::possibly_foolish_untaint(
-                       attachment_location($form->field('page'))));
+                       attachment_location(scalar $form->field('page'))));
                my $absdestdir=$config{srcdir}."/".$destdir;
                my $destfile=IkiWiki::basename($filename);
                my $dest=$absdestdir.$destfile;
index c5177833f2430d28860e26bdeca1356c6b7a03eb..aa9b49c8c3da621b2734cfee9b7bfb044b297cde 100644 (file)
@@ -555,11 +555,12 @@ sub editcomment ($$) {
                }
                
                $postcomment=1;
-               my $ok=IkiWiki::check_content(content => $form->field('editcontent'),
-                       subject => $form->field('subject'),
+               my $ok=IkiWiki::check_content(
+                       content => scalar $form->field('editcontent'),
+                       subject => scalar $form->field('subject'),
                        $config{comments_allowauthor} ? (
-                               author => $form->field('author'),
-                               url => $form->field('url'),
+                               author => scalar $form->field('author'),
+                               url => scalar $form->field('url'),
                        ) : (),
                        page => $location,
                        cgi => $cgi,
@@ -599,7 +600,7 @@ sub editcomment ($$) {
                                length $form->field('subject')) {
                                $message = sprintf(
                                        gettext("Added a comment: %s"),
-                                       $form->field('subject'));
+                                       scalar $form->field('subject'));
                        }
 
                        IkiWiki::rcs_add($file);
index 78d0704c7fd3b699acbb65df391e475b29fae52b..fad7ecc5ac5f04a82aa368fc5755e2f105a2e212 100644 (file)
@@ -430,7 +430,7 @@ sub cgi_editpage ($$) {
                        $conflict=rcs_commit(
                                file => $file,
                                message => $message,
-                               token => $form->field("rcsinfo"),
+                               token => scalar $form->field("rcsinfo"),
                                session => $session,
                        );
                        enable_commit_hook();
index 641e397eb1a41e06d303909e6b8437070df0e23f..010d6d54c6dbb27c256b035c1b5f8a58df2c2d45 100644 (file)
@@ -5,6 +5,7 @@ use warnings;
 use strict;
 use IkiWiki;
 use Encode;
+use File::Path qw{remove_tree};
 use URI::Escape q{uri_escape_utf8};
 use open qw{:utf8 :std};
 
@@ -153,40 +154,65 @@ sub genwrapper {
        }
 }
 
-my $git_dir=undef;
-my $prefix=undef;
+# Loosely based on git-new-workdir from git contrib.
+sub create_temp_working_dir ($$) {
+       my $rootdir = shift;
+       my $branch = shift;
+       my $working = "$rootdir/.git/ikiwiki-temp-working";
+       remove_tree($working);
 
-sub in_git_dir ($$) {
-       $git_dir=shift;
-       my @ret=shift->();
-       $git_dir=undef;
-       $prefix=undef;
-       return @ret;
+       foreach my $dir ("", ".git") {
+               if (!mkdir("$working/$dir")) {
+                       error("Unable to create $working/$dir: $!");
+               }
+       }
+
+       # Hooks are deliberately not included: we will commit to the temporary
+       # branch that is used in the temporary working tree, and we don't want
+       # to run the post-commit hook there.
+       #
+       # logs/refs is not included because we don't use the reflog.
+       # remotes, rr-cache, svn are similarly excluded.
+       foreach my $link ("config", "refs", "objects", "info", "packed-refs") {
+               if (!symlink("../../$link", "$working/.git/$link")) {
+                       error("Unable to create symlink $working/.git/$link: $!");
+               }
+       }
+
+       open (my $out, '>', "$working/.git/HEAD") or
+               error("failed to write $working.git/HEAD: $!");
+       print $out "ref: refs/heads/$branch\n" or
+               error("failed to write $working.git/HEAD: $!");
+       close $out or
+               error("failed to write $working.git/HEAD: $!");
+       return $working;
 }
 
-sub safe_git (&@) {
+sub safe_git {
        # Start a child process safely without resorting to /bin/sh.
        # Returns command output (in list content) or success state
        # (in scalar context), or runs the specified data handler.
 
-       my ($error_handler, $data_handler, @cmdline) = @_;
+       my %params = @_;
 
        my $pid = open my $OUT, "-|";
 
+       error("Working directory not specified") unless defined $params{chdir};
        error("Cannot fork: $!") if !defined $pid;
 
        if (!$pid) {
                # In child.
                # Git commands want to be in wc.
-               if (! defined $git_dir) {
-                       chdir $config{srcdir}
-                           or error("cannot chdir to $config{srcdir}: $!");
+               if ($params{chdir} ne '.') {
+                       chdir $params{chdir}
+                           or error("cannot chdir to $params{chdir}: $!");
                }
-               else {
-                       chdir $git_dir
-                           or error("cannot chdir to $git_dir: $!");
+
+               if ($params{stdout}) {
+                       open(STDOUT, '>&', $params{stdout}) or error("Cannot reopen stdout: $!");
                }
-               exec @cmdline or error("Cannot exec '@cmdline': $!");
+
+               exec @{$params{cmdline}} or error("Cannot exec '@{$params{cmdline}}': $!");
        }
        # In parent.
 
@@ -201,25 +227,51 @@ sub safe_git (&@) {
 
                chomp;
 
-               if (! defined $data_handler) {
+               if (! defined $params{data_handler}) {
                        push @lines, $_;
                }
                else {
-                       last unless $data_handler->($_);
+                       last unless $params{data_handler}->($_);
                }
        }
 
        close $OUT;
 
-       $error_handler->("'@cmdline' failed: $!") if $? && $error_handler;
+       $params{error_handler}->("'@{$params{cmdline}}' failed: $!") if $? && $params{error_handler};
 
        return wantarray ? @lines : ($? == 0);
 }
 # Convenient wrappers.
-sub run_or_die ($@) { safe_git(\&error, undef, @_) }
-sub run_or_cry ($@) { safe_git(sub { warn @_ }, undef, @_) }
-sub run_or_non ($@) { safe_git(undef, undef, @_) }
+sub run_or_die_in ($$@) {
+       my $dir = shift;
+       safe_git(chdir => $dir, error_handler => \&error, cmdline => \@_);
+}
+sub run_or_cry_in ($$@) {
+       my $dir = shift;
+       safe_git(chdir => $dir, error_handler => sub { warn @_ }, cmdline => \@_);
+}
+sub run_or_non_in ($$@) {
+       my $dir = shift;
+       safe_git(chdir => $dir, cmdline => \@_);
+}
+
+sub ensure_committer ($) {
+       my $dir = shift;
+
+       if (! length $ENV{GIT_AUTHOR_NAME} || ! length $ENV{GIT_COMMITTER_NAME}) {
+               my $name = join('', run_or_non_in($dir, "git", "config", "user.name"));
+               if (! length $name) {
+                       run_or_die_in($dir, "git", "config", "user.name", "IkiWiki");
+               }
+       }
 
+       if (! length $ENV{GIT_AUTHOR_EMAIL} || ! length $ENV{GIT_COMMITTER_EMAIL}) {
+               my $email = join('', run_or_non_in($dir, "git", "config", "user.email"));
+               if (! length $email) {
+                       run_or_die_in($dir, "git", "config", "user.email", "ikiwiki.info");
+               }
+       }
+}
 
 sub merge_past ($$$) {
        # Unlike with Subversion, Git cannot make a 'svn merge -rN:M file'.
@@ -258,6 +310,8 @@ sub merge_past ($$$) {
        my @undo;      # undo stack for cleanup in case of an error
        my $conflict;  # file content with conflict markers
 
+       ensure_committer($config{srcdir});
+
        eval {
                # Hide local changes from Git by renaming the modified file.
                # Relative paths must be converted to absolute for renaming.
@@ -276,30 +330,30 @@ sub merge_past ($$$) {
                my $branch = "throw_away_${sha1}"; # supposed to be unique
 
                # Create a throw-away branch and rewind backward.
-               push @undo, sub { run_or_cry('git', 'branch', '-D', $branch) };
-               run_or_die('git', 'branch', $branch, $sha1);
+               push @undo, sub { run_or_cry_in($config{srcdir}, 'git', 'branch', '-D', $branch) };
+               run_or_die_in($config{srcdir}, 'git', 'branch', $branch, $sha1);
 
                # Switch to throw-away branch for the merge operation.
                push @undo, sub {
-                       if (!run_or_cry('git', 'checkout', $config{gitmaster_branch})) {
-                               run_or_cry('git', 'checkout','-f',$config{gitmaster_branch});
+                       if (!run_or_cry_in($config{srcdir}, 'git', 'checkout', $config{gitmaster_branch})) {
+                               run_or_cry_in($config{srcdir}, 'git', 'checkout','-f',$config{gitmaster_branch});
                        }
                };
-               run_or_die('git', 'checkout', $branch);
+               run_or_die_in($config{srcdir}, 'git', 'checkout', $branch);
 
                # Put the modified file in _this_ branch.
                rename($hidden, $target)
                    or error("rename '$hidden' to '$target' failed: $!");
 
                # _Silently_ commit all modifications in the current branch.
-               run_or_non('git', 'commit', '-m', $message, '-a');
+               run_or_non_in($config{srcdir}, 'git', 'commit', '-m', $message, '-a');
                # ... and re-switch to master.
-               run_or_die('git', 'checkout', $config{gitmaster_branch});
+               run_or_die_in($config{srcdir}, 'git', 'checkout', $config{gitmaster_branch});
 
                # Attempt to merge without complaining.
-               if (!run_or_non('git', 'pull', '--no-commit', '.', $branch)) {
+               if (!run_or_non_in($config{srcdir}, 'git', 'pull', '--no-commit', '.', $branch)) {
                        $conflict = readfile($target);
-                       run_or_die('git', 'reset', '--hard');
+                       run_or_die_in($config{srcdir}, 'git', 'reset', '--hard');
                }
        };
        my $failure = $@;
@@ -315,7 +369,11 @@ sub merge_past ($$$) {
        return $conflict;
 }
 
-sub decode_git_file ($) {
+{
+my %prefix_cache;
+
+sub decode_git_file ($$) {
+       my $dir=shift;
        my $file=shift;
 
        # git does not output utf-8 filenames, but instead
@@ -326,20 +384,22 @@ sub decode_git_file ($) {
        }
 
        # strip prefix if in a subdir
-       if (! defined $prefix) {
-               ($prefix) = run_or_die('git', 'rev-parse', '--show-prefix');
-               if (! defined $prefix) {
-                       $prefix="";
+       if (! defined $prefix_cache{$dir}) {
+               ($prefix_cache{$dir}) = run_or_die_in($dir, 'git', 'rev-parse', '--show-prefix');
+               if (! defined $prefix_cache{$dir}) {
+                       $prefix_cache{$dir}="";
                }
        }
-       $file =~ s/^\Q$prefix\E//;
+       $file =~ s/^\Q$prefix_cache{$dir}\E//;
 
        return decode("utf8", $file);
 }
+}
 
-sub parse_diff_tree ($) {
+sub parse_diff_tree ($$) {
        # Parse the raw diff tree chunk and return the info hash.
        # See git-diff-tree(1) for the syntax.
+       my $dir = shift;
        my $dt_ref = shift;
 
        # End of stream?
@@ -408,6 +468,17 @@ sub parse_diff_tree ($) {
        }
        shift @{ $dt_ref } if $dt_ref->[0] =~ /^$/;
 
+       $ci{details} = [parse_changed_files($dir, $dt_ref)];
+
+       return \%ci;
+}
+
+sub parse_changed_files ($$) {
+       my $dir = shift;
+       my $dt_ref = shift;
+
+       my @files;
+
        # Modified files.
        while (my $line = shift @{ $dt_ref }) {
                if ($line =~ m{^
@@ -425,8 +496,8 @@ sub parse_diff_tree ($) {
                        my $status = shift(@tmp);
 
                        if (length $file) {
-                               push @{ $ci{'details'} }, {
-                                       'file'      => decode_git_file($file),
+                               push @files, {
+                                       'file'      => decode_git_file($dir, $file),
                                        'sha1_from' => $sha1_from[0],
                                        'sha1_to'   => $sha1_to,
                                        'mode_from' => $mode_from[0],
@@ -439,23 +510,23 @@ sub parse_diff_tree ($) {
                last;
        }
 
-       return \%ci;
+       return @files;
 }
 
-sub git_commit_info ($;$) {
+sub git_commit_info ($$;$) {
        # Return an array of commit info hashes of num commits
        # starting from the given sha1sum.
-       my ($sha1, $num) = @_;
+       my ($dir, $sha1, $num) = @_;
 
        my @opts;
        push @opts, "--max-count=$num" if defined $num;
 
-       my @raw_lines = run_or_die('git', 'log', @opts,
+       my @raw_lines = run_or_die_in($dir, 'git', 'log', @opts,
                '--pretty=raw', '--raw', '--abbrev=40', '--always', '-c',
-               '-r', $sha1, '--', '.');
+               '-r', $sha1, '--no-renames', '--', '.');
 
        my @ci;
-       while (my $parsed = parse_diff_tree(\@raw_lines)) {
+       while (my $parsed = parse_diff_tree($dir, \@raw_lines)) {
                push @ci, $parsed;
        }
 
@@ -472,7 +543,7 @@ sub rcs_find_changes ($) {
        # merge commit where some files were not really added.
        # This is why the code below verifies that the files really
        # exist.
-       my @raw_lines = run_or_die('git', 'log',
+       my @raw_lines = run_or_die_in($config{srcdir}, 'git', 'log',
                '--pretty=raw', '--raw', '--abbrev=40', '--always', '-c',
                '--no-renames', , '--reverse',
                '-r', "$oldrev..HEAD", '--', '.');
@@ -482,7 +553,7 @@ sub rcs_find_changes ($) {
        my %deleted;
        my $nullsha = 0 x 40;
        my $newrev=$oldrev;
-       while (my $ci = parse_diff_tree(\@raw_lines)) {
+       while (my $ci = parse_diff_tree($config{srcdir}, \@raw_lines)) {
                $newrev=$ci->{sha1};
                foreach my $i (@{$ci->{details}}) {
                        my $file=$i->{file};
@@ -504,14 +575,16 @@ sub rcs_find_changes ($) {
        return (\%changed, \%deleted, $newrev);
 }
 
-sub git_sha1_file ($) {
+sub git_sha1_file ($$) {
+       my $dir=shift;
        my $file=shift;
-       git_sha1("--", $file);
+       return git_sha1($dir, $file);
 }
 
-sub git_sha1 (@) {
+sub git_sha1 ($@) {
+       my $dir = shift;
        # Ignore error since a non-existing file might be given.
-       my ($sha1) = run_or_non('git', 'rev-list', '--max-count=1', 'HEAD',
+       my ($sha1) = run_or_non_in($dir, 'git', 'rev-list', '--max-count=1', 'HEAD',
                '--', @_);
        if (defined $sha1) {
                ($sha1) = $sha1 =~ m/($sha1_pattern)/; # sha1 is untainted now
@@ -520,14 +593,15 @@ sub git_sha1 (@) {
 }
 
 sub rcs_get_current_rev () {
-       git_sha1();
+       return git_sha1($config{srcdir});
 }
 
 sub rcs_update () {
        # Update working directory.
+       ensure_committer($config{srcdir});
 
        if (length $config{gitorigin_branch}) {
-               run_or_cry('git', 'pull', '--prune', $config{gitorigin_branch});
+               run_or_cry_in($config{srcdir}, 'git', 'pull', '--prune', $config{gitorigin_branch});
        }
 }
 
@@ -536,7 +610,7 @@ sub rcs_prepedit ($) {
        # This will be later used in rcs_commit if a merge is required.
        my ($file) = @_;
 
-       return git_sha1_file($file);
+       return git_sha1_file($config{srcdir}, $file);
 }
 
 sub rcs_commit (@) {
@@ -547,8 +621,11 @@ sub rcs_commit (@) {
 
        # Check to see if the page has been changed by someone else since
        # rcs_prepedit was called.
-       my $cur    = git_sha1_file($params{file});
-       my ($prev) = $params{token} =~ /^($sha1_pattern)$/; # untaint
+       my $cur = git_sha1_file($config{srcdir}, $params{file});
+       my $prev;
+       if (defined $params{token}) {
+               ($prev) = $params{token} =~ /^($sha1_pattern)$/; # untaint
+       }
 
        if (defined $cur && defined $prev && $cur ne $prev) {
                my $conflict = merge_past($prev, $params{file}, $dummy_commit_msg);
@@ -578,20 +655,28 @@ sub rcs_commit_helper (@) {
                elsif (defined $params{session}->remote_addr()) {
                        $u=$params{session}->remote_addr();
                }
-               if (defined $u) {
+               if (length $u) {
                        $u=encode_utf8($u);
                        $ENV{GIT_AUTHOR_NAME}=$u;
                }
+               else {
+                       $u = 'anonymous';
+               }
                if (defined $params{session}->param("nickname")) {
                        $u=encode_utf8($params{session}->param("nickname"));
                        $u=~s/\s+/_/g;
                        $u=~s/[^-_0-9[:alnum:]]+//g;
                }
-               if (defined $u) {
+               if (length $u) {
                        $ENV{GIT_AUTHOR_EMAIL}="$u\@web";
                }
+               else {
+                       $ENV{GIT_AUTHOR_EMAIL}='anonymous@web';
+               }
        }
 
+       ensure_committer($config{srcdir});
+
        $params{message} = IkiWiki::possibly_foolish_untaint($params{message});
        my @opts;
        if ($params{message} !~ /\S/) {
@@ -615,10 +700,10 @@ sub rcs_commit_helper (@) {
                push @opts, '--', $params{file};
        }
        # git commit returns non-zero if nothing really changed.
-       # So we should ignore its exit status (hence run_or_non).
-       if (run_or_non('git', 'commit', '-m', $params{message}, '-q', @opts)) {
+       # So we should ignore its exit status (hence run_or_non_in).
+       if (run_or_non_in($config{srcdir}, 'git', 'commit', '-m', $params{message}, '-q', @opts)) {
                if (length $config{gitorigin_branch}) {
-                       run_or_cry('git', 'push', $config{gitorigin_branch}, $config{gitmaster_branch});
+                       run_or_cry_in($config{srcdir}, 'git', 'push', $config{gitorigin_branch}, $config{gitmaster_branch});
                }
        }
        
@@ -631,7 +716,8 @@ sub rcs_add ($) {
 
        my ($file) = @_;
 
-       run_or_cry('git', 'add', $file);
+       ensure_committer($config{srcdir});
+       run_or_cry_in($config{srcdir}, 'git', 'add', '--', $file);
 }
 
 sub rcs_remove ($) {
@@ -639,13 +725,15 @@ sub rcs_remove ($) {
 
        my ($file) = @_;
 
-       run_or_cry('git', 'rm', '-f', $file);
+       ensure_committer($config{srcdir});
+       run_or_cry_in($config{srcdir}, 'git', 'rm', '-f', '--', $file);
 }
 
 sub rcs_rename ($$) {
        my ($src, $dest) = @_;
 
-       run_or_cry('git', 'mv', '-f', $src, $dest);
+       ensure_committer($config{srcdir});
+       run_or_cry_in($config{srcdir}, 'git', 'mv', '-f', '--', $src, $dest);
 }
 
 sub rcs_recentchanges ($) {
@@ -657,7 +745,7 @@ sub rcs_recentchanges ($) {
        error($@) if $@;
 
        my @rets;
-       foreach my $ci (git_commit_info('HEAD', $num || 1)) {
+       foreach my $ci (git_commit_info($config{srcdir}, 'HEAD', $num || 1)) {
                # Skip redundant commits.
                next if ($ci->{'comment'} && @{$ci->{'comment'}}[0] eq $dummy_commit_msg);
 
@@ -743,7 +831,12 @@ sub rcs_diff ($;$) {
                        if (@lines || $line=~/^diff --git/);
                return 1;
        };
-       safe_git(undef, $addlines, "git", "show", $sha1);
+       safe_git(
+               chdir => $config{srcdir},
+               error_handler => undef,
+               data_handler => $addlines,
+               cmdline => ["git", "show", $sha1],
+       );
        if (wantarray) {
                return @lines;
        }
@@ -761,7 +854,7 @@ sub findtimes ($$) {
 
        if (! keys %time_cache) {
                my $date;
-               foreach my $line (run_or_die('git', 'log',
+               foreach my $line (run_or_die_in($config{srcdir}, 'git', 'log',
                                '--pretty=format:%at',
                                '--name-only', '--relative')) {
                        if (! defined $date && $line =~ /^(\d+)$/) {
@@ -771,7 +864,7 @@ sub findtimes ($$) {
                                $date=undef;
                        }
                        else {
-                               my $f=decode_git_file($line);
+                               my $f=decode_git_file($config{srcdir}, $line);
 
                                if (! $time_cache{$f}) {
                                        $time_cache{$f}[0]=$date; # mtime
@@ -823,7 +916,8 @@ sub git_find_root {
 
 }
 
-sub git_parse_changes {
+sub git_parse_changes ($$@) {
+       my $dir = shift;
        my $reverted = shift;
        my @changes = @_;
 
@@ -873,11 +967,12 @@ sub git_parse_changes {
                                die $@ if $@;
                                my $fh;
                                ($fh, $path)=File::Temp::tempfile(undef, UNLINK => 1);
-                               my $cmd = "cd $git_dir && ".
-                                         "git show $detail->{sha1_to} > '$path'";
-                               if (system($cmd) != 0) {
-                                       error("failed writing temp file '$path'.");
-                               }
+                               safe_git(
+                                       chdir => $dir,
+                                       error_handler => sub { error("failed writing temp file '$path': ".shift."."); },
+                                       stdout => $fh,
+                                       cmdline => ['git', 'show', $detail->{sha1_to}],
+                               );
                        }
 
                        push @rets, {
@@ -907,9 +1002,7 @@ sub rcs_receive () {
                # (Also, if a subdir is involved, we don't want to chdir to
                # it and only see changes in it.)
                # The pre-receive hook already puts us in the right place.
-               in_git_dir(".", sub {
-                       push @rets, git_parse_changes(0, git_commit_info($oldrev."..".$newrev));
-               });
+               push @rets, git_parse_changes('.', 0, git_commit_info('.', $oldrev."..".$newrev));
        }
 
        return reverse @rets;
@@ -919,12 +1012,17 @@ sub rcs_preprevert ($) {
        my $rev=shift;
        my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint
 
+       my @undo;      # undo stack for cleanup in case of an error
+
        # Examine changes from root of git repo, not from any subdir,
        # in order to see all changes.
        my ($subdir, $rootdir) = git_find_root();
-       in_git_dir($rootdir, sub {
-               my @commits=git_commit_info($sha1, 1);
-       
+       ensure_committer($rootdir);
+
+       # preserve indentation of previous in_git_dir code for now
+       do {
+               my @commits=git_commit_info($rootdir, $sha1, 1);
+
                if (! @commits) {
                        error "unknown commit"; # just in case
                }
@@ -935,8 +1033,60 @@ sub rcs_preprevert ($) {
                        error gettext("you are not allowed to revert a merge");
                }
 
-               git_parse_changes(1, @commits);
-       });
+               # Due to the presence of rename-detection, we cannot actually
+               # see what will happen in a revert without trying it.
+               # But we can guess, which is enough to rule out most changes
+               # that we won't allow reverting.
+               git_parse_changes($rootdir, 1, @commits);
+
+               my $failure;
+               my @ret;
+               eval {
+                       my $branch = "ikiwiki_revert_${sha1}"; # supposed to be unique
+
+                       push @undo, sub {
+                               run_or_cry_in($rootdir, 'git', 'branch', '-D', $branch) if $failure;
+                       };
+                       if (run_or_non_in($rootdir, 'git', 'rev-parse', '--quiet', '--verify', $branch)) {
+                               run_or_non_in($rootdir, 'git', 'branch', '-D', $branch);
+                       }
+                       run_or_die_in($rootdir, 'git', 'branch', $branch, $config{gitmaster_branch});
+
+                       my $working = create_temp_working_dir($rootdir, $branch);
+
+                       push @undo, sub {
+                               remove_tree($working);
+                       };
+
+                       run_or_die_in($working, 'git', 'checkout', '--quiet', '--force', $branch);
+                       run_or_die_in($working, 'git', 'revert', '--no-commit', $sha1);
+                       run_or_die_in($working, 'git', 'commit', '-m', "revert $sha1", '-a');
+
+                       my @raw_lines;
+                       @raw_lines = run_or_die_in($rootdir, 'git', 'diff', '--pretty=raw',
+                               '--raw', '--abbrev=40', '--always', '--no-renames',
+                               "..${branch}");
+
+                       my $ci = {
+                               details => [parse_changed_files($rootdir, \@raw_lines)],
+                       };
+
+                       @ret = git_parse_changes($rootdir, 0, $ci);
+               };
+               $failure = $@;
+
+               # Process undo stack (in reverse order).  By policy cleanup
+               # actions should normally print a warning on failure.
+               while (my $handle = pop @undo) {
+                       $handle->();
+               }
+
+               if ($failure) {
+                       my $message = sprintf(gettext("Failed to revert commit %s"), $sha1);
+                       error("$message\n$failure\n");
+               }
+               return @ret;
+       };
 }
 
 sub rcs_revert ($) {
@@ -944,13 +1094,13 @@ sub rcs_revert ($) {
        my $rev = shift;
        my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint
 
-       if (run_or_non('git', 'revert', '--strategy=recursive',
-                       '--strategy-option=no-renames',
-                       '--no-commit', $sha1)) {
+       ensure_committer($config{srcdir});
+
+       if (run_or_non_in($config{srcdir}, 'git', 'cherry-pick', '--no-commit', "ikiwiki_revert_$sha1")) {
                return undef;
        }
        else {
-               run_or_die('git', 'reset', '--hard');
+               run_or_non_in($config{srcdir}, 'git', 'branch', '-D', "ikiwiki_revert_$sha1");
                return sprintf(gettext("Failed to revert commit %s"), $sha1);
        }
 }
index b50a22a00c2e0bc6179a21b22b987d8241282afb..079bb10d4fb7eda121916976ddd0945934a4557b 100644 (file)
@@ -34,7 +34,7 @@ sub formbuilder (@) {
        }
        elsif ($form->submitted eq "Save Preferences" && $form->validate &&
               defined $form->field("subscriptions")) {
-               setsubscriptions($username, $form->field('subscriptions'));
+               setsubscriptions($username, scalar $form->field('subscriptions'));
        }
 }
 
index 0cf2a26ea99380b6dd395824945bfcebd42c33b9..84961c51fd2be865b9b02447cdf839d8c6b8f9f8 100644 (file)
@@ -231,7 +231,7 @@ sub formbuilder_setup (@) {
                                $form->field(
                                        name => "password",
                                        validate => sub {
-                                               checkpassword($form->field("name"), shift);
+                                               checkpassword(scalar $form->field("name"), shift);
                                        },
                                );
                        }
@@ -319,16 +319,20 @@ sub formbuilder (@) {
 
        if ($form->title eq "signin" || $form->title eq "register") {
                if (($form->submitted && $form->validate) || $do_register) {
+                       my $user_name = $form->field('name');
+
                        if ($form->submitted eq 'Login') {
-                               $session->param("name", $form->field("name"));
+                               $session->param("name", $user_name);
                                IkiWiki::cgi_postsignin($cgi, $session);
                        }
                        elsif ($form->submitted eq 'Create Account') {
-                               my $user_name=$form->field('name');
+                               my $email = $form->field('email');
+                               my $password = $form->field('password');
+
                                if (IkiWiki::userinfo_setall($user_name, {
-                                       'email' => $form->field('email'),
+                                       'email' => $email,
                                        'regdate' => time})) {
-                                       setpassword($user_name, $form->field('password'));
+                                       setpassword($user_name, $password);
                                        $form->field(name => "confirm_password", type => "hidden");
                                        $form->field(name => "email", type => "hidden");
                                        $form->text(gettext("Account creation successful. Now you can Login."));
@@ -338,7 +342,6 @@ sub formbuilder (@) {
                                }
                        }
                        elsif ($form->submitted eq 'Reset Password') {
-                               my $user_name=$form->field("name");
                                my $email=IkiWiki::userinfo_get($user_name, "email");
                                if (! length $email) {
                                        error(gettext("No email address, so cannot email password reset instructions."));
@@ -388,8 +391,9 @@ sub formbuilder (@) {
        elsif ($form->title eq "preferences") {
                if ($form->submitted eq "Save Preferences" && $form->validate) {
                        my $user_name=$form->field('name');
-                       if (defined $form->field("password") && length $form->field("password")) {
-                               setpassword($user_name, $form->field('password'));
+                       my $password=$form->field('password');
+                       if (defined $password && length $password) {
+                               setpassword($user_name, $password);
                        }
                }
        }
index 6107a4a2252c256e715a3b0bd210eb28d45371b3..1528f235ff82282022332af0f55c4664b06bf013 100644 (file)
@@ -548,7 +548,7 @@ sub formbuilder_setup (@) {
                # their buttons, which is why this hook must be run last.
                # The canrename/canremove hooks already ensure this is forbidden
                # at the backend level, so this is only UI sugar.
-               if (istranslation($form->field("page"))) {
+               if (istranslation(scalar $form->field("page"))) {
                        map {
                                for (my $i = 0; $i < @{$params{buttons}}; $i++) {
                                        if (@{$params{buttons}}[$i] eq $_) {
index 6d56340b896519e921e9b6c7d8a06ffd1d56fe2a..2456c22cb12af55f273ad32e820e728db6b2e7c7 100644 (file)
@@ -258,7 +258,7 @@ sub formbuilder (@) {
                my $session=$params{session};
 
                if ($form->submitted eq "Rename" && $form->field("do") eq "edit") {
-                       rename_start($q, $session, 0, $form->field("page"));
+                       rename_start($q, $session, 0, scalar $form->field("page"));
                }
                elsif ($form->submitted eq "Rename Attachment") {
                        my @selected=map { Encode::decode_utf8($_) } $q->param("attachment_select");
@@ -311,7 +311,7 @@ sub sessioncgi ($$) {
                        # performed in check_canrename later.
                        my $srcfile=IkiWiki::possibly_foolish_untaint($pagesources{$src})
                                if exists $pagesources{$src};
-                       my $dest=IkiWiki::possibly_foolish_untaint(titlepage($form->field("new_name")));
+                       my $dest=IkiWiki::possibly_foolish_untaint(titlepage(scalar $form->field("new_name")));
                        my $destfile=$dest;
                        if (! $q->param("attachment")) {
                                my $type=$q->param('type');
index 3e7c3e91746c6aaaa9676427c70c33abe5914209..229d44e27baccbdbd18d729245f7c0226a0318a6 100644 (file)
@@ -1,18 +1,57 @@
-ikiwiki (3.20141016.4) UNRELEASED; urgency=medium
-
-  [ Amitai Schlair ]
-  * img: ignore the case of the extension when detecting image format,
-    fixing the regression that *.JPG etc. would not be displayed
-    since 3.20141016.3
-
-  [ Simon McVittie ]
-  * Add CVE-2016-4561 reference to 3.20141016.3 changelog
-  * Security: tell `git revert` not to follow renames. If it does, then
-    renaming a file can result in a revert writing outside the wiki srcdir
-    or altering a file that the reverting user should not be able to alter,
-    an authorization bypass. Thanks, intrigeri. (CVE-2016-10026)
-
- -- Simon McVittie <smcv@debian.org>  Mon, 09 May 2016 22:35:16 +0100
+ikiwiki (3.20141016.4) UNRELEASED; urgency=high
+
+  * Reference CVE-2016-4561 in 3.20141016.3 changelog
+  * Security: force CGI::FormBuilder->field to scalar context where
+    necessary, avoiding unintended function argument injection
+    analogous to CVE-2014-1572.
+    - passwordauth: prevent authentication bypass via multiple name
+      parameters (OVE-20170111-0001)
+    - passwordauth: prevent userinfo forgery via repeated email
+      parameter (OVE-20170111-0001)
+    - comments, editpage: prevent commit metadata forgery
+      (CVE-2016-9646, OVE-20161226-0001)
+    - CGI, attachment, comments, editpage, notifyemail, passwordauth,
+      po, rename: harden against similar issues that are not believed
+      to be exploitable
+  * t/passwordauth.t: new automated test for OVE-20170111-0001
+  * Backport IkiWiki::Plugin::git from 3.20170110 to fix the following
+    bugs, including one minor security vulnerability:
+    - Security: try revert operations before approving them. Previously,
+      automatic rename detection could result in a revert writing outside
+      the wiki srcdir or altering a file that the reverting user should not
+      be able to alter, an authorization bypass.
+      (CVE-2016-10026 represents the original vulnerability.)
+      The incomplete fix released in 3.20161219 was not effective for git
+      versions prior to 2.8.0rc0.
+      (CVE-2016-9645 represents that incomplete solution. Debian stable
+      was never vulnerable to this one.)
+    - Fix the warnings "cannot chdir to .../ikiwiki-temp-working: No such
+      file or directory" seen in the initial fixes for those security issues
+    - If no committer identity is known, set it to
+      "IkiWiki <ikiwiki.info>" in .git/config. This resolves commit errors
+      in versions of git that require a non-trivial committer identity.
+    - Use git log --no-renames to generate recentchanges, fixing the git
+      test-case with git 2.9 (Closes: #835612)
+    - Don't issue a warning if the rcsinfo CGI parameter is undefined
+    - Do not fail to commit changes with a recent git version
+      and an anonymous committer
+    - Do not fail on filenames starting with a dash
+      (patch from Florian Wagner)
+    - Don't add a redundant "--" and run "git rev-list ... -- -- ..."
+  * Backport t/git-cgi.t from 3.20170110 to have automated test coverage
+    for using the CGI with git, including tests for CVE-2016-10026
+     - Build-depend on libipc-run-perl for better build-time test coverage
+  * Backport IkiWiki::Plugin::img from 3.20160905 to fix a regression
+    in 3.20141016.3:
+    - img: ignore the case of the extension when detecting image format,
+      fixing the regression that *.JPG etc. would not be displayed
+      (patch from Amitai Schleier)
+  * Backport tests' installed-test (autopkgtest) support from 3.20160121,
+    adjusted for compatibility with the older pkg-perl-autopkgtest in jessie
+    - d/control: add enough build-dependencies to run all tests, except for
+      non-git VCSs
+
+ -- Simon McVittie <smcv@debian.org>  Wed, 11 Jan 2017 15:22:38 +0000
 
 ikiwiki (3.20141016.3) jessie-security; urgency=high
 
index 68f543a2445437bb8aba8b465c6926303e84281d..d2011bf4e732ca40b17751dcaa5a562c7c202a96 100644 (file)
@@ -3,6 +3,7 @@ Section: web
 Priority: optional
 Build-Depends: perl, debhelper (>= 9)
 Build-Depends-Indep: dpkg-dev (>= 1.9.0), libxml-simple-perl,
+  git (>= 1:1.7),
   libtext-markdown-discount-perl,
   libtimedate-perl, libhtml-template-perl,
   libhtml-scrubber-perl, wdg-html-validator,
@@ -10,12 +11,19 @@ Build-Depends-Indep: dpkg-dev (>= 1.9.0), libxml-simple-perl,
   libfile-chdir-perl, libyaml-libyaml-perl, librpc-xml-perl,
   libcgi-pm-perl, libcgi-session-perl, ghostscript,
   libmagickcore-extra,
-  libcgi-formbuilder-perl
+  libcgi-formbuilder-perl,
+  libfile-mimeinfo-perl,
+  libipc-run-perl,
+  libnet-openid-consumer-perl,
+  libxml-feed-perl,
+  libxml-parser-perl,
+  libxml-twig-perl
 Maintainer: Simon McVittie <smcv@debian.org>
 Uploaders: Josh Triplett <josh@freedesktop.org>
 Standards-Version: 3.9.5
 Homepage: http://ikiwiki.info/
 Vcs-Git: git://git.ikiwiki.info/
+Testsuite: autopkgtest-pkg-perl
 
 Package: ikiwiki
 Architecture: all
diff --git a/debian/tests/control b/debian/tests/control
new file mode 100644 (file)
index 0000000..8b0ce72
--- /dev/null
@@ -0,0 +1,12 @@
+Test-Command: env INSTALLED_TESTS=1 /usr/share/pkg-perl-autopkgtest/runner build-deps
+Depends: @, @builddeps@, pkg-perl-autopkgtest
+
+Test-Command: env INSTALLED_TESTS=1 /usr/share/pkg-perl-autopkgtest/runner runtime-deps
+Depends: @, pkg-perl-autopkgtest
+
+Test-Command: env INSTALLED_TESTS=1 /usr/share/pkg-perl-autopkgtest/runner runtime-deps-and-recommends
+Depends: @, pkg-perl-autopkgtest
+Restrictions: needs-recommends
+
+Test-Command: env INSTALLED_TESTS=1 /usr/share/pkg-perl-autopkgtest/runner heavy-deps
+Depends: @, pkg-perl-autopkgtest, pkg-perl-autopkgtest-heavy
diff --git a/debian/tests/pkg-perl/smoke-env b/debian/tests/pkg-perl/smoke-env
new file mode 100644 (file)
index 0000000..7747381
--- /dev/null
@@ -0,0 +1 @@
+INSTALLED_TESTS=1
diff --git a/debian/tests/pkg-perl/syntax-skip b/debian/tests/pkg-perl/syntax-skip
new file mode 100644 (file)
index 0000000..404e431
--- /dev/null
@@ -0,0 +1,4 @@
+IkiWiki/Plugin/amazon_s3.pm
+IkiWiki/Plugin/cvs.pm
+IkiWiki/Plugin/monotone.pm
+IkiWiki/Plugin/po.pm
diff --git a/debian/tests/pkg-perl/use-name b/debian/tests/pkg-perl/use-name
new file mode 100644 (file)
index 0000000..3d60011
--- /dev/null
@@ -0,0 +1 @@
+IkiWiki
index c3a13c214330e32fe9473f6891608b6b53872f85..f1c44067ec77648b84d3ed954c61e5560a003f6f 100755 (executable)
@@ -85,6 +85,12 @@ git)
 
        cd "$srcdir"
        git init
+       if [ -z "$(git config user.name)" ]; then
+               git config user.name IkiWiki
+       fi
+       if [ -z "$(git config user.email)" ]; then
+               git config user.email ikiwiki.info
+       fi
        echo /.ikiwiki > .gitignore
        git add .
        git commit -m "initial commit"
index 74ddc61c5a8b636a80b5ed24c293ce2c3582c4de..26e3859abedc37b39c70a2bcf40c505751f73455 100755 (executable)
@@ -1,21 +1,32 @@
 #!/usr/bin/perl
 use warnings;
 use strict;
-use Test::More 'no_plan';
+use Test::More;
+
+my $installed = $ENV{INSTALLED_TESTS};
 
 ok(! system("rm -rf t/tmp; mkdir t/tmp"));
-ok(! system("make -s ikiwiki.out"));
-ok(! system("make underlay_install DESTDIR=`pwd`/t/tmp/install PREFIX=/usr >/dev/null"));
+
+my @command;
+if ($installed) {
+       @command = qw(env LC_ALL=C ikiwiki);
+}
+else {
+       ok(! system("make -s ikiwiki.out"));
+       ok(! system("make underlay_install DESTDIR=`pwd`/t/tmp/install PREFIX=/usr >/dev/null"));
+       @command = qw(env LC_ALL=C perl -I. ./ikiwiki.out
+               --underlaydir=t/tmp/install/usr/share/ikiwiki/basewiki
+               --set underlaydirbase=t/tmp/install/usr/share/ikiwiki
+               --templatedir=templates);
+}
 
 foreach my $plugin ("", "listdirectives") {
-       ok(! system("LC_ALL=C perl -I. ./ikiwiki.out -rebuild -plugin brokenlinks ".
+       ok(! system(@command, qw(--rebuild --plugin brokenlinks),
                        # always enabled because pages link to it conditionally,
                        # which brokenlinks cannot handle properly
-                       "-plugin smiley ".
-                       ($plugin ? "-plugin $plugin " : "").
-                       "-underlaydir=t/tmp/install/usr/share/ikiwiki/basewiki ".
-                       "-set underlaydirbase=t/tmp/install/usr/share/ikiwiki ".
-                       "-templatedir=templates t/basewiki_brokenlinks t/tmp/out"));
+                       qw(--plugin smiley),
+                       ($plugin ? ("--plugin", $plugin) : ()),
+                       qw(t/basewiki_brokenlinks t/tmp/out)));
        my $result=`grep 'no broken links' t/tmp/out/index.html`;
        ok(length($result));
        if (! length $result) {
@@ -27,3 +38,5 @@ foreach my $plugin ("", "listdirectives") {
        ok(! system("rm -rf t/tmp/out t/basewiki_brokenlinks/.ikiwiki"));
 }
 ok(! system("rm -rf t/tmp"));
+
+done_testing();
index da2148b6b8f8cc5a3a92db07420397971ae1fe84..a5add970192c953b77c9968ebd0a51cbb4f46755 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/perl
 use warnings;
 use strict;
-use Test::More 'no_plan';
+use Test::More;
 use IkiWiki;
 
 ok(! system("rm -rf t/tmp"));
@@ -9,6 +9,20 @@ ok(mkdir "t/tmp");
 ok(! system("cp -R t/tinyblog t/tmp/in"));
 ok(mkdir "t/tmp/in/post" or -d "t/tmp/in/post");
 
+my $installed = $ENV{INSTALLED_TESTS};
+
+my @command;
+if ($installed) {
+       @command = qw(ikiwiki);
+}
+else {
+       ok(! system("make -s ikiwiki.out"));
+       @command = qw(perl -I. ./ikiwiki.out
+               --underlaydir=underlays/basewiki
+               --set underlaydirbase=underlays
+               --templatedir=templates);
+}
+
 my $comment;
 
 $comment = <<EOF;
@@ -39,8 +53,7 @@ ok(utime(222222222, 222222222, "t/tmp/in/post/comment_2._comment"));
 ok(utime(333333333, 333333333, "t/tmp/in/post/comment_1._comment"));
 
 # Build the wiki
-ok(! system("make -s ikiwiki.out"));
-ok(! system("perl -I. ./ikiwiki.out -verbose -plugin comments -url=http://example.com -cgiurl=http://example.com/ikiwiki.cgi -rss -atom -underlaydir=underlays/basewiki -set underlaydirbase=underlays -set comments_pagespec='*' -templatedir=templates t/tmp/in t/tmp/out"));
+ok(! system(@command, qw(--verbose --plugin comments --url=http://example.com --cgiurl=http://example.com/ikiwiki.cgi --rss --atom --set comments_pagespec=* t/tmp/in t/tmp/out)));
 
 # Check that the comments are in the right order
 
@@ -55,3 +68,5 @@ sub slurp {
 my $content = slurp("t/tmp/out/post/index.html");
 ok(defined $content);
 ok($content =~ m/I conquered.*I explored.*I landed/s);
+
+done_testing();
index d7e04d3ae39a66b1bdcd711aa0aa0ee12f0584ec..07c392cd3a70ae59992f422124814cd21fdd5b32 100755 (executable)
@@ -4,15 +4,30 @@ use warnings;
 use strict;
 use Test::More tests => 106;
 
+my $installed = $ENV{INSTALLED_TESTS};
+
+my @command;
+if ($installed) {
+       ok(1, "running installed");
+       @command = qw(ikiwiki);
+}
+else {
+       ok(! system("make -s ikiwiki.out"));
+       @command = qw(perl -I. ./ikiwiki.out
+               --underlaydir=underlays/basewiki
+               --set underlaydirbase=underlays
+               --templatedir=templates);
+}
+
 # setup
 my $srcdir="t/tmp/src";
 my $destdir="t/tmp/dest";
-ok(! system("make -s ikiwiki.out"));
 
 # runs ikiwiki to build test site
 sub runiki {
        my $testdesc=shift;
-       ok((! system("perl -I. ./ikiwiki.out -plugin txt -plugin rawhtml -underlaydir=underlays/basewiki -set underlaydirbase=underlays -templatedir=templates $srcdir $destdir @_")),
+       ok((! system(@command, qw(--plugin txt --plugin rawhtml),
+                               $srcdir, $destdir, @_)),
                $testdesc);
 }
 sub refreshiki {
diff --git a/t/cvs.t b/t/cvs.t
index cbac43252ecbe7ae4c1ef850cff83fe1187d1dde..43a2ca3a81f77e07afb997c3e118e64957ba1e81 100755 (executable)
--- a/t/cvs.t
+++ b/t/cvs.t
@@ -4,6 +4,8 @@ use strict;
 use Test::More; my $total_tests = 72;
 use IkiWiki;
 
+my $installed = $ENV{INSTALLED_TESTS};
+
 my $default_test_methods = '^test_*';
 my @required_programs = qw(
        cvs
@@ -606,12 +608,14 @@ sub _generate_and_configure_post_commit_hook {
        $config{wrapper} = $config{cvs_wrapper};
 
        require IkiWiki::Wrapper;
-       {
-               no warnings 'once';
+       if ($installed) {
                $IkiWiki::program_to_wrap = 'ikiwiki.out';
-               # XXX substitute its interpreter to Makefile's $(PERL)
-               # XXX best solution: do this to all scripts during build
        }
+       else {
+               $IkiWiki::program_to_wrap = `which ikiwiki`;
+       }
+       # XXX substitute its interpreter to Makefile's $(PERL)
+       # XXX best solution: do this to all scripts during build
        IkiWiki::gen_wrapper();
 
        my $cvs = "cvs -d $config{cvsrepo}";
diff --git a/t/git-cgi.t b/t/git-cgi.t
new file mode 100755 (executable)
index 0000000..ee77257
--- /dev/null
@@ -0,0 +1,317 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+
+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);
+}
+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();
+
+       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 $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');
+
+       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});
+
+       # 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});
+}
+
+test();
+
+done_testing();
diff --git a/t/git.t b/t/git.t
index 0396ae0652a26b5be875ed9a6c733a81f6656117..8990a554ef2c2585dbea30e945ae01e039700ffb 100755 (executable)
--- a/t/git.t
+++ b/t/git.t
@@ -27,8 +27,16 @@ $config{diffurl} = '/nonexistent/cgit/plain/[[file]]';
 IkiWiki::loadplugins();
 IkiWiki::checkconfig();
 
+my $makerepo;
+if ($ENV{INSTALLED_TESTS}) {
+       $makerepo = "ikiwiki-makerepo";
+}
+else {
+       $makerepo = "./ikiwiki-makerepo";
+}
+
 ok (mkdir($config{srcdir}));
-is (system("./ikiwiki-makerepo git $config{srcdir} $dir/repo"), 0);
+is (system("$makerepo git $config{srcdir} $dir/repo"), 0);
 
 my @changes;
 @changes = IkiWiki::rcs_recentchanges(3);
index 84c561fa87e380ca63c6cf902e0f87192f5892c5..3933ab7eaf25b00e52674878ffa51759509a3e1a 100755 (executable)
--- a/t/html.t
+++ b/t/html.t
@@ -6,6 +6,8 @@ use Test::More;
 my @pages;
 
 BEGIN {
+       plan(skip_all => 'running installed') if $ENV{INSTALLED_TESTS};
+
        @pages=qw(index features news plugins/map security);
        if (system("command -v validate >/dev/null") != 0) {
                plan skip_all => "html validator not present";
diff --git a/t/img.t b/t/img.t
index 968200b3e126506ade8feafd7f5e350dcd7c4272..5e92f1aff309dba448475f4eda51470d694c5e8d 100755 (executable)
--- a/t/img.t
+++ b/t/img.t
@@ -34,6 +34,22 @@ else {
 
 push @command, qw(--set usedirs=0 --plugin img t/tmp/in t/tmp/out --verbose);
 
+my $installed = $ENV{INSTALLED_TESTS};
+
+my @command;
+if ($installed) {
+       @command = qw(ikiwiki);
+}
+else {
+       ok(! system("make -s ikiwiki.out"));
+       @command = qw(perl -I. ./ikiwiki.out
+               --underlaydir=underlays/basewiki
+               --set underlaydirbase=underlays
+               --templatedir=templates);
+}
+
+push @command, qw(--set usedirs=0 --plugin img t/tmp/in t/tmp/out --verbose);
+
 my $magick = new Image::Magick;
 
 $magick->Read("t/img/twopages.pdf");
index 726227b8f0a7e88419ea2986ab7537063174773d..536bd6d67e7c6e05ed622e1e302e4f7e45cb256c 100755 (executable)
@@ -4,6 +4,24 @@ use strict;
 use Test::More;
 use IkiWiki;
 
+my $installed = $ENV{INSTALLED_TESTS};
+
+my @command;
+if ($installed) {
+       @command = qw(ikiwiki);
+}
+else {
+       ok(! system("make -s ikiwiki.out"));
+       @command = qw(perl -I. ./ikiwiki.out
+               --underlaydir=underlays/basewiki
+               --set underlaydirbase=underlays
+               --templatedir=templates);
+}
+
+push @command, qw(--set usedirs=0 --plugin inline
+       --url=http://example.com --cgiurl=http://example.com/ikiwiki.cgi
+       --rss --atom t/tmp/in t/tmp/out --verbose);
+
 my $blob;
 
 ok(! system("rm -rf t/tmp"));
@@ -33,13 +51,8 @@ foreach my $page (qw(protagonists/shepard protagonists/link
        write_old_file("$page.mdwn", "this page is {$page}");
 }
 
-ok(! system("make -s ikiwiki.out"));
-
-my $command = "perl -I. ./ikiwiki.out -set usedirs=0 -plugin inline -url=http://example.com -cgiurl=http://example.com/ikiwiki.cgi -rss -atom -underlaydir=underlays/basewiki -set underlaydirbase=underlays -templatedir=templates t/tmp/in t/tmp/out -verbose";
-
-ok(! system($command));
-
-ok(! system("$command -refresh"));
+ok(! system(@command));
+ok(! system(@command, "--refresh"));
 
 $blob = readfile("t/tmp/out/protagonists.html");
 like($blob, qr{Add a new post}, 'rootpage=yes gives postform');
diff --git a/t/passwordauth.t b/t/passwordauth.t
new file mode 100755 (executable)
index 0000000..26d6c27
--- /dev/null
@@ -0,0 +1,225 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+
+use Cwd qw(getcwd);
+use Test::More;
+
+BEGIN {
+       plan(skip_all => "Authen::Passphrase not available")
+               unless eval q{
+                       use Authen::Passphrase qw();
+                       1;
+               };
+
+       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('IkiWiki::Plugin::passwordauth');
+       use_ok('IkiWiki::Setup');
+       use_ok('IkiWiki::UserInfo');
+       use_ok('YAML::XS');
+}
+
+# We check for English messages
+$ENV{LC_ALL} = 'C';
+
+my $installed = $ENV{INSTALLED_TESTS};
+
+my @command;
+if ($installed) {
+       @command = qw(ikiwiki);
+}
+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_setup_file {
+       my %setup = (
+               wikiname => 'this is the name of my wiki',
+               srcdir => getcwd.'/t/tmp/in',
+               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 passwordauth recentchanges)],
+               adminuser => [qw(alice)],
+               disable_plugins => [qw(emailauth openid)],
+               locked_pages => '*',
+       );
+       unless ($installed) {
+               $setup{ENV} = { 'PERL5LIB' => getcwd.'/blib/lib' };
+       }
+       writefile("test.setup", "t/tmp",
+               "# IkiWiki::Setup::Yaml - YAML formatted setup file\n" .
+               Dump(\%setup));
+       %IkiWiki::config = IkiWiki::defaultconfig();
+       IkiWiki::Setup::load("t/tmp/test.setup");
+       IkiWiki::loadplugins();
+       IkiWiki::checkconfig();
+}
+
+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 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();
+
+       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,
+       );
+       print("# $query_string\n");
+       run(["./t/tmp/ikiwiki.cgi"], \$in, \$out, init => sub {
+               map {
+                       $ENV{$_} = $envvars{$_}
+               } keys(%envvars);
+       });
+
+       return $out;
+}
+
+sub test_prefs {
+       my $content;
+       my $status;
+
+       IkiWiki::userinfo_setall('alice', {regdate => time, email => 'alice@example.com'});
+       IkiWiki::userinfo_setall('bob', {regdate => time, email => 'bob@example.com'});
+       IkiWiki::userinfo_setall('name', {regdate => time, email => 'nobody@example.com'});
+       IkiWiki::Plugin::passwordauth::setpassword('alice', "Alice's password");
+       IkiWiki::Plugin::passwordauth::setpassword('bob', "Bob's password");
+
+       $content = run_cgi(
+               params => {
+                       do => 'prefs',
+               },
+       );
+
+       # prefs requires signing in so we are redirected, with the postsignin
+       # action saved in the session
+       like($content, qr/<form .*name="signin"/);
+
+       # remember the cookie so we can continue to act in that session
+       my ($cookie) = ($content =~ m/^Set-Cookie: (.*)$/im);
+
+       # sign in
+       $content = run_cgi(
+               environ => {
+                       HTTP_COOKIE => $cookie,
+               },
+               params => {
+                       do => 'signin',
+                       name => 'bob',
+                       password => "Bob's password",
+                       _submit => 'Login',
+                       _submitted_signin => '1',
+               },
+       );
+
+       # We are signed-in as bob now
+       like($content, qr{page=bob.*Create your user page});
+       like($content, qr{<input.*name="name".*value="bob"});
+       like($content, qr{<input.*name="email".*value="bob\@example.com"});
+}
+
+sub test_formbuilder_disaster {
+       my $content;
+       my $status;
+
+       ok(! system(qw(rm -rf t/tmp)));
+       ok(! system(qw(mkdir t/tmp)));
+       ok(! system(qw(mkdir t/tmp/in)));
+
+       write_setup_file();
+       thoroughly_rebuild();
+
+       IkiWiki::userinfo_setall('alice', {regdate => time, email => 'alice@example.com'});
+       IkiWiki::userinfo_setall('bob', {regdate => time, email => 'bob@example.com'});
+       IkiWiki::userinfo_setall('name', {regdate => time, email => 'nobody@example.com'});
+       IkiWiki::Plugin::passwordauth::setpassword('alice', "Alice's password");
+       IkiWiki::Plugin::passwordauth::setpassword('bob', "Bob's password");
+
+       $content = run_cgi(
+               params => {
+                       do => 'prefs',
+               },
+       );
+
+       # prefs requires signing in so we are redirected, with the postsignin
+       # action saved in the session
+       like($content, qr/<form .*name="signin"/);
+
+       # remember the cookie so we can continue to act in that session
+       my ($cookie) = ($content =~ m/^Set-Cookie: (.*)$/im);
+
+       # sign in
+       $content = run_cgi(
+               environ => {
+                       HTTP_COOKIE => $cookie,
+               },
+               params => {
+                       do => 'signin',
+                       name => ['bob', 'name', 'alice'],
+                       password => "Bob's password",
+                       _submit => 'Login',
+                       _submitted_signin => '1',
+               },
+       );
+
+       like($content, qr{page=bob.*Create your user page});
+       like($content, qr{<input.*name="name".*value="bob"});
+       like($content, qr{<input.*name="email".*value="bob\@example.com"});
+
+       unlike($content, qr{alice});
+}
+
+ok(! system(qw(rm -rf t/tmp)));
+ok(! system(qw(mkdir t/tmp)));
+ok(! system(qw(mkdir t/tmp/in)));
+
+write_setup_file();
+thoroughly_rebuild();
+
+test_prefs();
+test_formbuilder_disaster();
+
+done_testing();
index 36be984c56fd80aa88796d65bb98dee64f06fe23..edb05a81b1bbdf487aee7b59fff19e6c48fae329 100755 (executable)
@@ -1,14 +1,31 @@
 #!/usr/bin/perl
 use warnings;
 use strict;
-use Test::More 'no_plan';
+use Test::More;
+
+my $installed = $ENV{INSTALLED_TESTS};
+
+my @command;
+if ($installed) {
+       @command = qw(ikiwiki);
+}
+else {
+       ok(! system("make -s ikiwiki.out"));
+       @command = qw(perl -I. ./ikiwiki.out
+               --underlaydir=underlays/basewiki
+               --set underlaydirbase=underlays
+               --templatedir=templates);
+}
 
 ok(! system("rm -rf t/tmp"));
 ok(! system("mkdir t/tmp"));
-ok(! system("make -s ikiwiki.out"));
-ok(! system("perl -I. ./ikiwiki.out -plugin inline -url=http://example.com -cgiurl=http://example.com/ikiwiki.cgi -rss -atom -underlaydir=underlays/basewiki -set underlaydirbase=underlays -templatedir=templates t/tinyblog t/tmp/out"));
+ok(! system(@command, qw(--plugin inline --url=http://example.com
+               --cgiurl=http://example.com/ikiwiki.cgi --rss --atom
+               t/tinyblog t/tmp/out)));
 # This guid should never, ever change, for any reason whatsoever!
 my $guid="http://example.com/post/";
 ok(length `egrep '<guid.*>$guid</guid>' t/tmp/out/index.rss`);
 ok(length `egrep '<id>$guid</id>' t/tmp/out/index.atom`);
 ok(! system("rm -rf t/tmp t/tinyblog/.ikiwiki"));
+
+done_testing();
index 94505a05e2ca70dabcbdd28f4cda14be353fbf9f..77c905bde89464f895ac19bc1ebe732df4bab7a9 100755 (executable)
@@ -9,13 +9,28 @@ BEGIN {
                        "XML::Feed and/or HTML::Parser or File::MimeInfo not available"};
        }
        else {
-               eval q{use Test::More tests => 136};
+               eval q{use Test::More tests => 137};
        }
 }
 
 use Cwd;
 use File::Basename;
 
+my $installed = $ENV{INSTALLED_TESTS};
+
+my @base_command;
+if ($installed) {
+       ok(1, "running installed");
+       @base_command = qw(ikiwiki);
+}
+else {
+       ok(! system("make -s ikiwiki.out"));
+       @base_command = qw(perl -I. ./ikiwiki.out
+               --underlaydir=underlays/basewiki
+               --set underlaydirbase=underlays
+               --templatedir=templates);
+}
+
 my $tmp = 't/tmp';
 my $statedir = 't/tinypodcast/.ikiwiki';
 
@@ -23,10 +38,8 @@ sub podcast {
        my $podcast_style = shift;
 
        my $baseurl = 'http://example.com';
-       my @command = (qw(./ikiwiki.out -plugin inline -rss -atom));
-       push @command, qw(-underlaydir=underlays/basewiki);
-       push @command, qw(-set underlaydirbase=underlays -templatedir=templates);
-       push @command, "-url=$baseurl", qw(t/tinypodcast), "$tmp/out";
+       my @command = (@base_command, qw(--plugin inline --rss --atom));
+       push @command, "--url=$baseurl", qw(t/tinypodcast), "$tmp/out";
 
        ok(! system("mkdir $tmp"),
                q{setup});
@@ -113,9 +126,7 @@ sub podcast {
 }
 
 sub single_page_html {
-       my @command = (qw(./ikiwiki.out));
-       push @command, qw(-underlaydir=underlays/basewiki);
-       push @command, qw(-set underlaydirbase=underlays -templatedir=templates);
+       my @command = @base_command;
        push @command, qw(t/tinypodcast), "$tmp/out";
 
        ok(! system("mkdir $tmp"),
@@ -158,9 +169,7 @@ sub single_page_html {
 }
 
 sub inlined_pages_html {
-       my @command = (qw(./ikiwiki.out -plugin inline));
-       push @command, qw(-underlaydir=underlays/basewiki);
-       push @command, qw(-set underlaydirbase=underlays -templatedir=templates);
+       my @command = (@base_command, qw(--plugin inline));
        push @command, qw(t/tinypodcast), "$tmp/out";
 
        ok(! system("mkdir $tmp"),
index 054f8f6646a83d7a46fd45c6041f28fa7b481e8a..73145dfd70133f51209263598c1eb578813a1529 100755 (executable)
@@ -16,6 +16,20 @@ use Errno qw(ENOENT);
 
 # Black-box (ish) test for relative linking between CGI and static content
 
+my $installed = $ENV{INSTALLED_TESTS};
+
+my @command;
+if ($installed) {
+       @command = qw(ikiwiki);
+}
+else {
+       ok(! system("make -s ikiwiki.out"));
+       @command = qw(perl -I. ./ikiwiki.out
+               --underlaydir=underlays/basewiki
+               --set underlaydirbase=underlays
+               --templatedir=templates);
+}
+
 sub parse_cgi_content {
        my $content = shift;
        my %bits;
@@ -53,7 +67,6 @@ sub write_setup_file {
 wikiname: this is the name of my wiki
 srcdir: t/tmp/in
 destdir: t/tmp/out
-templatedir: templates
 $urlline
 cgiurl: $args{cgiurl}
 $w3mmodeline
@@ -72,7 +85,7 @@ EOF
 
 sub thoroughly_rebuild {
        ok(unlink("t/tmp/ikiwiki.cgi") || $!{ENOENT});
-       ok(! system("./ikiwiki.out --setup t/tmp/test.setup --rebuild --wrappers"));
+       ok(! system(@command, qw(--setup t/tmp/test.setup --rebuild --wrappers)));
 }
 
 sub check_cgi_mode_bits {
@@ -132,7 +145,6 @@ sub run_cgi {
 }
 
 sub test_startup {
-       ok(! system("make -s ikiwiki.out"));
        ok(! system("rm -rf t/tmp"));
        ok(! system("mkdir t/tmp"));
 
index b7c6efd58345de974628f6e55dab4424336c9c09..1d496be2dec2e5231b053533adfd0d2a2bb761cd 100755 (executable)
@@ -3,6 +3,8 @@ use warnings;
 use strict;
 use Test::More;
 
+plan(skip_all => 'running installed') if $ENV{INSTALLED_TESTS};
+
 my @progs="ikiwiki.in";
 my @libs="IkiWiki.pm";
 # monotone, external, amazon_s3, po, and cvs
index e3d1feca9ef3d3791ba7e93cf6bad2faf9abecc7..3e6509f35b7b2d76fcc7f0910e78cce5d2e9482e 100755 (executable)
@@ -3,6 +3,8 @@ use warnings;
 use strict;
 use Test::More;
 
+plan(skip_all => 'running installed') if $ENV{INSTALLED_TESTS};
+
 my @templates=(glob("templates/*.tmpl"), glob("doc/templates/*.mdwn"));
 plan(tests => 2*@templates);
 
index 826c51d3667ab1ae93c636c330e010d379630fec..4991e4521eaeee71b86b991c1e54643c9290da4a 100755 (executable)
@@ -1,7 +1,9 @@
 #!/usr/bin/perl
 use warnings;
 use strict;
-use Test::More 'no_plan';
+use Test::More;
+
+plan(skip_all => 'running installed') if $ENV{INSTALLED_TESTS};
 
 $/=undef;
 open(IN, "doc/templates.mdwn") || die "doc/templates.mdwn: $!";
@@ -12,3 +14,5 @@ foreach my $file (glob("templates/*.tmpl")) {
        $file=~s/templates\///;
        ok($page =~ /\Q$file\E/, "$file documented on doc/templates.mdwn");
 }
+
+done_testing();
index dce3b3c7e4df551984c30c6ed0a0cecac53631ed..cac64c36628c3bd75147e0e1ccb8d9aa74a5d3fc 100755 (executable)
--- a/t/trail.t
+++ b/t/trail.t
@@ -1,7 +1,7 @@
 #!/usr/bin/perl
 use warnings;
 use strict;
-use Test::More 'no_plan';
+use Test::More;
 use IkiWiki;
 
 sub check_trail {
@@ -27,6 +27,24 @@ my $blob;
 ok(! system("rm -rf t/tmp"));
 ok(! system("mkdir t/tmp"));
 
+my $installed = $ENV{INSTALLED_TESTS};
+
+my @command;
+if ($installed) {
+       @command = qw(ikiwiki);
+}
+else {
+       ok(! system("make -s ikiwiki.out"));
+       @command = qw(perl -I. ./ikiwiki.out
+               --underlaydir=underlays/basewiki
+               --set underlaydirbase=underlays
+               --templatedir=templates);
+}
+
+push @command, qw(--set usedirs=0 --plugin trail --plugin inline
+       --url=http://example.com --cgiurl=http://example.com/ikiwiki.cgi
+       --rss --atom t/tmp/in t/tmp/out --verbose);
+
 # Write files with a date in the past, so that when we refresh,
 # the update is detected.
 sub write_old_file {
@@ -129,13 +147,8 @@ write_old_file("wind_in_the_willows.mdwn", <<EOF
 EOF
 );
 
-ok(! system("make -s ikiwiki.out"));
-
-my $command = "perl -I. ./ikiwiki.out -set usedirs=0 -plugin trail -plugin inline -url=http://example.com -cgiurl=http://example.com/ikiwiki.cgi -rss -atom -underlaydir=underlays/basewiki -set underlaydirbase=underlays -templatedir=templates t/tmp/in t/tmp/out -verbose";
-
-ok(! system($command));
-
-ok(! system("$command -refresh"));
+ok(! system(@command));
+ok(! system(@command, "--refresh"));
 
 $blob = readfile("t/tmp/out/meme.html");
 ok($blob =~ /<a href="(\.\/)?badger.html">badger<\/a>/m);
@@ -232,7 +245,7 @@ writefile("limited/c.mdwn", "t/tmp/in", '[[!meta title="New C page"]]c');
 
 writefile("untrail.mdwn", "t/tmp/in", "no longer a trail");
 
-ok(! system("$command -refresh"));
+ok(! system(@command, "--refresh"));
 
 check_trail("add/a.html", "n=add/b p=");
 check_trail("add/b.html", "n=add/c p=add/a");
@@ -290,3 +303,5 @@ check_no_trail("untrail/a.html");
 check_no_trail("untrail/b.html");
 
 ok(! system("rm -rf t/tmp"));
+
+done_testing();