2 package IkiWiki::Plugin::filecheck;
8 my %units=( # size in bytes
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.
43 hook(type => "getsetup", id => "filecheck", call => \&getsetup);
58 my $base=$size+0; # force to number
60 foreach my $unit (sort keys %units) {
61 if ($size=~/[0-9\s]\Q$unit\E$/i) {
62 return $base * $units{$unit};
68 # This is provided for other plugins that want to convert back the other way.
72 foreach my $unit (reverse sort { $units{$a} <=> $units{$b} || $b cmp $a } keys %units) {
73 if ($size / $units{$unit} > 0.25) {
74 return (int($size / $units{$unit} * 10)/10).$unit;
77 return $size; # near zero, or negative
80 package IkiWiki::PageSpec;
82 sub match_maxsize ($$;@) {
84 my $maxsize=eval{IkiWiki::Plugin::filecheck::parsesize(shift)};
86 return IkiWiki::ErrorReason->new("unable to parse maxsize (or number too large)");
90 my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
91 if (! defined $file) {
92 return IkiWiki::ErrorReason->new("file does not exist");
95 if (-s $file > $maxsize) {
96 return IkiWiki::FailReason->new("file too large (".(-s $file)." > $maxsize)");
99 return IkiWiki::SuccessReason->new("file not too large");
103 sub match_minsize ($$;@) {
105 my $minsize=eval{IkiWiki::Plugin::filecheck::parsesize(shift)};
107 return IkiWiki::ErrorReason->new("unable to parse minsize (or number too large)");
111 my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
112 if (! defined $file) {
113 return IkiWiki::ErrorReason->new("file does not exist");
116 if (-s $file < $minsize) {
117 return IkiWiki::FailReason->new("file too small");
120 return IkiWiki::SuccessReason->new("file not too small");
124 sub match_mimetype ($$;@) {
129 my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
130 if (! defined $file) {
131 return IkiWiki::ErrorReason->new("file does not exist");
136 # First, try File::Mimeinfo. This is fast, but doesn't recognise
138 eval q{use File::MimeInfo::Magic};
139 my $mimeinfo_ok=! $@;
142 $mimetype=File::MimeInfo::Magic::magic($file);
145 # Fall back to using file, which has a more complete
147 if (! defined $mimetype) {
148 open(my $file_h, "-|", "file", "-bi", $file);
153 if (! defined $mimetype) {
154 # Fall back to default value.
155 $mimetype=File::MimeInfo::Magic::default($file)
157 if (! defined $mimetype) {
161 # Ignore any parameters, we only want the type itself
162 $mimetype =~ s/;.*//;
164 my $regexp=IkiWiki::glob2re($wanted);
165 if ($mimetype!~$regexp) {
166 return IkiWiki::FailReason->new("file MIME type is $mimetype, not $wanted");
169 return IkiWiki::SuccessReason->new("file MIME type is $mimetype");
173 sub match_virusfree ($$;@) {
178 my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
179 if (! defined $file) {
180 return IkiWiki::ErrorReason->new("file does not exist");
183 if (! exists $IkiWiki::config{virus_checker} ||
184 ! length $IkiWiki::config{virus_checker}) {
185 return IkiWiki::ErrorReason->new("no virus_checker configured");
188 # The file needs to be fed into the virus checker on stdin,
189 # because the file is not world-readable, and if clamdscan is
190 # used, clamd would fail to read it.
191 eval q{use IPC::Open2};
193 open (IN, "<", $file) || return IkiWiki::ErrorReason->new("failed to read file");
196 $SIG{PIPE} = sub { $sigpipe=1 };
197 my $pid=open2(\*CHECKER_OUT, "<&IN", $IkiWiki::config{virus_checker});
198 my $reason=<CHECKER_OUT>;
200 1 while (<CHECKER_OUT>);
203 $SIG{PIPE}="DEFAULT";
204 if ($sigpipe || $?) {
205 if (! length $reason) {
206 $reason="virus checker $IkiWiki::config{virus_checker}; failed with no output";
208 return IkiWiki::FailReason->new("file seems to contain a virus ($reason)");
211 return IkiWiki::SuccessReason->new("file seems virusfree ($reason)");
215 sub match_ispage ($$;@) {
218 if (defined IkiWiki::pagetype($filename)) {
219 return IkiWiki::SuccessReason->new("file is a wiki page");
222 return IkiWiki::FailReason->new("file is not a wiki page");