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