diff options
Diffstat (limited to 'IkiWiki/Plugin')
-rw-r--r-- | IkiWiki/Plugin/attachment.pm | 58 | ||||
-rw-r--r-- | IkiWiki/Plugin/img.pm | 10 | ||||
-rw-r--r-- | IkiWiki/Plugin/remove.pm | 212 | ||||
-rw-r--r-- | IkiWiki/Plugin/rename.pm | 285 |
4 files changed, 543 insertions, 22 deletions
diff --git a/IkiWiki/Plugin/attachment.pm b/IkiWiki/Plugin/attachment.pm index 3982c4883..e08aa3677 100644 --- a/IkiWiki/Plugin/attachment.pm +++ b/IkiWiki/Plugin/attachment.pm @@ -11,6 +11,40 @@ sub import { #{{{ hook(type => "formbuilder", id => "attachment", call => \&formbuilder); } # }}} +sub check_canattach ($$;$) { + my $session=shift; + my $dest=shift; # where it's going to be put, under the srcdir + my $file=shift; # the path to the attachment currently + + # Don't allow an attachment to be uploaded with the same name as an + # existing page. + if (exists $pagesources{$dest} && $pagesources{$dest} ne $dest) { + error(sprintf(gettext("there is already a page named %s"), $dest)); + } + + # Use a special pagespec to test that the attachment is valid. + my $allowed=1; + foreach my $admin (@{$config{adminuser}}) { + my $allowed_attachments=IkiWiki::userinfo_get($admin, "allowed_attachments"); + if (defined $allowed_attachments && + length $allowed_attachments) { + $allowed=pagespec_match($dest, + $allowed_attachments, + file => $file, + user => $session->param("name"), + ip => $ENV{REMOTE_ADDR}, + ); + last if $allowed; + } + } + if (! $allowed) { + error(gettext("prohibited by allowed_attachments")." ($allowed)"); + } + else { + return 1; + } +} + sub checkconfig () { #{{{ $config{cgi_disable_uploads}=0; } #}}} @@ -113,25 +147,8 @@ sub formbuilder (@) { #{{{ # Check that the user is allowed to edit a page with the # name of the attachment. IkiWiki::check_canedit($filename, $q, $session, 1); - - # Use a special pagespec to test that the attachment is valid. - my $allowed=1; - foreach my $admin (@{$config{adminuser}}) { - my $allowed_attachments=IkiWiki::userinfo_get($admin, "allowed_attachments"); - if (defined $allowed_attachments && - length $allowed_attachments) { - $allowed=pagespec_match($filename, - $allowed_attachments, - file => $tempfile, - user => $session->param("name"), - ip => $ENV{REMOTE_ADDR}, - ); - last if $allowed; - } - } - if (! $allowed) { - error(gettext("attachment rejected")." ($allowed)"); - } + # And that the attachment itself is acceptable. + check_canattach($session, $filename, $tempfile); # Needed for fast_file_copy and for rendering below. require IkiWiki::Render; @@ -419,6 +436,9 @@ sub match_user ($$;@) { #{{{ if (defined $params{user} && lc $params{user} eq lc $user) { return IkiWiki::SuccessReason->new("user is $user"); } + elsif (! defined $params{user}) { + return IkiWiki::FailReason->new("not logged in"); + } else { return IkiWiki::FailReason->new("user is $params{user}, not $user"); } diff --git a/IkiWiki/Plugin/img.pm b/IkiWiki/Plugin/img.pm index 17a9367d3..748d28ace 100644 --- a/IkiWiki/Plugin/img.pm +++ b/IkiWiki/Plugin/img.pm @@ -41,6 +41,10 @@ sub preprocess (@) { #{{{ } my $file = bestlink($params{page}, $image); + my $srcfile = srcfile($file, 1); + if (! length $file || ! defined $srcfile) { + return htmllink($params{page}, $params{destpage}, $image); + } my $dir = $params{page}; my $base = IkiWiki::basename($file); @@ -61,12 +65,12 @@ sub preprocess (@) { #{{{ will_render($params{page}, $imglink); - if (-e $outfile && (-M srcfile($file) >= -M $outfile)) { + if (-e $outfile && (-M $srcfile >= -M $outfile)) { $r = $im->Read($outfile); error sprintf(gettext("failed to read %s: %s"), $outfile, $r) if $r; } else { - $r = $im->Read(srcfile($file)); + $r = $im->Read($srcfile); error sprintf(gettext("failed to read %s: %s"), $file, $r) if $r; $r = $im->Resize(geometry => "${w}x${h}"); @@ -83,7 +87,7 @@ sub preprocess (@) { #{{{ } } else { - $r = $im->Read(srcfile($file)); + $r = $im->Read($srcfile); error sprintf(gettext("failed to read %s: %s"), $file, $r) if $r; $imglink = $file; } diff --git a/IkiWiki/Plugin/remove.pm b/IkiWiki/Plugin/remove.pm new file mode 100644 index 000000000..4c73ed9e5 --- /dev/null +++ b/IkiWiki/Plugin/remove.pm @@ -0,0 +1,212 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::remove; + +use warnings; +use strict; +use IkiWiki 2.00; + +sub import { #{{{ + hook(type => "formbuilder_setup", id => "remove", call => \&formbuilder_setup); + hook(type => "formbuilder", id => "remove", call => \&formbuilder); + hook(type => "sessioncgi", id => "remove", call => \&sessioncgi); + +} # }}} + +sub check_canremove ($$$$) { + my $page=shift; + my $q=shift; + my $session=shift; + my $attachment=shift; + + # Must be a known source file. + if (! exists $pagesources{$page}) { + error(sprintf(gettext("%s does not exist"), + htmllink("", "", $page, noimageinline => 1))); + } + + # Must exist on disk, and be a regular file. + my $file=$pagesources{$page}; + if (! -e "$config{srcdir}/$file") { + error(sprintf(gettext("%s is not in the srcdir, so it cannot be deleted"), $file)); + } + elsif (-l "$config{srcdir}/$file" && ! -f _) { + error(sprintf(gettext("%s is not a file"), $file)); + } + + # Must be editiable. + IkiWiki::check_canedit($page, $q, $session); + + # This is sorta overkill, but better safe + # than sorry. If a user can't upload an + # attachment, don't let them delete it. + if ($attachment) { + IkiWiki::Plugin::attachment::check_canattach($session, $page, $file); + } +} + +sub formbuilder_setup (@) { #{{{ + my %params=@_; + my $form=$params{form}; + my $q=$params{cgi}; + + if (defined $form->field("do") && $form->field("do") eq "edit") { + # Removal button for the page, and also for attachments. + push @{$params{buttons}}, "Remove"; + $form->tmpl_param("field-remove" => '<input name="_submit" type="submit" value="Remove Attachments" />'); + } +} #}}} + +sub confirmation_form ($$) { #{{{ + my $q=shift; + my $session=shift; + + eval q{use CGI::FormBuilder}; + error($@) if $@; + my $f = CGI::FormBuilder->new( + name => "remove", + header => 0, + charset => "utf-8", + method => 'POST', + javascript => 0, + params => $q, + action => $config{cgiurl}, + stylesheet => IkiWiki::baseurl()."style.css", + fields => [qw{do page}], + ); + + $f->field(name => "do", type => "hidden", value => "remove", force => 1); + + return $f, ["Remove", "Cancel"]; +} #}}} + +sub removal_confirm ($$@) { + my $q=shift; + my $session=shift; + my $attachment=shift; + my @pages=@_; + + check_canremove($_, $q, $session, $attachment) foreach @pages; + + # Save current form state to allow returning to it later + # without losing any edits. + # (But don't save what button was submitted, to avoid + # looping back to here.) + # Note: "_submit" is CGI::FormBuilder internals. + $q->param(-name => "_submit", -value => ""); + $session->param(postremove => scalar $q->Vars); + IkiWiki::cgi_savesession($session); + + my ($f, $buttons)=confirmation_form($q, $session); + $f->title(sprintf(gettext("confirm removal of %s"), + join(", ", map { IkiWiki::pagetitle($_) } @pages))); + $f->field(name => "page", type => "hidden", value => \@pages, force => 1); + if (defined $attachment) { + $f->field(name => "attachment", type => "hidden", + value => $attachment, force => 1); + } + + IkiWiki::showform($f, $buttons, $session, $q); + exit 0; +} + +sub postremove ($) { + my $session=shift; + + # Load saved form state and return to edit form. + my $postremove=CGI->new($session->param("postremove")); + $session->clear("postremove"); + IkiWiki::cgi_savesession($session); + IkiWiki::cgi($postremove, $session); +} + +sub formbuilder (@) { #{{{ + my %params=@_; + my $form=$params{form}; + + if (defined $form->field("do") && $form->field("do") eq "edit") { + my $q=$params{cgi}; + my $session=$params{session}; + + if ($form->submitted eq "Remove") { + removal_confirm($q, $session, 0, $form->field("page")); + } + elsif ($form->submitted eq "Remove Attachments") { + my @selected=$q->param("attachment_select"); + if (! @selected) { + error(gettext("Please select the attachments to remove.")); + } + removal_confirm($q, $session, 1, @selected); + } + } +} #}}} + +sub sessioncgi ($$) { #{{{ + my $q=shift; + + if ($q->param("do") eq 'remove') { + my $session=shift; + my ($form, $buttons)=confirmation_form($q, $session); + IkiWiki::decode_form_utf8($form); + + if ($form->submitted eq 'Cancel') { + postremove($session); + } + elsif ($form->submitted eq 'Remove' && $form->validate) { + my @pages=$q->param("page"); + + # Validate removal by checking that the page exists, + # and that the user is allowed to edit(/remove) it. + my @files; + foreach my $page (@pages) { + check_canremove($page, $q, $session, $q->param("attachment")); + + # This untaint is safe because of the + # checks performed above, which verify the + # page is a normal file, etc. + push @files, IkiWiki::possibly_foolish_untaint($pagesources{$page}); + } + + # Do removal, and update the wiki. + require IkiWiki::Render; + if ($config{rcs}) { + IkiWiki::disable_commit_hook(); + foreach my $file (@files) { + IkiWiki::rcs_remove($file); + } + IkiWiki::rcs_commit_staged(gettext("removed"), + $session->param("name"), $ENV{REMOTE_ADDR}); + IkiWiki::enable_commit_hook(); + IkiWiki::rcs_update(); + } + else { + foreach my $file (@files) { + IkiWiki::prune("$config{srcdir}/$file"); + } + } + IkiWiki::refresh(); + IkiWiki::saveindex(); + + if ($q->param("attachment")) { + # Attachments were deleted, so redirect + # back to the edit form. + postremove($session); + } + else { + # The page is gone, so redirect to parent + # of the page. + my $parent=IkiWiki::dirname($pages[0]); + if (! exists $pagesources{$parent}) { + $parent="index"; + } + IkiWiki::redirect($q, $config{url}."/".htmlpage($parent)); + } + } + else { + IkiWiki::showform($form, $buttons, $session, $q); + } + + exit 0; + } +} + +1 diff --git a/IkiWiki/Plugin/rename.pm b/IkiWiki/Plugin/rename.pm new file mode 100644 index 000000000..38f703ddd --- /dev/null +++ b/IkiWiki/Plugin/rename.pm @@ -0,0 +1,285 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::rename; + +use warnings; +use strict; +use IkiWiki 2.00; + +sub import { #{{{ + hook(type => "formbuilder_setup", id => "rename", call => \&formbuilder_setup); + hook(type => "formbuilder", id => "rename", call => \&formbuilder); + hook(type => "sessioncgi", id => "rename", call => \&sessioncgi); + +} # }}} + +sub check_canrename ($$$$$$$) { #{{{ + my $src=shift; + my $srcfile=shift; + my $dest=shift; + my $destfile=shift; + my $q=shift; + my $session=shift; + my $attachment=shift; + + # Must be a known source file. + if (! exists $pagesources{$src}) { + error(sprintf(gettext("%s does not exist"), + htmllink("", "", $src, noimageinline => 1))); + } + + # Must exist on disk, and be a regular file. + if (! -e "$config{srcdir}/$srcfile") { + error(sprintf(gettext("%s is not in the srcdir, so it cannot be renamed"), $srcfile)); + } + elsif (-l "$config{srcdir}/$srcfile" && ! -f _) { + error(sprintf(gettext("%s is not a file"), $srcfile)); + } + + # Must be editable. + IkiWiki::check_canedit($src, $q, $session); + if ($attachment) { + IkiWiki::Plugin::attachment::check_canattach($session, $src, $srcfile); + } + + # Dest checks can be omitted by passing undef. + if (defined $dest) { + if ($src eq $dest || $srcfile eq $destfile) { + error(gettext("no change to the file name was specified")); + } + + # Must be a legal filename, and not absolute. + if (IkiWiki::file_pruned($destfile, $config{srcdir}) || + $destfile=~/^\//) { + error(sprintf(gettext("illegal name"))); + } + + # Must not be a known source file. + if (exists $pagesources{$dest}) { + error(sprintf(gettext("%s already exists"), + htmllink("", "", $dest, noimageinline => 1))); + } + + # Must not exist on disk already. + if (-l "$config{srcdir}/$destfile" || -e _) { + error(sprintf(gettext("%s already exists on disk"), $destfile)); + } + + # Must be editable. + IkiWiki::check_canedit($dest, $q, $session); + if ($attachment) { + # Note that $srcfile is used here, not $destfile, + # because it wants the current file, to check it. + IkiWiki::Plugin::attachment::check_canattach($session, $dest, $srcfile); + } + } +} #}}} + +sub rename_form ($$$) { #{{{ + my $q=shift; + my $session=shift; + my $page=shift; + + eval q{use CGI::FormBuilder}; + error($@) if $@; + my $f = CGI::FormBuilder->new( + name => "rename", + title => sprintf(gettext("rename %s"), IkiWiki::pagetitle($page)), + header => 0, + charset => "utf-8", + method => 'POST', + javascript => 0, + params => $q, + action => $config{cgiurl}, + stylesheet => IkiWiki::baseurl()."style.css", + fields => [qw{do page new_name attachment}], + ); + + $f->field(name => "do", type => "hidden", value => "rename", force => 1); + $f->field(name => "page", type => "hidden", value => $page, force => 1); + $f->field(name => "new_name", value => IkiWiki::pagetitle($page), size => 60); + $f->field(name => "attachment", type => "hidden"); + + return $f, ["Rename", "Cancel"]; +} #}}} + +sub rename_start ($$$$) { + my $q=shift; + my $session=shift; + my $attachment=shift; + my $page=shift; + + check_canrename($page, $pagesources{$page}, undef, undef, + $q, $session, $attachment); + + # Save current form state to allow returning to it later + # without losing any edits. + # (But don't save what button was submitted, to avoid + # looping back to here.) + # Note: "_submit" is CGI::FormBuilder internals. + $q->param(-name => "_submit", -value => ""); + $session->param(postrename => scalar $q->Vars); + IkiWiki::cgi_savesession($session); + + my ($f, $buttons)=rename_form($q, $session, $page); + if (defined $attachment) { + $f->field(name => "attachment", value => $attachment, force => 1); + } + + IkiWiki::showform($f, $buttons, $session, $q); + exit 0; +} + +sub postrename ($;$$) { + my $session=shift; + my $dest=shift; + my $attachment=shift; + + # Load saved form state and return to edit page. + my $postrename=CGI->new($session->param("postrename")); + $session->clear("postrename"); + IkiWiki::cgi_savesession($session); + + if (defined $dest && ! $attachment) { + # They renamed the page they were editing. This requires + # fixups to the edit form state. + # Tweak the edit form to be editing the new page. + $postrename->param("page", $dest); + # Get a new edit token; old one might not be valid for the + # renamed file. + $postrename->param("rcsinfo", IkiWiki::rcs_prepedit($pagesources{$dest})); + } + + IkiWiki::cgi_editpage($postrename, $session); +} + +sub formbuilder (@) { #{{{ + my %params=@_; + my $form=$params{form}; + + if (defined $form->field("do") && $form->field("do") eq "edit") { + my $q=$params{cgi}; + my $session=$params{session}; + + if ($form->submitted eq "Rename") { + rename_start($q, $session, 0, $form->field("page")); + } + elsif ($form->submitted eq "Rename Attachment") { + my @selected=$q->param("attachment_select"); + if (@selected > 1) { + error(gettext("Only one attachment can be renamed at a time.")); + } + elsif (! @selected) { + error(gettext("Please select the attachment to rename.")) + } + rename_start($q, $session, 1, $selected[0]); + } + } +} #}}} + +my $renamesummary; + +sub formbuilder_setup (@) { #{{{ + my %params=@_; + my $form=$params{form}; + my $q=$params{cgi}; + + if (defined $form->field("do") && $form->field("do") eq "edit") { + # Rename button for the page, and also for attachments. + push @{$params{buttons}}, "Rename"; + $form->tmpl_param("field-rename" => '<input name="_submit" type="submit" value="Rename Attachment" />'); + + if (defined $renamesummary) { + $form->tmpl_param(message => $renamesummary); + } + } +} #}}} + +sub sessioncgi ($$) { #{{{ + my $q=shift; + + if ($q->param("do") eq 'rename') { + my $session=shift; + my ($form, $buttons)=rename_form($q, $session, $q->param("page")); + IkiWiki::decode_form_utf8($form); + + if ($form->submitted eq 'Cancel') { + postrename($session); + } + elsif ($form->submitted eq 'Rename' && $form->validate) { + # These untaints are safe because of the checks + # performed in check_canrename below. + my $src=$q->param("page"); + my $srcfile=IkiWiki::possibly_foolish_untaint($pagesources{$src}); + my $dest=IkiWiki::possibly_foolish_untaint(IkiWiki::titlepage($q->param("new_name"))); + + # The extension of dest is the same as src if it's + # a page. If it's an extension, the extension is + # already included. + my $destfile=$dest; + if (! $q->param("attachment")) { + my ($ext)=$srcfile=~/(\.[^.]+)$/; + $destfile.=$ext; + } + + check_canrename($src, $srcfile, $dest, $destfile, + $q, $session, $q->param("attachment")); + + # Ensures that the dest directory exists and is ok. + IkiWiki::prep_writefile($destfile, $config{srcdir}); + + # Do rename, and update the wiki. + require IkiWiki::Render; + if ($config{rcs}) { + IkiWiki::disable_commit_hook(); + IkiWiki::rcs_rename($srcfile, $destfile); + IkiWiki::rcs_commit_staged( + sprintf(gettext("rename %s to %s"), $src, $dest), + $session->param("name"), $ENV{REMOTE_ADDR}); + IkiWiki::enable_commit_hook(); + IkiWiki::rcs_update(); + } + else { + if (! rename("$config{srcdir}/$srcfile", "$config{srcdir}/$destfile")) { + error("rename: $!"); + } + } + IkiWiki::refresh(); + IkiWiki::saveindex(); + + # scan for broken links to $src + my @brokenlinks; + foreach my $page (keys %links) { + foreach my $link (@{$links{$page}}) { + my $bestlink=bestlink($page, $link); + if ($bestlink eq $src) { + push @brokenlinks, $page; + } + } + } + + # Generate a rename summary, that will be shown at the top + # of the edit template. + my $template=template("renamesummary.tmpl"); + $template->param(src => $src); + $template->param(dest => $dest); + $template->param(linklist => [ + map { + { + page => htmllink($dest, $dest, $_, + noimageinline => 1) + } + } @brokenlinks + ]); + $renamesummary=$template->output; + + postrename($session, $dest, $q->param("attachment")); + } + else { + IkiWiki::showform($form, $buttons, $session, $q); + } + + exit 0; + } +} + +1 |