File Coverage

blib/lib/Brick/Files.pm
Criterion Covered Total %
statement 18 67 26.8
branch 0 26 0.0
condition 0 19 0.0
subroutine 6 14 42.8
pod 0 3 0.0
total 24 129 18.6


line stmt bran cond sub pod time code
1             package Brick::File;
2 5     5   32 use strict;
  5         11  
  5         180  
3              
4 5     5   21 use base qw(Exporter);
  5         8  
  5         469  
5 5     5   25 use vars qw($VERSION);
  5         7  
  5         227  
6              
7             $VERSION = '0.905';
8              
9             package Brick::Bucket;
10 5     5   20 use strict;
  5         8  
  5         142  
11              
12 5     5   20 use Carp qw(croak);
  5         7  
  5         1306  
13              
14             =encoding utf8
15              
16             =head1 NAME
17              
18             Brick::File - This is the description
19              
20             =head1 SYNOPSIS
21              
22             see L
23              
24             =head1 DESCRIPTION
25              
26             See C for the general discussion of constraint
27             creation.
28              
29             =head2 Utilities
30              
31             =over 4
32              
33             =cut
34              
35             # returns MIME type from File::MMagic on success, undef otherwise
36             sub _file_magic_type {
37 0     0     my( $bucket, $file ) = @_;
38              
39 0           require File::MMagic;
40              
41 0           my $mm = File::MMagic->new;
42              
43 0   0       my $format = $mm->checktype_filename( $file || '' );
44              
45             ## File::MMagic returns the illegal "application/msword" for all
46             ## microsoft junk.
47             ## We map this to either application/x-msword (default)
48             ## or application/vnd.ms-excel, depending on the extension
49              
50 0           my( $uploaded_ext ) = $file =~ m/\.(\w*)?$/g;
51              
52 0 0         if( $format eq "application/msword" ) {
    0          
53 5     5   33 no warnings 'uninitialized';
  5         10  
  5         6639  
54              
55 0 0         $format = ($uploaded_ext =~ /^xl[st]$/)
56             ?
57             "application/vnd.ms-excel"
58             :
59             "application/x-msword";
60             }
61             elsif( $format =~ m|x-system/x-error| ) {
62 0           $format = undef;
63             }
64              
65 0           return $format;
66             }
67              
68             =item is_mime_type( HASH_REF )
69              
70             Passes if the file matches one of the listed MIME types.
71              
72             mime_types array reference of possible MIME types
73             file_field the name of the file to check
74              
75             =cut
76              
77             sub is_mime_type {
78 0     0 0   my( $bucket, $setup ) = @_;
79              
80 0           my @caller = $bucket->__caller_chain_as_list;
81              
82 0 0         unless( UNIVERSAL::isa( $setup->{mime_types}, ref [] ) ) {
83 0           croak( "The mime_types key must be an array reference!" );
84             }
85              
86             my $hash = {
87             name => $setup->{name} || $caller[0]{'sub'},
88             description => ( $setup->{description} || "Match a file extension" ),
89             fields => [ $setup->{field} ],
90             code => sub {
91 0     0     my( $input ) = @_;
92              
93             die {
94             message => "[$input->{ $setup->{file_field} }] did not exist.",
95             failed_field => $setup->{file_field},
96             failed_value => $input->{ $setup->{file_field} },
97             handler => $caller[0]{'sub'},
98 0 0         } unless -e $input->{ $setup->{file_field} };
99              
100 0           my $mime_type = $bucket->_file_magic_type( $input->{ $setup->{file_field} } );
101              
102             die {
103             message => "[$input->{ $setup->{file_field} }] did not yeild a mime type.",
104             failed_field => $setup->{file_field},
105             failed_value => $input->{ $setup->{file_field} },
106 0 0         handler => $caller[0]{'sub'},
107             } unless $mime_type;
108              
109 0           foreach my $expected_type ( @{ $setup->{mime_types} } )
  0            
110             {
111 0 0         return 1 if lc $mime_type eq lc $expected_type;
112             }
113              
114             die {
115             message => "[$input->{ $setup->{file_field} }] did not have the right mime type. I think it's $mime_type.",
116             failed_field => $setup->{filename},
117             failed_value => $input->{ $setup->{file_field} },
118 0           handler => $caller[0]{'sub'},
119             };
120             },
121 0   0       };
      0        
122              
123 0           $bucket->__make_constraint(
124             $bucket->add_to_bucket ( $hash )
125             );
126              
127             }
128              
129             =item has_file_extension( HASH_REF )
130              
131             This constraint checks the filename against a list of extensions
132             which are the elements of ARRAY_REF.
133              
134             field the name of the field holding the filename
135             extensions an array reference of possible extensions
136              
137             =cut
138              
139             sub Brick::_get_file_extension { # just a sub, not a method
140 0     0     lc +( split /\./, $_[0] )[-1];
141             }
142              
143             sub has_file_extension {
144 0     0 0   my( $bucket, $setup ) = @_;
145              
146 0           my @caller = $bucket->__caller_chain_as_list;
147              
148 0 0         unless( UNIVERSAL::isa( $setup->{extensions}, ref [] ) )
149             {
150 0           croak( "The extensions key must be an array reference!" );
151             }
152              
153 0           my %extensions = map { lc $_, 1 } @{ $setup->{extensions} };
  0            
  0            
154              
155             my $hash = {
156             name => $setup->{name} || $caller[0]{'sub'},
157             description => ( $setup->{description} || "Match a file extension" ),
158             fields => [ $setup->{field} ],
159             code => sub {
160 0     0     my $extension = Brick::_get_file_extension( $_[0]->{ $setup->{field} } );
161              
162             die {
163             message => "[$_[0]->{ $setup->{field} }] did not have the right extension",
164             failed_field => $setup->{field},
165             failed_value => $_[0]->{ $setup->{field} },
166             handler => $caller[0]{'sub'},
167 0 0         } unless exists $extensions{ $extension };
168             },
169 0   0       };
      0        
170              
171 0           $bucket->__make_constraint(
172             $bucket->add_to_bucket ( $hash )
173             );
174              
175             }
176              
177             =item is_clamav_clean( HASH_REF )
178              
179             Passes if ClamAV doesn't complain about the file.
180              
181             clamscan_location the location of ClamAV, or /usr/local/bin/clamscan
182             filename the filename to check
183              
184             The filename can only contain word characters or a period.
185              
186             =cut
187              
188             sub is_clamav_clean {
189 0     0 0   my( $bucket, $setup ) = @_;
190              
191 0           my @caller = $bucket->__caller_chain_as_list;
192              
193 0   0       my $clamscan = $setup->{clamscan_location} || "/usr/local/bin/clamscan";
194              
195             my $hash = {
196             name => $setup->{name} || $caller[0]{'sub'},
197             description => ( $setup->{description} || "Check for viruses" ),
198             fields => [ $setup->{field} ],
199             code => sub {
200 0     0     my( $input ) = @_;
201              
202             die {
203             message => "Could not find clamscan",
204             failed_field => $setup->{clamscan_location},
205             failed_value => $_[0]->{ $setup->{clamscan_location} },
206 0 0         handler => $caller[0]{'sub'},
207             } unless -x $clamscan;
208              
209             die {
210             message => "File name has odd characters",
211             failed_field => $setup->{filename},
212             failed_value => $_[0]->{ $setup->{filename} },
213             handler => $caller[0]{'sub'},
214 0 0         } unless $setup->{filename} =~ m/^[\w.]+\z/;
215              
216             die {
217             message => "Could not find file to check for viruses",
218             failed_field => $setup->{filename},
219             failed_value => $_[0]->{ $setup->{filename} },
220             handler => $caller[0]{'sub'},
221 0 0         } unless -f $setup->{filename};
222              
223 0           my $results = do {
224 0           local $ENV{PATH} = '';
225              
226 0           `$clamscan --no-summary -i --stdout $setup->{filename}`;
227             };
228              
229             die {
230             message => "ClamAV complained: $results",
231             failed_field => $setup->{filename},
232             failed_value => $_[0]->{ $setup->{filename} },
233 0 0         handler => $caller[0]{'sub'},
234             } if $results;
235              
236 0           1;
237             },
238 0   0       };
      0        
239              
240 0           $bucket->__make_constraint(
241             $bucket->add_to_bucket ( $hash )
242             );
243              
244             }
245              
246             =pod
247              
248             sub file_clamav_clean {
249             my $clamscan = "/usr/local/bin/clamscan";
250              
251             return sub {
252             my $dfv = shift;
253             $dfv->name_this('file_clamav_clean');
254             my $q = $dfv->get_input_data;
255              
256             # Set $ENV{PATH} to the empty string to avoid taint error from
257             # exec call. Use local to temporarily clear it out in the context
258             # of this sub.
259             local $ENV{PATH} = q{};
260              
261              
262             $q->UNIVERSAL::can('param') or
263             die 'valid_file_clamav_clean: data object missing param() method';
264              
265             my $field = $dfv->get_current_constraint_field;
266              
267             my $img = $q->upload($field);
268              
269             if (not $img and my $err = $q->cgi_error) {
270             warn $err;
271             return undef;
272             }
273              
274             my $tmp_file = $q->tmpFileName($q->param($field)) or
275             (warn "$0: can't find tmp file for field named $field"),
276             return undef;
277              
278             ## now return true if $tmp_file is not a virus, false otherwise
279             unless (-x $clamscan) {
280             warn "$0: can't find clamscan, skipping test";
281             return 1; # it's valid because we don't see it
282             }
283              
284             defined (my $pid = open KID, "-|") or die "Can't fork: $!";
285             unless ($pid) { # child does:
286             open STDIN, "<$tmp_file" or die "Cannot open $tmp_file for input: $!";
287             exec $clamscan, qw(--no-summary -i --stdout -);
288             die "Cannot find $clamscan: $!";
289             }
290             ## parent does:
291             my $results = join '', ;
292             close KID;
293             return if $results; ## if clamscan spoke, it's a virus
294              
295             return 1;
296             };
297             }
298              
299             =back
300              
301             =head1 TO DO
302              
303             Regex::Common support
304              
305             =head1 SEE ALSO
306              
307             TBA
308              
309             =head1 SOURCE AVAILABILITY
310              
311             This source is in Github:
312              
313             https://github.com/briandfoy/brick
314              
315             =head1 AUTHOR
316              
317             brian d foy, C<< >>
318              
319             =head1 COPYRIGHT
320              
321             Copyright © 2007-2026, brian d foy . All rights reserved.
322              
323             You may redistribute this under the terms of the Artistic License 2.0.
324              
325             =cut
326              
327             1;