save attachments when page is saved too
[ikiwiki] / IkiWiki / Plugin / attachment.pm
1 #!/usr/bin/perl
2 package IkiWiki::Plugin::attachment;
3
4 use warnings;
5 use strict;
6 use IkiWiki 2.00;
7
8 sub import { #{{{
9         hook(type => "checkconfig", id => "attachment", call => \&checkconfig);
10         hook(type => "formbuilder_setup", id => "attachment", call => \&formbuilder_setup);
11         hook(type => "formbuilder", id => "attachment", call => \&formbuilder);
12 } # }}}
13
14 sub checkconfig () { #{{{
15         $config{cgi_disable_uploads}=0;
16 } #}}}
17
18 sub formbuilder_setup (@) { #{{{
19         my %params=@_;
20         my $form=$params{form};
21
22         if ($form->field("do") eq "edit") {
23                 $form->field(name => 'attachment', type => 'file');
24         }
25         elsif ($form->title eq "preferences") {
26                 my $session=$params{session};
27                 my $user_name=$session->param("name");
28
29                 $form->field(name => "allowed_attachments", size => 50,
30                         fieldset => "admin",
31                         comment => "(".htmllink("", "", "ikiwiki/PageSpec", noimageinline => 1).")");
32                 if (! IkiWiki::is_admin($user_name)) {
33                         $form->field(name => "allowed_attachments", type => "hidden");
34                 }
35                 if (! $form->submitted) {
36                         $form->field(name => "allowed_attachments", force => 1,
37                                 value => IkiWiki::userinfo_get($user_name, "allowed_attachments"));
38                 }
39                 if ($form->submitted && $form->submitted eq 'Save Preferences') {
40                         if (defined $form->field("allowed_attachments")) {
41                                 IkiWiki::userinfo_set($user_name, "allowed_attachments",
42                                 $form->field("allowed_attachments")) ||
43                                         error("failed to set allowed_attachments");
44                         }
45                 }
46         }
47 } #}}}
48
49 sub formbuilder (@) { #{{{
50         my %params=@_;
51         my $form=$params{form};
52
53         return if $form->field("do") ne "edit";
54
55         if ($form->submitted eq "Upload" || $form->submitted eq "Save Page") {
56                 my $q=$params{cgi};
57                 my $session=$params{session};
58
59                 my $filename=$q->param('attachment');
60                 if (! defined $filename || ! length $filename) {
61                         # no file, so do nothing
62                         return;
63                 }
64                 
65                 # This is an (apparently undocumented) way to get the name
66                 # of the temp file that CGI writes the upload to.
67                 my $tempfile=$q->tmpFileName($filename);
68                 
69                 # Put the attachment in a subdir of the page it's attached
70                 # to, unless that page is an "index" page.
71                 my $page=$form->field('page');
72                 $page=~s/(^|\/)index//;
73                 $filename=(length $page ? $page."/" : "").IkiWiki::basename($filename);
74                 
75                 # To untaint the filename, escape any hazardous characters,
76                 # and make sure it isn't pruned.
77                 $filename=IkiWiki::titlepage(IkiWiki::possibly_foolish_untaint($filename));
78                 if (IkiWiki::file_pruned($filename, $config{srcdir})) {
79                         error(gettext("bad attachment filename"));
80                 }
81                 
82                 # Check that the user is allowed to edit a page with the
83                 # name of the attachment.
84                 IkiWiki::check_canedit($filename, $q, $session, 1);
85                 
86                 # Use a special pagespec to test that the attachment is valid.
87                 my $allowed=1;
88                 foreach my $admin (@{$config{adminuser}}) {
89                         my $allowed_attachments=IkiWiki::userinfo_get($admin, "allowed_attachments");
90                         if (defined $allowed_attachments &&
91                             length $allowed_attachments) {
92                                 $allowed=pagespec_match($filename,
93                                         $allowed_attachments,
94                                         file => $tempfile);
95                                 last if $allowed;
96                         }
97                 }
98                 if (! $allowed) {
99                         error(gettext("attachment rejected")." ($allowed)");
100                 }
101
102                 # Needed for fast_file_copy and for rendering below.
103                 require IkiWiki::Render;
104
105                 # Move the attachment into place.
106                 # Try to use a fast rename; fall back to copying.
107                 IkiWiki::prep_writefile($filename, $config{srcdir});
108                 unlink($config{srcdir}."/".$filename);
109                 if (! rename($tempfile, $config{srcdir}."/".$filename)) {
110                         my $fh=$q->upload('attachment');
111                         if (! defined $fh || ! ref $fh) {
112                                 error("failed to get filehandle");
113                         }
114                         binmode($fh);
115                         writefile($filename, $config{srcdir}, undef, 1, sub {
116                                 IkiWiki::fast_file_copy($tempfile, $filename, $fh, @_);
117                         });
118                 }
119
120                 # Check the attachment in and trigger a wiki refresh.
121                 if ($config{rcs}) {
122                         IkiWiki::rcs_add($filename);
123                         IkiWiki::disable_commit_hook();
124                         IkiWiki::rcs_commit($filename, gettext("attachment upload"),
125                                 IkiWiki::rcs_prepedit($filename),
126                                 $session->param("name"), $ENV{REMOTE_ADDR});
127                         IkiWiki::enable_commit_hook();
128                         IkiWiki::rcs_update();
129                 }
130                 IkiWiki::refresh();
131                 IkiWiki::saveindex();
132         }
133 } # }}}
134
135 package IkiWiki::PageSpec;
136
137 sub parsesize ($) { #{{{
138         my $size=shift;
139         no warnings;
140         my $base=$size+0; # force to number
141         use warnings;
142         my $multiple=1;
143         if ($size=~/kb?$/i) {
144                 $multiple=2**10;
145         }
146         elsif ($size=~/mb?$/i) {
147                 $multiple=2**20;
148         }
149         elsif ($size=~/gb?$/i) {
150                 $multiple=2**30;
151         }
152         elsif ($size=~/tb?$/i) {
153                 $multiple=2**40;
154         }
155         return $base * $multiple;
156 } #}}}
157
158 sub match_maxsize ($$;@) { #{{{
159         shift;
160         my $maxsize=eval{parsesize(shift)};
161         if ($@) {
162                 return IkiWiki::FailReason->new("unable to parse maxsize (or number too large)");
163         }
164
165         my %params=@_;
166         if (! exists $params{file}) {
167                 return IkiWiki::FailReason->new("no file specified");
168         }
169
170         if (-s $params{file} > $maxsize) {
171                 return IkiWiki::FailReason->new("file too large");
172         }
173         else {
174                 return IkiWiki::SuccessReason->new("file not too large");
175         }
176 } #}}}
177
178 sub match_minsize ($$;@) { #{{{
179         shift;
180         my $minsize=eval{parsesize(shift)};
181         if ($@) {
182                 return IkiWiki::FailReason->new("unable to parse minsize (or number too large)");
183         }
184
185         my %params=@_;
186         if (! exists $params{file}) {
187                 return IkiWiki::FailReason->new("no file specified");
188         }
189
190         if (-s $params{file} < $minsize) {
191                 return IkiWiki::FailReason->new("file too small");
192         }
193         else {
194                 return IkiWiki::SuccessReason->new("file not too small");
195         }
196 } #}}}
197
198 sub match_ispage ($$;@) { #{{{
199         my $filename=shift;
200
201         if (defined IkiWiki::pagetype($filename)) {
202                 return IkiWiki::SuccessReason->new("file is a wiki page");
203         }
204         else {
205                 return IkiWiki::FailReason->new("file is not a wiki page");
206         }
207 } #}}}
208
209 1