Merge branch 'master' of ssh://git.ikiwiki.info/srv/git/ikiwiki.info
[ikiwiki.git] / IkiWiki / Plugin / filecheck.pm
blob4f4e67489a41c51ecae344119362f0ea611d481a
1 #!/usr/bin/perl
2 package IkiWiki::Plugin::filecheck;
4 use warnings;
5 use strict;
6 use IkiWiki 3.00;
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
42 sub import {
43 hook(type => "getsetup", id => "filecheck", call => \&getsetup);
46 sub getsetup () {
47 return
48 plugin => {
49 safe => 1,
50 rebuild => undef,
51 section => "misc",
55 sub parsesize ($) {
56 my $size=shift;
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};
66 return $base;
69 # This is provided for other plugins that want to convert back the other way.
70 sub humansize ($) {
71 my $size=shift;
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;
78 return $size; # near zero, or negative
81 package IkiWiki::PageSpec;
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)");
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");
96 if (-s $file > $maxsize) {
97 return IkiWiki::FailReason->new("file too large (".(-s $file)." > $maxsize)");
99 else {
100 return IkiWiki::SuccessReason->new("file not too large");
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)");
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");
117 if (-s $file < $minsize) {
118 return IkiWiki::FailReason->new("file too small");
120 else {
121 return IkiWiki::SuccessReason->new("file not too small");
125 sub match_mimetype ($$;@) {
126 my $page=shift;
127 my $wanted=shift;
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");
135 # Get the mime type.
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);
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 chomp $mimetype;
152 close $file_h;
154 if (! defined $mimetype || $mimetype !~s /;.*//) {
155 # Fall back to default value.
156 $mimetype=File::MimeInfo::Magic::default($file)
157 if $mimeinfo_ok;
158 if (! defined $mimetype) {
159 $mimetype="unknown";
163 my $regexp=IkiWiki::glob2re($wanted);
164 if ($mimetype!~$regexp) {
165 return IkiWiki::FailReason->new("file MIME type is $mimetype, not $wanted");
167 else {
168 return IkiWiki::SuccessReason->new("file MIME type is $mimetype");
172 sub match_virusfree ($$;@) {
173 my $page=shift;
174 my $wanted=shift;
176 my %params=@_;
177 my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
178 if (! defined $file) {
179 return IkiWiki::ErrorReason->new("file does not exist");
182 if (! exists $IkiWiki::config{virus_checker} ||
183 ! length $IkiWiki::config{virus_checker}) {
184 return IkiWiki::ErrorReason->new("no virus_checker configured");
187 # The file needs to be fed into the virus checker on stdin,
188 # because the file is not world-readable, and if clamdscan is
189 # used, clamd would fail to read it.
190 eval q{use IPC::Open2};
191 error($@) if $@;
192 open (IN, "<", $file) || return IkiWiki::ErrorReason->new("failed to read file");
193 binmode(IN);
194 my $sigpipe=0;
195 $SIG{PIPE} = sub { $sigpipe=1 };
196 my $pid=open2(\*CHECKER_OUT, "<&IN", $IkiWiki::config{virus_checker});
197 my $reason=<CHECKER_OUT>;
198 chomp $reason;
199 1 while (<CHECKER_OUT>);
200 close(CHECKER_OUT);
201 waitpid $pid, 0;
202 $SIG{PIPE}="DEFAULT";
203 if ($sigpipe || $?) {
204 if (! length $reason) {
205 $reason="virus checker $IkiWiki::config{virus_checker}; failed with no output";
207 return IkiWiki::FailReason->new("file seems to contain a virus ($reason)");
209 else {
210 return IkiWiki::SuccessReason->new("file seems virusfree ($reason)");
214 sub match_ispage ($$;@) {
215 my $filename=shift;
217 if (defined IkiWiki::pagetype($filename)) {
218 return IkiWiki::SuccessReason->new("file is a wiki page");
220 else {
221 return IkiWiki::FailReason->new("file is not a wiki page");