ikiwiki/IkiWiki/Plugin/attachment.pm

243 lines
7.4 KiB
Perl
Raw Normal View History

#!/usr/bin/perl
package IkiWiki::Plugin::attachment;
use warnings;
use strict;
use IkiWiki 3.00;
2008-07-01 06:42:42 +02:00
sub import {
add_underlay("javascript");
hook(type => "getsetup", id => "attachment", call => \&getsetup);
hook(type => "checkconfig", id => "attachment", call => \&checkconfig);
hook(type => "formbuilder_setup", id => "attachment", call => \&formbuilder_setup);
hook(type => "formbuilder", id => "attachment", call => \&formbuilder);
IkiWiki::loadplugin("filecheck");
}
sub getsetup () {
return
plugin => {
safe => 1,
rebuild => 0,
},
allowed_attachments => {
2008-08-02 22:40:46 +02:00
type => "pagespec",
example => "virusfree() and mimetype(image/*) and maxsize(50kb)",
description => "enhanced PageSpec specifying what attachments are allowed",
2008-08-03 20:36:17 +02:00
link => "ikiwiki/PageSpec/attachment",
safe => 1,
rebuild => 0,
},
virus_checker => {
type => "string",
example => "clamdscan -",
description => "virus checker program (reads STDIN, returns nonzero if virus found)",
safe => 0, # executed
rebuild => 0,
},
}
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 $IkiWiki::pagesources{$dest} &&
$IkiWiki::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;
if (defined $config{allowed_attachments} &&
length $config{allowed_attachments}) {
$allowed=pagespec_match($dest,
$config{allowed_attachments},
file => $file,
user => $session->param("name"),
ip => $ENV{REMOTE_ADDR},
);
}
if (! $allowed) {
2008-07-22 21:06:59 +02:00
error(gettext("prohibited by allowed_attachments")." ($allowed)");
}
else {
return 1;
}
}
sub checkconfig () {
$config{cgi_disable_uploads}=0;
}
sub formbuilder_setup (@) {
my %params=@_;
my $form=$params{form};
my $q=$params{cgi};
if (defined $form->field("do") && ($form->field("do") eq "edit" ||
$form->field("do") eq "create")) {
# Add attachment field, set type to multipart.
$form->enctype(&CGI::MULTIPART);
$form->field(name => 'attachment', type => 'file');
2008-07-01 23:19:38 +02:00
# These buttons are not put in the usual place, so
2008-07-02 02:35:54 +02:00
# are not added to the normal formbuilder button list.
2008-07-01 23:19:38 +02:00
$form->tmpl_param("field-upload" => '<input name="_submit" type="submit" value="Upload Attachment" />');
$form->tmpl_param("field-link" => '<input name="_submit" type="submit" value="Insert Links" />');
# Add the toggle javascript; the attachments interface uses
# it to toggle visibility.
require IkiWiki::Plugin::toggle;
$form->tmpl_param("javascript" => IkiWiki::Plugin::toggle::include_javascript($params{page}, 1));
# Start with the attachments interface toggled invisible,
# but if it was used, keep it open.
if ($form->submitted ne "Upload Attachment" &&
(! defined $q->param("attachment_select") ||
! length $q->param("attachment_select"))) {
$form->tmpl_param("attachments-class" => "toggleable");
}
else {
$form->tmpl_param("attachments-class" => "toggleable-open");
}
}
}
sub formbuilder (@) {
my %params=@_;
my $form=$params{form};
2008-07-02 00:08:31 +02:00
my $q=$params{cgi};
return if ! defined $form->field("do") || ($form->field("do") ne "edit" && $form->field("do") ne "create") ;
2008-07-02 01:05:15 +02:00
my $filename=$q->param('attachment');
if (defined $filename && length $filename &&
($form->submitted eq "Upload Attachment" || $form->submitted eq "Save Page")) {
2008-07-01 21:35:01 +02:00
my $session=$params{session};
# This is an (apparently undocumented) way to get the name
# of the temp file that CGI writes the upload to.
my $tempfile=$q->tmpFileName($filename);
2008-07-08 22:21:01 +02:00
if (! defined $tempfile || ! length $tempfile) {
attachment: Support perl 5.8's buggy version of CGI.pm. This is truely horribly disgusting. CGI::tmpFileName, in current perls, is an undocumented function (which should be a clue..) that takes the original filename of an uploaded attachment, and returns the name of the tempfile that CGI has stored it in. In old perls, though, CGI::tmpFileName does not take a filename. It takes a key from the object's {'.tmpfiles'} hash. This key is something crazy like '*Fh::fh00001group' -- apparently the stringification of a filehandle object. Just to add to the fun, tmpFileName doesn't take the key, it expects a refernce to the key. Argh?! But the fun doesn't stop there, because in perl 5.8, CGI.pm is also broken in two other ways. The upload() method is supposed to return a filehandle to the temp file. It doesn't. The param() method is supposed to return a filehandle to the temp file, that stringifies to the original filename. It returns just the original filename, no filehandle. Combine all these bugs, and you end up with this disgusting commit. Since I have no way to get the filehandle, I *need* to get the tempfile name. If I had the filehandle, I could probably pass it into tmpFileName, and it might strigify to the right key name. But I don't, so the only way to determine the key is to grub through the .tmpfiles hash ourselves. And finally, one the temp file name is discovered, a filehandle can finally be obtained by (re)opening it. I recommend that this commit be reverted when perl 5.8 is a mercifully faded memory. I'm really, really, really glad I'm actually being paid for working on this right now!
2008-07-09 00:10:05 +02:00
# perl 5.8 needs an alternative, awful method
if ($q =~ /HASH/ && exists $q->{'.tmpfiles'}) {
foreach my $key (keys(%{$q->{'.tmpfiles'}})) {
$tempfile=$q->tmpFileName(\$key);
last if defined $tempfile && length $tempfile;
}
}
if (! defined $tempfile || ! length $tempfile) {
error("CGI::tmpFileName failed to return the uploaded file name");
}
}
2008-09-27 20:14:36 +02:00
$filename=linkpage(IkiWiki::possibly_foolish_untaint(
2008-07-01 23:19:38 +02:00
attachment_location($form->field('page')).
IkiWiki::basename($filename)));
if (IkiWiki::file_pruned($filename, $config{srcdir})) {
error(gettext("bad attachment filename"));
}
2008-07-01 05:17:01 +02:00
# Check that the user is allowed to edit a page with the
# name of the attachment.
2008-07-01 21:35:01 +02:00
IkiWiki::check_canedit($filename, $q, $session, 1);
# And that the attachment itself is acceptable.
check_canattach($session, $filename, $tempfile);
2008-07-01 06:42:42 +02:00
2008-07-01 21:35:01 +02:00
# Needed for fast_file_copy and for rendering below.
2008-07-01 19:48:07 +02:00
require IkiWiki::Render;
2008-07-01 06:42:42 +02:00
# Move the attachment into place.
# Try to use a fast rename; fall back to copying.
2008-07-01 19:39:02 +02:00
IkiWiki::prep_writefile($filename, $config{srcdir});
2008-07-01 06:42:42 +02:00
unlink($config{srcdir}."/".$filename);
2008-07-02 01:05:15 +02:00
if (rename($tempfile, $config{srcdir}."/".$filename)) {
# The temp file has tight permissions; loosen up.
chmod(0666 & ~umask, $config{srcdir}."/".$filename);
}
else {
2008-07-01 06:42:42 +02:00
my $fh=$q->upload('attachment');
if (! defined $fh || ! ref $fh) {
# needed by old CGI versions
$fh=$q->param('attachment');
if (! defined $fh || ! ref $fh) {
# even that doesn't always work,
# fall back to opening the tempfile
2008-07-08 18:16:36 +02:00
$fh=undef;
attachment: Support perl 5.8's buggy version of CGI.pm. This is truely horribly disgusting. CGI::tmpFileName, in current perls, is an undocumented function (which should be a clue..) that takes the original filename of an uploaded attachment, and returns the name of the tempfile that CGI has stored it in. In old perls, though, CGI::tmpFileName does not take a filename. It takes a key from the object's {'.tmpfiles'} hash. This key is something crazy like '*Fh::fh00001group' -- apparently the stringification of a filehandle object. Just to add to the fun, tmpFileName doesn't take the key, it expects a refernce to the key. Argh?! But the fun doesn't stop there, because in perl 5.8, CGI.pm is also broken in two other ways. The upload() method is supposed to return a filehandle to the temp file. It doesn't. The param() method is supposed to return a filehandle to the temp file, that stringifies to the original filename. It returns just the original filename, no filehandle. Combine all these bugs, and you end up with this disgusting commit. Since I have no way to get the filehandle, I *need* to get the tempfile name. If I had the filehandle, I could probably pass it into tmpFileName, and it might strigify to the right key name. But I don't, so the only way to determine the key is to grub through the .tmpfiles hash ourselves. And finally, one the temp file name is discovered, a filehandle can finally be obtained by (re)opening it. I recommend that this commit be reverted when perl 5.8 is a mercifully faded memory. I'm really, really, really glad I'm actually being paid for working on this right now!
2008-07-09 00:10:05 +02:00
open($fh, "<", $tempfile) || error("failed to open \"$tempfile\": $!");
}
2008-07-01 06:42:42 +02:00
}
binmode($fh);
writefile($filename, $config{srcdir}, undef, 1, sub {
IkiWiki::fast_file_copy($tempfile, $filename, $fh, @_);
});
}
2008-07-01 06:42:42 +02:00
2008-07-01 21:35:01 +02:00
# Check the attachment in and trigger a wiki refresh.
if ($config{rcs}) {
IkiWiki::rcs_add($filename);
IkiWiki::disable_commit_hook();
IkiWiki::rcs_commit($filename, gettext("attachment upload"),
IkiWiki::rcs_prepedit($filename),
$session->param("name"), $ENV{REMOTE_ADDR});
IkiWiki::enable_commit_hook();
IkiWiki::rcs_update();
}
IkiWiki::refresh();
IkiWiki::saveindex();
}
2008-07-02 00:08:31 +02:00
elsif ($form->submitted eq "Insert Links") {
my $page=quotemeta($q->param("page"));
2008-07-02 00:08:31 +02:00
my $add="";
foreach my $f ($q->param("attachment_select")) {
$f=~s/^$page\///;
2008-07-02 00:08:31 +02:00
$add.="[[$f]]\n";
}
$form->field(name => 'editcontent',
value => $form->field('editcontent')."\n\n".$add,
force => 1) if length $add;
2008-07-02 00:08:31 +02:00
}
2008-07-02 01:05:15 +02:00
# Generate the attachment list only after having added any new
# attachments.
$form->tmpl_param("attachment_list" => [attachment_list($form->field('page'))]);
}
sub attachment_location ($) {
2008-07-02 01:05:15 +02:00
my $page=shift;
# Put the attachment in a subdir of the page it's attached
# to, unless that page is an "index" page.
$page=~s/(^|\/)index//;
$page.="/" if length $page;
return $page;
}
2008-07-02 01:05:15 +02:00
sub attachment_list ($) {
2008-07-02 01:05:15 +02:00
my $page=shift;
my $loc=attachment_location($page);
my @ret;
foreach my $f (values %pagesources) {
2008-09-27 19:34:46 +02:00
if (! defined pagetype($f) &&
2008-07-02 01:05:15 +02:00
$f=~m/^\Q$loc\E[^\/]+$/ &&
-e "$config{srcdir}/$f") {
push @ret, {
2008-07-02 22:08:48 +02:00
"field-select" => '<input type="checkbox" name="attachment_select" value="'.$f.'" />',
2008-07-02 01:05:15 +02:00
link => htmllink($page, $page, $f, noimageinline => 1),
size => IkiWiki::Plugin::filecheck::humansize((stat(_))[7]),
2008-07-02 01:05:15 +02:00
mtime => displaytime($IkiWiki::pagemtime{$f}),
};
}
}
# Sort newer attachments to the top of the list, so a newly-added
# attachment appears just before the form used to add it.
return sort { $b->{mtime_raw} <=> $a->{mtime_raw} || $a->{link} cmp $b->{link} } @ret;
}
2008-07-02 01:05:15 +02:00
1