File Coverage

blib/lib/Brick/Files.pm
Criterion Covered Total %
statement 18 72 25.0
branch 0 28 0.0
condition 0 21 0.0
subroutine 6 15 40.0
pod 0 3 0.0
total 24 139 17.2


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