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