humansize moved to filecheck
[ikiwiki.git] / IkiWiki / Plugin / filecheck.pm
1 #!/usr/bin/perl
2 package IkiWiki::Plugin::filecheck;
3
4 use warnings;
5 use strict;
6 use IkiWiki 2.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 parsesize ($) { #{{{
43         my $size=shift;
44
45         no warnings;
46         my $base=$size+0; # force to number
47         use warnings;
48         foreach my $unit (sort keys %units) {
49                 if ($size=~/[0-9\s]\Q$unit\E$/i) {
50                         return $base * $units{$unit};
51                 }
52         }
53         return $base;
54 } #}}}
55
56 # This is provided for other plugins that want to convert back the other way.
57 sub humansize ($) { #{{{
58         my $size=shift;
59
60         foreach my $unit (reverse sort { $units{$a} <=> $units{$b} || $b cmp $a } keys %units) {
61                 if ($size / $units{$unit} > 0.25) {
62                         return (int($size / $units{$unit} * 10)/10).$unit;
63                 }
64         }
65         return $size; # near zero, or negative
66 } #}}}
67
68 package IkiWiki::PageSpec;
69
70 sub match_maxsize ($$;@) { #{{{
71         my $page=shift;
72         my $maxsize=eval{IkiWiki::Plugin::attachment::parsesize(shift)};
73         if ($@) {
74                 return IkiWiki::FailReason->new("unable to parse maxsize (or number too large)");
75         }
76
77         my %params=@_;
78         my $file=exists $params{file} ? $params{file} : $IkiWiki::pagesources{$page};
79         if (! defined $file) {
80                 return IkiWiki::FailReason->new("no file specified");
81         }
82
83         if (-s $file > $maxsize) {
84                 return IkiWiki::FailReason->new("file too large (".(-s $file)." >  $maxsize)");
85         }
86         else {
87                 return IkiWiki::SuccessReason->new("file not too large");
88         }
89 } #}}}
90
91 sub match_minsize ($$;@) { #{{{
92         my $page=shift;
93         my $minsize=eval{IkiWiki::Plugin::attachment::parsesize(shift)};
94         if ($@) {
95                 return IkiWiki::FailReason->new("unable to parse minsize (or number too large)");
96         }
97
98         my %params=@_;
99         my $file=exists $params{file} ? $params{file} : $IkiWiki::pagesources{$page};
100         if (! defined $file) {
101                 return IkiWiki::FailReason->new("no file specified");
102         }
103
104         if (-s $file < $minsize) {
105                 return IkiWiki::FailReason->new("file too small");
106         }
107         else {
108                 return IkiWiki::SuccessReason->new("file not too small");
109         }
110 } #}}}
111
112 sub match_mimetype ($$;@) { #{{{
113         my $page=shift;
114         my $wanted=shift;
115
116         my %params=@_;
117         my $file=exists $params{file} ? $params{file} : $IkiWiki::pagesources{$page};
118         if (! defined $file) {
119                 return IkiWiki::FailReason->new("no file specified");
120         }
121
122         # Use ::magic to get the mime type, the idea is to only trust
123         # data obtained by examining the actual file contents.
124         eval q{use File::MimeInfo::Magic};
125         if ($@) {
126                 return IkiWiki::FailReason->new("failed to load File::MimeInfo::Magic ($@); cannot check MIME type");
127         }
128         my $mimetype=File::MimeInfo::Magic::magic($file);
129         if (! defined $mimetype) {
130                 $mimetype="unknown";
131         }
132
133         my $regexp=IkiWiki::glob2re($wanted);
134         if ($mimetype!~/^$regexp$/i) {
135                 return IkiWiki::FailReason->new("file MIME type is $mimetype, not $wanted");
136         }
137         else {
138                 return IkiWiki::SuccessReason->new("file MIME type is $mimetype");
139         }
140 } #}}}
141
142 sub match_virusfree ($$;@) { #{{{
143         my $page=shift;
144         my $wanted=shift;
145
146         my %params=@_;
147         my $file=exists $params{file} ? $params{file} : $IkiWiki::pagesources{$page};
148         if (! defined $file) {
149                 return IkiWiki::FailReason->new("no file specified");
150         }
151
152         if (! exists $IkiWiki::config{virus_checker} ||
153             ! length $IkiWiki::config{virus_checker}) {
154                 return IkiWiki::FailReason->new("no virus_checker configured");
155         }
156
157         # The file needs to be fed into the virus checker on stdin,
158         # because the file is not world-readable, and if clamdscan is
159         # used, clamd would fail to read it.
160         eval q{use IPC::Open2};
161         error($@) if $@;
162         open (IN, "<", $file) || return IkiWiki::FailReason->new("failed to read file");
163         binmode(IN);
164         my $sigpipe=0;
165         $SIG{PIPE} = sub { $sigpipe=1 };
166         my $pid=open2(\*CHECKER_OUT, "<&IN", $IkiWiki::config{virus_checker}); 
167         my $reason=<CHECKER_OUT>;
168         chomp $reason;
169         1 while (<CHECKER_OUT>);
170         close(CHECKER_OUT);
171         waitpid $pid, 0;
172         $SIG{PIPE}="DEFAULT";
173         if ($sigpipe || $?) {
174                 if (! length $reason) {
175                         $reason="virus checker $IkiWiki::config{virus_checker}; failed with no output";
176                 }
177                 return IkiWiki::FailReason->new("file seems to contain a virus ($reason)");
178         }
179         else {
180                 return IkiWiki::SuccessReason->new("file seems virusfree ($reason)");
181         }
182 } #}}}
183
184 sub match_ispage ($$;@) { #{{{
185         my $filename=shift;
186
187         if (defined IkiWiki::pagetype($filename)) {
188                 return IkiWiki::SuccessReason->new("file is a wiki page");
189         }
190         else {
191                 return IkiWiki::FailReason->new("file is not a wiki page");
192         }
193 } #}}}