ikiwiki/IkiWiki/Plugin/filecheck.pm

223 lines
5.3 KiB
Perl

#!/usr/bin/perl
package IkiWiki::Plugin::filecheck;
use warnings;
use strict;
use IkiWiki 3.00;
my %units=( # size in bytes
B => 1,
byte => 1,
KB => 2 ** 10,
kilobyte => 2 ** 10,
K => 2 ** 10,
KB => 2 ** 10,
kilobyte => 2 ** 10,
M => 2 ** 20,
MB => 2 ** 20,
megabyte => 2 ** 20,
G => 2 ** 30,
GB => 2 ** 30,
gigabyte => 2 ** 30,
T => 2 ** 40,
TB => 2 ** 40,
terabyte => 2 ** 40,
P => 2 ** 50,
PB => 2 ** 50,
petabyte => 2 ** 50,
E => 2 ** 60,
EB => 2 ** 60,
exabyte => 2 ** 60,
Z => 2 ** 70,
ZB => 2 ** 70,
zettabyte => 2 ** 70,
Y => 2 ** 80,
YB => 2 ** 80,
yottabyte => 2 ** 80,
# ikiwiki, if you find you need larger data quantities, either modify
# yourself to add them, or travel back in time to 2008 and kill me.
# -- Joey
);
sub import {
hook(type => "getsetup", id => "filecheck", call => \&getsetup);
}
sub getsetup () {
return
plugin => {
safe => 1,
rebuild => undef,
section => "misc",
},
}
sub parsesize ($) {
my $size=shift;
no warnings;
my $base=$size+0; # force to number
use warnings;
foreach my $unit (sort keys %units) {
if ($size=~/[0-9\s]\Q$unit\E$/i) {
return $base * $units{$unit};
}
}
return $base;
}
# This is provided for other plugins that want to convert back the other way.
sub humansize ($) {
my $size=shift;
foreach my $unit (reverse sort { $units{$a} <=> $units{$b} || $b cmp $a } keys %units) {
if ($size / $units{$unit} > 0.25) {
return (int($size / $units{$unit} * 10)/10).$unit;
}
}
return $size; # near zero, or negative
}
package IkiWiki::PageSpec;
sub match_maxsize ($$;@) {
my $page=shift;
my $maxsize=eval{IkiWiki::Plugin::filecheck::parsesize(shift)};
if ($@) {
return IkiWiki::ErrorReason->new("unable to parse maxsize (or number too large)");
}
my %params=@_;
my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
if (! defined $file) {
return IkiWiki::ErrorReason->new("file does not exist");
}
if (-s $file > $maxsize) {
return IkiWiki::FailReason->new("file too large (".(-s $file)." > $maxsize)");
}
else {
return IkiWiki::SuccessReason->new("file not too large");
}
}
sub match_minsize ($$;@) {
my $page=shift;
my $minsize=eval{IkiWiki::Plugin::filecheck::parsesize(shift)};
if ($@) {
return IkiWiki::ErrorReason->new("unable to parse minsize (or number too large)");
}
my %params=@_;
my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
if (! defined $file) {
return IkiWiki::ErrorReason->new("file does not exist");
}
if (-s $file < $minsize) {
return IkiWiki::FailReason->new("file too small");
}
else {
return IkiWiki::SuccessReason->new("file not too small");
}
}
sub match_mimetype ($$;@) {
my $page=shift;
my $wanted=shift;
my %params=@_;
my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
if (! defined $file) {
return IkiWiki::ErrorReason->new("file does not exist");
}
# Get the mime type.
#
# First, try File::Mimeinfo. This is fast, but doesn't recognise
# all files.
eval q{use File::MimeInfo::Magic};
my $mimeinfo_ok=! $@;
my $mimetype;
if ($mimeinfo_ok) {
my $mimetype=File::MimeInfo::Magic::magic($file);
}
# Fall back to using file, which has a more complete
# magic database.
if (! defined $mimetype) {
open(my $file_h, "-|", "file", "-bi", $file);
$mimetype=<$file_h>;
close $file_h;
}
if (! defined $mimetype || $mimetype !~s /;.*//) {
# Fall back to default value.
$mimetype=File::MimeInfo::Magic::default($file)
if $mimeinfo_ok;
if (! defined $mimetype) {
$mimetype="unknown";
}
}
my $regexp=IkiWiki::glob2re($wanted);
if ($mimetype!~/^$regexp$/i) {
return IkiWiki::FailReason->new("file MIME type is $mimetype, not $wanted");
}
else {
return IkiWiki::SuccessReason->new("file MIME type is $mimetype");
}
}
sub match_virusfree ($$;@) {
my $page=shift;
my $wanted=shift;
my %params=@_;
my $file=exists $params{file} ? $params{file} : IkiWiki::srcfile($IkiWiki::pagesources{$page});
if (! defined $file) {
return IkiWiki::ErrorReason->new("file does not exist");
}
if (! exists $IkiWiki::config{virus_checker} ||
! length $IkiWiki::config{virus_checker}) {
return IkiWiki::ErrorReason->new("no virus_checker configured");
}
# The file needs to be fed into the virus checker on stdin,
# because the file is not world-readable, and if clamdscan is
# used, clamd would fail to read it.
eval q{use IPC::Open2};
error($@) if $@;
open (IN, "<", $file) || return IkiWiki::ErrorReason->new("failed to read file");
binmode(IN);
my $sigpipe=0;
$SIG{PIPE} = sub { $sigpipe=1 };
my $pid=open2(\*CHECKER_OUT, "<&IN", $IkiWiki::config{virus_checker});
my $reason=<CHECKER_OUT>;
chomp $reason;
1 while (<CHECKER_OUT>);
close(CHECKER_OUT);
waitpid $pid, 0;
$SIG{PIPE}="DEFAULT";
if ($sigpipe || $?) {
if (! length $reason) {
$reason="virus checker $IkiWiki::config{virus_checker}; failed with no output";
}
return IkiWiki::FailReason->new("file seems to contain a virus ($reason)");
}
else {
return IkiWiki::SuccessReason->new("file seems virusfree ($reason)");
}
}
sub match_ispage ($$;@) {
my $filename=shift;
if (defined IkiWiki::pagetype($filename)) {
return IkiWiki::SuccessReason->new("file is a wiki page");
}
else {
return IkiWiki::FailReason->new("file is not a wiki page");
}
}