add a link to libravatar
[ikiwiki] / IkiWiki / Plugin / filecheck.pm
1 #!/usr/bin/perl
2 package IkiWiki::Plugin::filecheck;
3
4 use warnings;
5 use strict;
6 use IkiWiki 3.00;
7
8 my %units=(             # size in bytes
9         B               => 1,
10         byte            => 1,
11         KB              => 2 ** 10,
12         kilobyte        => 2 ** 10,
13         K               => 2 ** 10,
14         KB              => 2 ** 10,
15         kilobyte        => 2 ** 10,
16         M               => 2 ** 20,
17         MB              => 2 ** 20,
18         megabyte        => 2 ** 20,
19         G               => 2 ** 30,
20         GB              => 2 ** 30,
21         gigabyte        => 2 ** 30,
22         T               => 2 ** 40,
23         TB              => 2 ** 40,
24         terabyte        => 2 ** 40,
25         P               => 2 ** 50,
26         PB              => 2 ** 50,
27         petabyte        => 2 ** 50,
28         E               => 2 ** 60,
29         EB              => 2 ** 60,
30         exabyte         => 2 ** 60,
31         Z               => 2 ** 70,
32         ZB              => 2 ** 70,
33         zettabyte       => 2 ** 70,
34         Y               => 2 ** 80,
35         YB              => 2 ** 80,
36         yottabyte       => 2 ** 80,
37         # ikiwiki, if you find you need larger data quantities, either modify
38         # yourself to add them, or travel back in time to 2008 and kill me.
39         #   -- Joey
40 );
41
42 sub import {
43         hook(type => "getsetup", id => "filecheck",  call => \&getsetup);
44 }
45
46 sub getsetup () {
47         return
48                 plugin => {
49                         safe => 1,
50                         rebuild => undef,
51                         section => "misc",
52                 },
53 }
54
55 sub parsesize ($) {
56         my $size=shift;
57
58         no warnings;
59         my $base=$size+0; # force to number
60         use warnings;
61         foreach my $unit (sort keys %units) {
62                 if ($size=~/[0-9\s]\Q$unit\E$/i) {
63                         return $base * $units{$unit};
64                 }
65         }
66         return $base;
67 }
68
69 # This is provided for other plugins that want to convert back the other way.
70 sub humansize ($) {
71         my $size=shift;
72
73         foreach my $unit (reverse sort { $units{$a} <=> $units{$b} || $b cmp $a } keys %units) {
74                 if ($size / $units{$unit} > 0.25) {
75                         return (int($size / $units{$unit} * 10)/10).$unit;
76                 }
77         }
78         return $size; # near zero, or negative
79 }
80
81 package IkiWiki::PageSpec;
82
83 sub match_maxsize ($$;@) {
84         my $page=shift;
85         my $maxsize=eval{IkiWiki::Plugin::filecheck::parsesize(shift)};
86         if ($@) {
87                 return IkiWiki::ErrorReason->new("unable to parse maxsize (or number too large)");
88         }
89
90         my %params=@_;
91         my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
92         if (! defined $file) {
93                 return IkiWiki::ErrorReason->new("file does not exist");
94         }
95
96         if (-s $file > $maxsize) {
97                 return IkiWiki::FailReason->new("file too large (".(-s $file)." >  $maxsize)");
98         }
99         else {
100                 return IkiWiki::SuccessReason->new("file not too large");
101         }
102 }
103
104 sub match_minsize ($$;@) {
105         my $page=shift;
106         my $minsize=eval{IkiWiki::Plugin::filecheck::parsesize(shift)};
107         if ($@) {
108                 return IkiWiki::ErrorReason->new("unable to parse minsize (or number too large)");
109         }
110
111         my %params=@_;
112         my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
113         if (! defined $file) {
114                 return IkiWiki::ErrorReason->new("file does not exist");
115         }
116
117         if (-s $file < $minsize) {
118                 return IkiWiki::FailReason->new("file too small");
119         }
120         else {
121                 return IkiWiki::SuccessReason->new("file not too small");
122         }
123 }
124
125 sub match_mimetype ($$;@) {
126         my $page=shift;
127         my $wanted=shift;
128
129         my %params=@_;
130         my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
131         if (! defined $file) {
132                 return IkiWiki::ErrorReason->new("file does not exist");
133         }
134
135         # Get the mime type.
136         #
137         # First, try File::Mimeinfo. This is fast, but doesn't recognise
138         # all files.
139         eval q{use File::MimeInfo::Magic};
140         my $mimeinfo_ok=! $@;
141         my $mimetype;
142         if ($mimeinfo_ok) {
143                 my $mimetype=File::MimeInfo::Magic::magic($file);
144         }
145
146         # Fall back to using file, which has a more complete
147         # magic database.
148         if (! defined $mimetype) {
149                 open(my $file_h, "-|", "file", "-bi", $file);
150                 $mimetype=<$file_h>;
151                 close $file_h;
152         }
153         if (! defined $mimetype || $mimetype !~s /;.*//) {
154                 # Fall back to default value.
155                 $mimetype=File::MimeInfo::Magic::default($file)
156                         if $mimeinfo_ok;
157                 if (! defined $mimetype) {
158                         $mimetype="unknown";
159                 }
160         }
161
162         my $regexp=IkiWiki::glob2re($wanted);
163         if ($mimetype!~/^$regexp$/i) {
164                 return IkiWiki::FailReason->new("file MIME type is $mimetype, not $wanted");
165         }
166         else {
167                 return IkiWiki::SuccessReason->new("file MIME type is $mimetype");
168         }
169 }
170
171 sub match_virusfree ($$;@) {
172         my $page=shift;
173         my $wanted=shift;
174
175         my %params=@_;
176         my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
177         if (! defined $file) {
178                 return IkiWiki::ErrorReason->new("file does not exist");
179         }
180
181         if (! exists $IkiWiki::config{virus_checker} ||
182             ! length $IkiWiki::config{virus_checker}) {
183                 return IkiWiki::ErrorReason->new("no virus_checker configured");
184         }
185
186         # The file needs to be fed into the virus checker on stdin,
187         # because the file is not world-readable, and if clamdscan is
188         # used, clamd would fail to read it.
189         eval q{use IPC::Open2};
190         error($@) if $@;
191         open (IN, "<", $file) || return IkiWiki::ErrorReason->new("failed to read file");
192         binmode(IN);
193         my $sigpipe=0;
194         $SIG{PIPE} = sub { $sigpipe=1 };
195         my $pid=open2(\*CHECKER_OUT, "<&IN", $IkiWiki::config{virus_checker}); 
196         my $reason=<CHECKER_OUT>;
197         chomp $reason;
198         1 while (<CHECKER_OUT>);
199         close(CHECKER_OUT);
200         waitpid $pid, 0;
201         $SIG{PIPE}="DEFAULT";
202         if ($sigpipe || $?) {
203                 if (! length $reason) {
204                         $reason="virus checker $IkiWiki::config{virus_checker}; failed with no output";
205                 }
206                 return IkiWiki::FailReason->new("file seems to contain a virus ($reason)");
207         }
208         else {
209                 return IkiWiki::SuccessReason->new("file seems virusfree ($reason)");
210         }
211 }
212
213 sub match_ispage ($$;@) {
214         my $filename=shift;
215
216         if (defined IkiWiki::pagetype($filename)) {
217                 return IkiWiki::SuccessReason->new("file is a wiki page");
218         }
219         else {
220                 return IkiWiki::FailReason->new("file is not a wiki page");
221         }
222 }