File Coverage

blib/lib/Data/FormValidator/Constraints/Upload.pm
Criterion Covered Total %
statement 19 164 11.5
branch 0 52 0.0
condition 1 45 2.2
subroutine 5 20 25.0
pod 4 8 50.0
total 29 289 10.0


line stmt bran cond sub pod time code
1             package Data::FormValidator::Constraints::Upload;
2 1     1   640 use Exporter 'import';
  1         1  
  1         54  
3 1     1   5 use strict;
  1         1  
  1         519  
4              
5             # Items to export into callers namespace by default. Note: do not export
6             # names by default without a very good reason. Use EXPORT_OK instead.
7             # Do not simply export all your public functions/methods/constants.
8              
9             # This allows declaration use Data::FormValidator::Constraints::Upload ':all';
10             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
11             # will save memory.
12              
13             our @EXPORT = qw(
14             valid_file_format
15             valid_image_max_dimensions
16             valid_file_max_bytes
17             valid_image_min_dimensions
18             );
19              
20             our @EXPORT_OK = qw(
21             file_format
22             image_max_dimensions
23             file_max_bytes
24             image_min_dimensions
25             );
26              
27             our $VERSION = 4.85;
28              
29             sub file_format {
30 0     0 1 0 my %params = @_;
31             return sub {
32 0     0   0 my $self = shift;
33 0         0 $self->set_current_constraint_name('file_format');
34 0         0 valid_file_format($self,\%params);
35             }
36 0         0 }
37              
38             sub image_max_dimensions {
39 0   0 0 1 0 my $w = shift || die 'image_max_dimensions: missing maximum width value';
40 0   0     0 my $h = shift || die 'image_max_dimensions: missing maximum height value';
41             return sub {
42 0     0   0 my $self = shift;
43 0         0 $self->set_current_constraint_name('image_max_dimensions');
44 0         0 valid_image_max_dimensions($self,\$w,\$h);
45             }
46 0         0 }
47              
48             sub file_max_bytes {
49 0     0 1 0 my ($max_bytes) = @_;
50             return sub {
51 0     0   0 my $self = shift;
52 0         0 $self->set_current_constraint_name('file_max_bytes');
53 0         0 valid_file_max_bytes($self,\$max_bytes);
54             }
55 0         0 }
56              
57             sub image_min_dimensions {
58 0   0 0 1 0 my $w = shift || die 'image_min_dimensions: missing minimum width value';
59 0   0     0 my $h = shift || die 'image_min_dimensions: missing minimum height value';
60             return sub {
61 0     0   0 my $self = shift;
62 0         0 $self->set_current_constraint_name('image_min_dimensions');
63 0         0 valid_image_min_dimensions($self,\$w,\$h);
64             }
65 0         0 }
66              
67             sub valid_file_format {
68 0     0 0 0 my $self = shift;
69 0 0       0 $self->isa('Data::FormValidator::Results') ||
70             die "file_format: first argument is not a Data::FormValidator::Results object. ";
71 0   0     0 my $params = shift || {};
72             # if (ref $params ne 'HASH' ) {
73             # die "format: hash reference expected. Make sure you have
74             # included 'params => []' in your constraint definition, even if there
75             # are no additional arguments";
76             # }
77 0         0 my $q = $self->get_filtered_data;
78              
79 0         0 my $field = $self->get_current_constraint_field;
80 0         0 my $fh = _get_upload_fh($self);
81              
82             ## load filehandle
83 0 0       0 if (!$fh) {
84 0 0       0 warn "$0: can't get filehandle for field named $field" and return undef;
85             }
86              
87             ## load file magic stuff
88 0         0 require File::MMagic;
89 0         0 my $mm = File::MMagic->new;
90 0         0 my $fm_mt;
91              
92             ## only use filehandle bits for magic data
93 0   0     0 $fm_mt = $mm->checktype_filehandle($fh) ||
94             (warn "$0: can't get filehandle for field named $field" and return undef);
95             # Work around a bug in File::MMagic (RT#12074)
96 0         0 seek($fh,0,0);
97              
98             # File::MMagic returns 'application/octet-stream" as a punt
99             # for "I don't know, here's a generic binary MIME type.
100             # In some cases that is has indicated a bug in File::MMagic,
101             # but it's a generally worthless response for identifying the file type.
102             # so, we throw away the response in that case. The uploaded MIME type
103             # will be used instead later, if present
104 0 0       0 $fm_mt = undef if ($fm_mt eq 'application/octet-stream');
105              
106              
107             ## fetch mime type universally (or close)
108 0         0 my $uploaded_mt = _get_upload_mime_type($self);
109              
110             # try the File::MMagic, then the uploaded field, then return undef we find neither
111 0 0 0     0 my $mt = ($fm_mt || $uploaded_mt) or return undef;
112              
113             # figure out an extension
114 1     1   444 use MIME::Types;
  1         3571  
  1         656  
115 0         0 my $mimetypes = MIME::Types->new;
116 0         0 my MIME::Type $t = $mimetypes->type($mt);
117 0 0       0 my @mt_exts = $t ? $t->extensions : ();
118              
119             ## setup filename to retrieve extension
120 0         0 my $fn = $self->get_input_data->param($field);
121 0         0 my ($uploaded_ext) = ($fn =~ m/\.([\w\d]*)?$/);
122 0         0 my $ext;
123              
124 0 0       0 if (scalar @mt_exts) {
125             # If the upload extension is one recognized by MIME::Type, use it.
126 0 0       0 if (grep {/^$uploaded_ext$/} @mt_exts) {
  0         0  
127 0         0 $ext = $uploaded_ext;
128             }
129             # otherwise, use one from MIME::Type, just to be safe
130             else {
131 0         0 $ext = $mt_exts[0];
132             }
133             }
134             else {
135             # If is a provided extension but no MIME::Type extension, use that.
136             # It's possible that there no extension uploaded or found)
137 0         0 $ext = $uploaded_ext;
138             }
139              
140             # Add the mime_type and extension to the valid data set
141 0   0     0 my $info = $self->meta($field) || {};
142 0         0 $info = { %$info, mime_type => $mt, extension => ".$ext" };
143 0         0 $self->meta($field,$info);
144              
145 0         0 return _is_allowed_type($mt, $params);
146             }
147              
148             ## Returns true if the passed-in mime-type matches our allowed types
149             sub _is_allowed_type {
150 4     4   351 my $mt = shift;
151 4         30 my $params = shift;
152              
153             # XXX perhaps this should be in a global variable so it's easier
154             # for other apps to change the defaults;
155 4   50     25 $params->{mime_types} ||= [qw!image/jpeg image/pjpeg image/gif image/png!];
156 4         5 my %allowed_types = map { $_ => 1 } @{ $params->{mime_types} };
  16         23  
  4         8  
157              
158 4         24 return $allowed_types{lc $mt};
159             }
160              
161              
162             sub valid_image_max_dimensions {
163 0     0 0   my $self = shift;
164 0 0         $self->isa('Data::FormValidator::Results') ||
165             die "image_max_dimensions: first argument is not a Data::FormValidator::Results object. ";
166 0   0       my $max_width_ref = shift || die 'image_max_dimensions: missing maximum width value';
167 0   0       my $max_height_ref = shift || die 'image_max_dimensions: missing maximum height value';
168 0           my $max_width = $$max_width_ref;
169 0           my $max_height = $$max_height_ref;
170 0 0         ($max_width > 0) || die 'image_max_dimensions: maximum width must be > 0';
171 0 0         ($max_height > 0) || die 'image_max_dimensions: maximum height must be > 0';
172              
173 0           my $q = $self->get_filtered_data;
174 0           my $field = $self->get_current_constraint_field;
175 0           my ($width,$height) = _get_img_size($self);
176              
177 0 0         unless ($width) {
178 0           warn "$0: imgsize test failed";
179 0           return undef;
180             }
181              
182             # Add the dimensions to the valid hash
183 0   0       my $info = $self->meta($field) || {};
184 0           $info = { %$info, width => $width, height => $height };
185 0           $self->meta($field,$info);
186              
187 0   0       return (($width <= $$max_width_ref) and ($height <= $$max_height_ref));
188             }
189              
190             sub valid_file_max_bytes {
191 0     0 0   my $self = shift;
192              
193 0 0         $self->isa('Data::FormValidator::Results') ||
194             die "first argument is not a Data::FormValidator::Results object.";
195              
196 0           my $max_bytes_ref = shift;
197 0           my $max_bytes;
198              
199 0 0 0       if ((ref $max_bytes_ref) and defined $$max_bytes_ref) {
200 0           $max_bytes = $$max_bytes_ref;
201             }
202             else {
203 0           $max_bytes = 1024*1024; # default to 1 Meg
204             }
205              
206 0           my $q = $self->get_filtered_data;
207              
208 0           my $field = $self->get_current_constraint_field;
209              
210             ## retrieve upload fh for field
211 0           my $fh = _get_upload_fh($self);
212 0 0 0       if (!$fh) { warn "Failed to load filehandle for $field" && return undef; }
  0            
213              
214             ## retrieve size
215 0           my $file_size = (stat ($fh))[7];
216              
217             # Add the size to the valid hash
218 0   0       my $info = $self->meta($field) || {};
219 0           $info = { %$info, bytes => $file_size };
220 0           $self->meta($field,$info);
221              
222 0           return ($file_size <= $max_bytes);
223             }
224              
225             sub valid_image_min_dimensions {
226 0     0 0   my $self = shift;
227 0 0         $self->isa('Data::FormValidator::Results') ||
228             die "image_min_dimensions: first argument is not a Data::FormValidator::Results object. ";
229 0   0       my $min_width_ref = shift ||
230             die 'image_min_dimensions: missing minimum width value';
231 0   0       my $min_height_ref = shift ||
232             die 'image_min_dimensions: missing minimum height value';
233 0           my $min_width = $$min_width_ref;
234 0           my $min_height = $$min_height_ref;
235              
236             ## do these matter?
237 0 0         ($min_width > 0) || die 'image_min_dimensions: minimum width must be > 0';
238 0 0         ($min_height > 0) || die 'image_min_dimensions: minimum height must be > 0';
239              
240 0           my $q = $self->get_filtered_data;
241 0           my $field = $self->get_current_constraint_field;
242 0           my ($width, $height) = _get_img_size($self);
243              
244 0 0         unless ($width) {
245 0           warn "image failed processing";
246 0           return undef;
247             }
248              
249             # Add the dimensions to the valid hash
250 0   0       my $info = $self->meta($field) || {};
251 0           $info = { %$info, width => $width, height => $height };
252 0           $self->meta($field,$info);
253              
254 0   0       return (($width >= $min_width) and ($height >= $min_height));
255             }
256              
257             sub _get_img_size
258             {
259 0     0     my $self = shift;
260 0           my $q = $self->get_filtered_data;
261              
262             ## setup caller to make can errors more useful
263 0           my $caller = (caller(1))[3];
264 0           my $pkg = __PACKAGE__ . "::";
265 0           $caller =~ s/$pkg//g;
266              
267 0           my $field = $self->get_current_constraint_field;
268              
269             ## retrieve filehandle from query object.
270 0           my $fh = _get_upload_fh($self);
271              
272             ## check error
273 0 0         if (not $fh) {
274 0           warn "Unable to load filehandle";
275 0           return undef;
276             }
277              
278 0           require Image::Size;
279 0           import Image::Size;
280              
281             ## check size
282 0           my ($width, $height, $err) = imgsize($fh);
283              
284 0 0         unless ($width) {
285 0           warn "$caller: imgsize test failed: $err";
286 0           return undef;
287             }
288              
289 0           return ($width, $height);
290             }
291              
292             ## fetch filehandle for use with various file type checking
293             ## call it with (_get_upload_fh($self)) since kind of mock object
294             sub _get_upload_fh
295             {
296 0     0     my $self = shift;
297 0           my $q = $self->get_filtered_data;
298 0           my $field = $self->get_current_constraint_field;
299              
300             # convert the FH for the filtered data into a -seekable- handle;
301             # depending on whether we're using CGI::Simple, CGI, or Apache::Request
302             # we might not have something -seekable-.
303 1     1   430 use IO::File;
  1         6115  
  1         260  
304              
305 0 0         if (ref $q->{$field} eq 'IO::File') {
306 0           return $q->{$field};
307             }
308             else {
309 0           return IO::File->new_from_fd(fileno($q->{$field}), 'r');
310             }
311             }
312              
313             ## returns mime type if included as part of the send
314             ##
315             ## NOTE: retrieves from original uploaded, -UNFILTERED- data
316             sub _get_upload_mime_type
317             {
318 0     0     my $self = shift;
319 0           my $q = $self->get_input_data;
320 0           my $field = $self->get_current_constraint_field;
321              
322 0 0         if ($q->isa('CGI')) {
323 0           my $fn = $q->param($field);
324              
325             ## nicely check for info
326 0 0         if ($q->uploadInfo($fn)) {
327 0           return $q->uploadInfo($fn)->{'Content-Type'}
328             }
329              
330 0           return undef;
331             }
332              
333 0 0         if ($q->isa('CGI::Simple')) {
334 0           my $fn = $q->param($field);
335 0           return $q->upload_info($fn, 'mime');
336             }
337              
338 0 0         if ($q->isa('Apache::Request')) {
339 0           my $upload = $q->upload($field);
340 0           return $upload->info('Content-type');
341             }
342              
343 0           return undef;
344             }
345              
346              
347             1;
348             __END__