File Coverage

blib/lib/CGI/Ex/Validate.pm
Criterion Covered Total %
statement 538 739 72.8
branch 442 740 59.7
condition 175 370 47.3
subroutine 22 24 91.6
pod 8 13 61.5
total 1185 1886 62.8


\n" \n" \n") \n \n") \n";
line stmt bran cond sub pod time code
1             package CGI::Ex::Validate;
2              
3             ###---------------------###
4             # See the perldoc in CGI/Ex/Validate.pod
5             # Copyright - Paul Seamons
6             # Distributed under the Perl Artistic License without warranty
7              
8 9     9   2433077 use strict;
  9         33  
  9         435  
9 9     9   74 use warnings;
  9         17  
  9         877  
10 9     9   125 use Carp qw(croak);
  9         24  
  9         116008  
11              
12             our $VERSION = '2.55'; # VERSION
13             our $QR_EXTRA = qr/^(\w+_error|as_(array|string|hash)_\w+|no_\w+)/;
14             our @UNSUPPORTED_BROWSERS = (qr/MSIE\s+5.0\d/i);
15             our $JS_URI_PATH;
16             our $JS_URI_PATH_VALIDATE;
17              
18             our $type_ne_required;
19             $type_ne_required //= 0;
20              
21             sub new {
22 305     305 1 125200 my $class = shift;
23 305 100       1588 return bless ref($_[0]) ? shift : {@_}, $class;
24             }
25              
26 2   33 2 1 16 sub cgix { shift->{'cgix'} ||= do { require CGI::Ex; CGI::Ex->new } }
  2         876  
  2         13  
27              
28             sub validate {
29 300 100   300 1 77199 my $self = (! ref($_[0])) ? shift->new # $class->validate
    50          
30             : UNIVERSAL::isa($_[0], __PACKAGE__) ? shift # $self->validate
31             : __PACKAGE__->new; # CGI::Ex::Validate::validate
32 300         790 my ($form, $val_hash, $what_was_validated) = @_;
33              
34 300 50 33     1496 die "Invalid form hash or cgi object" if ! $form || ! ref $form;
35 300 100       957 $form = $self->cgix->get_form($form) if ref $form ne 'HASH';
36              
37 300         875 my ($fields, $ARGS) = $self->get_ordered_fields($val_hash);
38 299 100       790 return if ! @$fields;
39              
40 298 100       775 local $type_ne_required = 1 if $ARGS->{'type_ne_required'};
41 298 100 100     985 return if $ARGS->{'validate_if'} && ! $self->check_conditional($form, $ARGS->{'validate_if'});
42              
43             # Finally we have our arrayref of hashrefs that each have their 'field' key
44             # now lets do the validation
45 294         901 $self->{'was_checked'} = {};
46 294         627 $self->{'was_valid'} = {};
47 294         626 $self->{'had_error'} = {};
48 294         517 my $found = 1;
49 294         823 my @errors;
50             my $hold_error; # hold the error for a moment - to allow for an "OR" operation
51 294         0 my %checked;
52 294         901 foreach (my $i = 0; $i < @$fields; $i++) {
53 318         626 my $ref = $fields->[$i];
54 318 50 33     872 if (! ref($ref) && $ref eq 'OR') {
55 0 0       0 $i++ if $found; # if found skip the OR altogether
56 0         0 $found = 1; # reset
57 0         0 next;
58             }
59 318         505 $found = 1;
60 318   50     925 my $key = $ref->{'field'} || die "Missing field key during normal validation";
61              
62             # allow for field names that contain regular expressions
63 318         504 my @keys;
64 318 100       1087 if ($key =~ m/^(!\s*|)m([^\s\w])(.*)\2([eigsmx]*)$/s) {
65 9         46 my ($not,$pat,$opt) = ($1,$3,$4);
66 9         19 $opt =~ tr/g//d;
67 9 50       23 die "The e option cannot be used on validation keys on field $key" if $opt =~ /e/;
68 9         31 foreach my $_key (sort keys %$form) {
69 15 100 33     185 next if ($not && $_key =~ m/(?$opt:$pat)/) || (! $not && $_key !~ m/(?$opt:$pat)/);
      66        
      66        
70 9         51 push @keys, [$_key, [undef, $1, $2, $3, $4, $5]];
71             }
72             } else {
73 309         792 @keys = ([$key]);
74             }
75              
76 318         648 foreach my $r (@keys) {
77 318         685 my ($field, $ifs_match) = @$r;
78 318 50       1082 if (! $checked{$field}++) {
79 318         877 $self->{'was_checked'}->{$field} = 1;
80 318         618 $self->{'was_valid'}->{$field} = 1;
81 318         809 $self->{'had_error'}->{$field} = 0;
82             }
83 318         779 local $ref->{'was_validated'} = 1;
84 318         949 my $err = $self->validate_buddy($form, $field, $ref, $ifs_match);
85 318 100       811 if ($ref->{'was_validated'}) {
86 301 100       690 push @$what_was_validated, $ref if $what_was_validated;
87             } else {
88 17         52 $self->{'was_valid'}->{$field} = 0;
89             }
90              
91             # test the error - if errors occur allow for OR - if OR fails use errors from first fail
92 318 100       664 if ($err) {
93 124         280 $self->{'was_valid'}->{$field} = 0;
94 124         359 $self->{'had_error'}->{$field} = 0;
95 124 50 66     497 if ($i < $#$fields && ! ref($fields->[$i + 1]) && $fields->[$i + 1] eq 'OR') {
      33        
96 0         0 $hold_error = $err;
97             } else {
98 124 50       400 push @errors, $hold_error ? @$hold_error : @$err;
99 124         771 $hold_error = undef;
100             }
101             } else {
102 194         1150 $hold_error = undef;
103             }
104             }
105             }
106 294 50       696 push(@errors, @$hold_error) if $hold_error; # allow for final OR to work
107              
108             # optionally check for unused keys in the form
109 294 100 66     1326 $self->no_extra_fields($form,$fields,$val_hash,\@errors) if ($ARGS->{'no_extra_fields'} || $self->{'no_extra_fields'});
110 294 50 33     1198 delete @$form{grep {!$self->{'was_valid'}->{$_}} keys %$form} if $ARGS->{'delete_unvalidated'} || $self->{'delete_unvalidated'};
  0         0  
111              
112 294 100       721 if (@errors) {
113 127         487 my @copy = grep {/$QR_EXTRA/o} keys %$self;
  591         2396  
114 127         263 @{ $ARGS }{@copy} = @{ $self }{@copy};
  127         432  
  127         311  
115 127 50       352 unshift @errors, $ARGS->{'title'} if $ARGS->{'title'};
116 127         403 my $err_obj = $self->new_error(\@errors, $ARGS);
117 127 50       382 die $err_obj if $ARGS->{'raise_error'};
118 127         1115 return $err_obj;
119             }
120              
121 167         1366 return; # success
122             }
123              
124             sub no_extra_fields {
125 9     9 1 23 my ($self,$form,$fields,$fv,$errors,$field_prefix) = @_;
126 9   50     32 $field_prefix ||= '';
127 9   50     34 local $self->{'_recurse'} = ($self->{'_recurse'} || 0) + 1;
128 9 50       20 die "Max dependency level reached 10\n" if $self->{'_recurse'} > 10;
129              
130 9         20 my %keys = map { ($_->{'field'} => 1) } @$fields;
  13         58  
131 9         32 foreach my $key (sort keys %$form) {
132 13 50       51 if (ref $form->{$key} eq 'HASH') {
    100          
133 0         0 my $field_type = $fv->{$key}->{'type'};
134 0 0       0 if(!defined $field_type) {
    0          
135             # Do nothing
136             }
137             elsif (ref $field_type ne 'HASH') {
138 0         0 push @$errors, [$field_prefix.$key, 'no_extra_fields', {}, undef];
139 0         0 next;
140             } else {
141 0         0 my $f = [map { {field=>$_} } keys %$field_type];
  0         0  
142 0         0 $self->no_extra_fields($form->{$key},$f,$field_type,$errors,$field_prefix.$key.'.');
143             }
144             } elsif (ref $form->{$key} eq 'ARRAY') {
145 1         3 my $field_type = $fv->{$key}->{'type'};
146 1 50       5 if (!defined $field_type) {
    50          
147             # Do nothing
148             } elsif (ref $field_type eq 'HASH') {
149 0         0 my $f = [map { {field=>$_} } keys %$field_type];
  0         0  
150 0         0 foreach (my $i = 0; $i <= $#{$form->{$key}}; $i ++) {
  0         0  
151 0 0       0 $self->no_extra_fields($form->{$key}->[$i],$f,$field_type,$errors,$field_prefix.$key.':'.$i.'.') if ref $form->{$key}->[$i];
152             }
153             }
154             }
155 13 100       43 next if $keys{$key};
156 4         18 push @$errors, [$field_prefix.$key, 'no_extra_fields', {}, undef];
157             }
158             }
159              
160             sub get_ordered_fields {
161 300     300 0 682 my ($self, $val_hash) = @_;
162              
163 300 100       698 die "Missing validation hash" if ! $val_hash;
164 299 50       874 if (ref $val_hash ne 'HASH') {
165 0 0 0     0 $val_hash = $self->get_validation($val_hash) if ref $val_hash ne 'SCALAR' || ! ref $val_hash;
166 0 0       0 die "Validation groups must be a hashref" if ref $val_hash ne 'HASH';
167             }
168              
169 299         518 my %ARGS;
170 299         1100 my @field_keys = grep { /^(?:group|general)\s+(\w+)/
171 348 100       1370 ? do {$ARGS{$1} = $val_hash->{$_} ; 0}
  26         89  
  26         68  
172             : 1 } sort keys %$val_hash;
173              
174             # Look first for items in 'group fields' or 'group order'
175 299         597 my $fields;
176 299 100 66     1411 if (my $ref = $ARGS{'fields'} || $ARGS{'order'}) {
177 1 50       4 my $type = $ARGS{'fields'} ? 'group fields' : 'group order';
178 1 50       8 die "Validation '$type' must be an arrayref when passed" if ! UNIVERSAL::isa($ref, 'ARRAY');
179 1         3 foreach my $field (@$ref) {
180 1 50       4 die "Non-defined value in '$type'" if ! defined $field;
181 1 50       5 if (ref $field) {
    50          
182 0 0       0 die "Found nonhashref value in '$type'" if ref($field) ne 'HASH';
183 0 0       0 die "Element missing \"field\" key/value in '$type'" if ! defined $field->{'field'};
184 0         0 push @$fields, $field;
185             } elsif ($field eq 'OR') {
186 0         0 push @$fields, 'OR';
187             } else {
188 1 50       4 die "No element found in '$type' for $field" if ! exists $val_hash->{$field};
189 1 50       5 die "Found nonhashref value in '$type'" if ref($val_hash->{$field}) ne 'HASH';
190 1         2 my $val = $val_hash->{$field};
191 1 50       6 $val = {%$val, field => $field} if ! $val->{'field'}; # copy the values to add the key
192 1         4 push @$fields, $val;
193             }
194             }
195              
196             # limit the keys that need to be searched to those not in fields or order
197 1 50       3 my %found = map { ref($_) ? ($_->{'field'} => 1) : () } @$fields;
  1         6  
198 1         2 @field_keys = grep { ! $found{$_} } @field_keys;
  2         7  
199             }
200              
201             # add any remaining field_vals from our original hash
202             # this is necessary for items that weren't in group fields or group order
203 299         724 foreach my $field (@field_keys) {
204 321 50       965 die "Found nonhashref value for field $field" if ref($val_hash->{$field}) ne 'HASH';
205 321 50       792 if (defined $val_hash->{$field}->{'field'}) {
206 0         0 push @$fields, $val_hash->{$field};
207             } else {
208 321         625 push @$fields, { %{$val_hash->{$field}}, field => $field };
  321         1722  
209             }
210             }
211              
212 299   100     1306 return ($fields || [], \%ARGS);
213             }
214              
215             sub new_error {
216 127     127 0 246 my $self = shift;
217 127         471 return CGI::Ex::Validate::Error->new(@_);
218             }
219              
220             ### allow for optional validation on groups and on individual items
221             sub check_conditional {
222 40     40 0 92 my ($self, $form, $ifs, $ifs_match) = @_;
223 40 50       92 die "Need reference passed to check_conditional" if ! $ifs;
224 40 50 66     162 $ifs = [$ifs] if ! ref($ifs) || UNIVERSAL::isa($ifs,'HASH');
225              
226 40         177 local $self->{'_check_conditional'} = 1;
227              
228             # run the if options here
229             # multiple items can be passed - all are required unless OR is used to separate
230 40         57 my $found = 1;
231 40         138 foreach (my $i = 0; $i <= $#$ifs; $i ++) {
232 40         82 my $ref = $ifs->[$i];
233 40 100       87 if (! ref $ref) {
234 38 50       96 if ($ref eq 'OR') {
235 0 0       0 $i++ if $found; # if found skip the OR altogether
236 0         0 $found = 1; # reset
237 0         0 next;
238             } else {
239 38 50       186 if ($ref =~ /^function\s*\(/) {
    100          
    100          
240 0         0 next;
241             } elsif ($ref =~ /^(.*?)\s+(was_valid|had_error|was_checked)$/) {
242 11         50 $ref = {field => $1, $2 => 1};
243             } elsif ($ref =~ s/^\s*!\s*//) {
244 2         10 $ref = {field => $ref, max_in_set => "0 of $ref"};
245             } else {
246 25         77 $ref = {field => $ref, required => 1};
247             }
248             }
249             }
250 40 50       129 last if ! $found;
251              
252             # get the field - allow for custom variables based upon a match
253 40   50     106 my $field = $ref->{'field'} || die "Missing field key during validate_if (possibly used a reference to a main hash *foo -> &foo)";
254 40 50       112 $field =~ s/\$(\d+)/defined($ifs_match->[$1]) ? $ifs_match->[$1] : ''/eg if $ifs_match;
  7 100       38  
255              
256             # max_values is properly checked elsewhere, however we need to stub in a value so defaults are properly set
257 40 100 50     113 $ref->{'max_values'} ||= scalar @{$form->{$field}} if ref $form->{$field} eq 'ARRAY';
  2         9  
258              
259 40         144 my $errs = $self->validate_buddy($form, $field, $ref);
260              
261 40 100       216 $found = 0 if $errs;
262             }
263 40         194 return $found;
264             }
265              
266              
267             ### this is where the main checking goes on
268             sub validate_buddy {
269 385     385 0 1103 my ($self, $form, $field, $field_val, $ifs_match, $field_prefix) = @_;
270 385   100     1748 $field_prefix ||= '';
271 385   100     1609 local $self->{'_recurse'} = ($self->{'_recurse'} || 0) + 1;
272 385 50       972 die "Max dependency level reached 10" if $self->{'_recurse'} > 10;
273 385         621 my @errors;
274              
275 385 50       899 if ($field_val->{'exclude_cgi'}) {
276 0         0 delete $field_val->{'was_validated'};
277 0         0 return 0;
278             }
279              
280             # allow for field names that contain regular expressions
281 385 50       995 if ($field =~ m/^(!\s*|)m([^\s\w])(.*)\2([eigsmx]*)$/s) {
282 0         0 my ($not,$pat,$opt) = ($1,$3,$4);
283 0         0 $opt =~ tr/g//d;
284 0 0       0 die "The e option cannot be used on validation keys on field $field" if $opt =~ /e/;
285 0         0 foreach my $_field (sort keys %$form) {
286 0 0 0     0 next if ($not && $_field =~ m/(?$opt:$pat)/) || (! $not && $_field !~ m/(?$opt:$pat)/);
      0        
      0        
287 0         0 my $errs = $self->validate_buddy($form, $_field, $field_val, [undef, $1, $2, $3, $4, $5]);
288 0 0       0 push @errors, @$errs if $errs;
289             }
290 0 0       0 return @errors ? \@errors : 0;
291             }
292              
293             # allow for canonical field name (allows api to present one field name but return a different one)
294             # need to do this relatively early since we are changing the value of $field
295 385 50       1009 if ($field_val->{'canonical'}) {
296 0         0 my $orig = $field_val->{'orig_field'} = $field;
297 0         0 $field = $field_val->{'canonical'};
298 0         0 $form->{$field} = delete $form->{$orig};
299             }
300              
301 385 100 100     1046 if ($field_val->{'was_valid'} && ! $self->{'was_valid'}->{$field}) { return [[$field_prefix.$field, 'was_valid', $field_val, $ifs_match]]; }
  7         41  
302 378 100 66     1027 if ($field_val->{'had_error'} && ! $self->{'had_error'}->{$field}) { return [[$field_prefix.$field, 'had_error', $field_val, $ifs_match]]; }
  3         16  
303 375 50 33     945 if ($field_val->{'was_checked'} && ! $self->{'was_checked'}->{$field}) { return [[$field_prefix.$field, 'was_checked', $field_val, $ifs_match]]; }
  0         0  
304              
305 375   100     1688 my $is_A = $field_val->{'type'} && ((ref($field_val->{'type'}) eq 'ARRAY' && '[]') || ($field_val->{'type'} eq 'array' && 'array'));
306              
307 375 50 66     1193 if (!exists($form->{$field}) && $field_val->{'alias'}) {
308 0 0       0 foreach my $alias (ref($field_val->{'alias'}) ? @{$field_val->{'alias'}} : $field_val->{'alias'}) {
  0         0  
309 0 0       0 next if ! exists $form->{$alias};
310 0         0 $form->{$field} = delete $form->{$alias};
311 0         0 last;
312             }
313             }
314              
315             # allow for default value
316 375 100 66     962 if (defined($field_val->{'default'})
      66        
317             && (!defined($form->{$field})
318             || (UNIVERSAL::isa($form->{$field},'ARRAY') ? !@{ $form->{$field} } : !length($form->{$field})))) {
319 5         12 $form->{$field} = $field_val->{'default'};
320             }
321              
322 375         568 my $values;
323 375 100 66     1530 if (ref $form->{$field} eq 'ARRAY') {
    100          
324 41         83 $values = $form->{$field};
325             } elsif ($is_A && $field_val->{'coerce'}) {
326 12 100       45 $values = exists($form->{$field}) ? ($form->{$field} = [$form->{$field}]) : [];
327             } else {
328 322         789 $values = [$form->{$field}];
329             }
330 375         1524 my $n_values = @$values;
331              
332             # allow for a few form modifiers
333 375         595 my $modified = 0;
334 375         731 foreach my $value (@$values) {
335 414 100       970 next if ! defined $value;
336 329 50       834 if (! $field_val->{'do_not_trim'}) { # whitespace
337 329 50       2345 $modified = 1 if $value =~ s/( ^\s+ | \s+$ )//xg;
338             }
339 329 50       817 if ($field_val->{'trim_control_chars'}) {
340 0 0       0 $modified = 1 if $value =~ y/\t/ /;
341 0 0       0 $modified = 1 if $value =~ y/\x00-\x1F//d;
342             }
343 329 50       1013 if ($field_val->{'to_upper_case'}) { # uppercase
    50          
344 0         0 $value = uc $value;
345 0         0 $modified = 1;
346             } elsif ($field_val->{'to_lower_case'}) { # lowercase
347 0         0 $value = lc $value;
348 0         0 $modified = 1;
349             }
350             }
351              
352 375         683 my %types;
353 375         1619 foreach (sort keys %$field_val) {
354 1157 100       4279 push @{$types{$1}}, $_ if /^ (compare|custom|equals|match|max_in_set|min_in_set|replace|required_if|sql|type|validate_if) _?\d* $/x;
  264         1264  
355             }
356              
357             # allow for inline specified modifications (ie s/foo/bar/)
358 375 100       1165 if ($types{'replace'}) { foreach my $type (@{ $types{'replace'} }) {
  6         6  
  6         10  
359             my $ref = UNIVERSAL::isa($field_val->{$type},'ARRAY') ? $field_val->{$type}
360 6 50       36 : [split(/\s*\|\|\s*/,$field_val->{$type})];
361 6         10 foreach my $rx (@$ref) {
362 6 50       31 if ($rx !~ m/^\s*s([^\s\w])(.+)\1(.*)\1([eigsmx]*)$/s) {
363 0         0 die "Not sure how to parse that replace ($rx)";
364             }
365 6         23 my ($pat, $swap, $opt) = ($2, $3, $4);
366 6 50       7 die "The e option cannot be used in swap on field $field" if $opt =~ /e/;
367 6         15 my $global = $opt =~ s/g//g;
368 6         8 $swap =~ s/\\n/\n/g;
369             my $expand = sub { # code similar to Template::Alloy::VMethod::vmethod_replace
370 9     9   13 my ($text, $start, $end) = @_;
371 9         13 my $copy = $swap;
372 9         42 $copy =~ s{ \\(\\|\$) | \$ (\d+) }{
373 3 50 33     22 $1 ? $1
    50          
374             : ($2 > $#$start || $2 == 0) ? ''
375             : substr($text, $start->[$2], $end->[$2] - $start->[$2]);
376             }exg;
377 9         12 $modified = 1;
378 9         58 $copy;
379 6         25 };
380 6         9 foreach my $value (@$values) {
381 6 100       15 next if ! defined $value;
382 5 100       7 if ($global) { $value =~ s{(?$opt:$pat)}{ $expand->($value, [@-], [@+]) }eg }
  4         179  
  8         33  
383 1         19 else { $value =~ s{(?$opt:$pat)}{ $expand->($value, [@-], [@+]) }e }
  1         5  
384             }
385             }
386             } }
387              
388 375 50 33     1059 if ($field_val->{'truncate'} and my $n = $field_val->{'max_len'}) {
389 0         0 foreach my $value (@$values) {
390 0 0 0     0 next if !defined($value) || length($value) <= $n;
391 0         0 $value = substr $value, 0, $n;
392 0         0 $modified = 1;
393             }
394             }
395              
396 375 100 66     922 $form->{$field} = $values->[0] if $modified && $n_values == 1; # put them back into the form if we have modified it
397              
398             # only continue if a validate_if is not present or passes test
399 375         578 my $needs_val = 0;
400 375         562 my $n_vif = 0;
401 375 100       909 if ($types{'validate_if'}) { foreach my $type (@{ $types{'validate_if'} }) {
  30         69  
  30         61  
402 30         47 $n_vif++;
403 30         76 my $ifs = $field_val->{$type};
404 30         82 my $ret = $self->check_conditional($form, $ifs, $ifs_match);
405 30 100       81 $needs_val++ if $ret;
406             } }
407 375 100 100     1528 if (! $needs_val && $n_vif) {
408 17         71 delete $field_val->{'was_validated'};
409 17         72 return 0;
410             }
411              
412             # check for simple existence
413             # optionally check only if another condition is met
414 358 100 100     1446 my $is_required = ($field_val->{'required'} || $field_val->{'req'}) ? 'required' : '';
415 358 100       888 if (! $is_required) {
416 278 100       670 if ($types{'required_if'}) { foreach my $type (@{ $types{'required_if'} }) {
  2         4  
  2         6  
417 2         5 my $ifs = $field_val->{$type};
418 2 100       7 next if ! $self->check_conditional($form, $ifs, $ifs_match);
419 1         3 $is_required = $type;
420 1         3 last;
421             } }
422             }
423 358 100 66     971 if ($is_required
      100        
424             && (!@$values
425             || grep {! defined || (! length && (!defined($field_val->{'min_len'}) || $field_val->{'min_len'}))} @$values)
426             ) {
427 33 100       131 return [] if $self->{'_check_conditional'};
428 22         160 return [[$field_prefix.$field, $is_required, $field_val, $ifs_match]];
429             }
430              
431 325 100 50     907 my $n = exists($field_val->{'min_values'}) ? $field_val->{'min_values'} || 0 : 0;
432 325 100       739 if ($n_values < $n) {
433 3 50       11 return [] if $self->{'_check_conditional'};
434 3         21 return [[$field_prefix.$field, 'min_values', $field_val, $ifs_match]];
435             }
436              
437 322 100 50     1475 $field_val->{'max_values'} = ($is_A ? 1e6 : $field_val->{'min_values'} || 1) if ! exists $field_val->{'max_values'};
    100          
438 322   50     793 $n = $field_val->{'max_values'} || 0;
439 322 100       770 if ($n_values > $n) {
440 4 50       58 return [] if $self->{'_check_conditional'};
441 4         34 return [[$field_prefix.$field, 'max_values', $field_val, $ifs_match]];
442             }
443              
444 318         1175 foreach ([min => $types{'min_in_set'}],
445             [max => $types{'max_in_set'}]) {
446 633   100     1616 my $keys = $_->[1] || next;
447 14         25 my $minmax = $_->[0];
448 14         29 foreach my $type (@$keys) {
449 14 50       97 $field_val->{$type} =~ m/^\s*(\d+)(?i:\s*of)?\s+(.+)\s*$/
450             || die "Invalid ${minmax}_in_set check $field_val->{$type}";
451 14         40 my $n = $1;
452 14         82 foreach my $_field (split /[\s,]+/, $2) {
453 38 100       148 my $ref = UNIVERSAL::isa($form->{$_field},'ARRAY') ? $form->{$_field} : [$form->{$_field}];
454 38         81 foreach my $_value (@$ref) {
455 42 100 100     288 $n -- if defined($_value) && length($_value);
456             }
457             }
458 14 100 100     93 if ( ($minmax eq 'min' && $n > 0)
      100        
      100        
459             || ($minmax eq 'max' && $n < 0)) {
460 6 100       19 return [] if $self->{'_check_conditional'};
461 5         67 return [[$field_prefix.$field, $type, $field_val, $ifs_match]];
462             }
463             }
464             }
465              
466             # at this point @errors should still be empty
467 312         817 my $content_checked; # allow later for possible untainting (only happens if content was checked)
468              
469 312         583 my $nT = $field_val->{'type'};
470 312 100 100     933 push @errors, [$field_prefix.$field, 'type', $field_val, $ifs_match] if $is_A && defined($form->{$field}) && ref $form->{$field} ne 'ARRAY';
      100        
471 312 100 100     1284 push @errors, [$field_prefix.$field, 'type', $field_val, $ifs_match] if $nT && $nT eq 'str' && ref $form->{$field} eq 'ARRAY';
      100        
472 312 100 100     844 $nT = (ref $nT && $nT->[0]) || undef if $is_A;
473              
474 312         624 OUTER: foreach my $value (@$values) {
475              
476 332 100 100     883 next if ! defined($value) && $type_ne_required;
477              
478 319 100       768 if (exists $field_val->{'enum'}) {
479 10 100       93 my $ref = ref($field_val->{'enum'}) ? $field_val->{'enum'} : [split(/\s*\|\|\s*/,$field_val->{'enum'})];
480 10         17 my $found = 0;
481 10         21 foreach (@$ref) {
482 30 100 100     109 $found = 1 if defined($value) && $_ eq $value;
483             }
484 10 100       26 if (! $found) {
485 3 50       10 return [] if $self->{'_check_conditional'};
486 3         11 push @errors, [$field_prefix.$field, 'enum', $field_val, $ifs_match];
487 3         10 next OUTER;
488             }
489 7         16 $content_checked = 1;
490             }
491              
492             # do specific type checks
493 316 100       673 if ($nT) {
494 129 100       359 if (! $self->check_type($value, $nT, $field, $form)){
495 44 50       137 return [] if $self->{'_check_conditional'};
496 44 100       121 my $_fv = ($nT eq $field_val->{'type'}) ? $field_val : {%$field_val, type => $nT};
497 44         187 push @errors, [$field_prefix.$field, 'type', $_fv, $ifs_match];
498 44         162 next OUTER;
499             }
500 85 50 66     283 if (ref($nT) eq 'HASH' && $form->{$field}) {
501             # recursively check these
502 22         59 foreach my $key (keys %$nT) {
503 20 100       30 foreach my $subform (@{ref($form->{$field}) eq 'ARRAY' ? $form->{$field} : [$form->{$field}]}) {
  20         83  
504 27         163 my $errs = $self->validate_buddy($subform, $key, $nT->{$key},[],$field_prefix.$field.'.');
505 27 100       117 push @errors, @$errs if $errs;
506             }
507             }
508 22 100       147 return @errors ? \@errors : 0;
509             }
510 63         115 $content_checked = 1;
511             }
512              
513             # field equals another field
514 250 100       588 if ($types{'equals'}) { foreach my $type (@{ $types{'equals'} }) {
  9         16  
  9         21  
515 9         64 my $field2 = $field_val->{$type};
516 9 50       27 my $not = ($field2 =~ s/^!\s*//) ? 1 : 0;
517 9         16 my $success = 0;
518 9 100       31 if ($field2 =~ m/^([\"\'])(.*)\1$/) {
519 2         7 my $test = $2;
520 2   66     12 $success = (defined($value) && $value eq $test);
521             } else {
522 7 50       24 $field2 =~ s/\$(\d+)/defined($ifs_match->[$1]) ? $ifs_match->[$1] : ''/eg if $ifs_match;
  2 100       15  
523 7 100 66     35 if (exists($form->{$field2}) && defined($form->{$field2})) {
    100          
524 5   100     46 $success = (defined($value) && $value eq $form->{$field2});
525             } elsif (! defined($value)) {
526 1         3 $success = 1; # occurs if they are both undefined
527             }
528             }
529 9 50       27 if ($not ? $success : ! $success) {
    100          
530 5 50       17 return [] if $self->{'_check_conditional'};
531 5         18 push @errors, [$field_prefix.$field, $type, $field_val, $ifs_match];
532 5         18 next OUTER;
533             }
534 4         28 $content_checked = 1;
535             } }
536              
537 245 100       606 if (exists $field_val->{'min_len'}) {
538 4         12 my $n = $field_val->{'min_len'};
539 4 100 100     20 if (! defined($value) || length($value) < $n) {
540 3 50       8 return [] if $self->{'_check_conditional'};
541 3         13 push @errors, [$field_prefix.$field, 'min_len', $field_val, $ifs_match];
542             }
543             }
544              
545 245 100       565 if (exists $field_val->{'max_len'}) {
546 17         25 my $n = $field_val->{'max_len'};
547 17 100 100     68 if (defined($value) && length($value) > $n) {
548 1 50       5 return [] if $self->{'_check_conditional'};
549 1         6 push @errors, [$field_prefix.$field, 'max_len', $field_val, $ifs_match];
550             }
551             }
552              
553             # now do match types
554 245 100       658 if ($types{'match'}) { foreach my $type (@{ $types{'match'} }) {
  17         55  
  17         58  
555             my $ref = UNIVERSAL::isa($field_val->{$type},'ARRAY') ? $field_val->{$type}
556             : UNIVERSAL::isa($field_val->{$type}, 'Regexp') ? [$field_val->{$type}]
557 17 100       195 : [split(/\s*\|\|\s*/,$field_val->{$type})];
    100          
558 17         57 foreach my $rx (@$ref) {
559 21 100       97 if (UNIVERSAL::isa($rx,'Regexp')) {
560 6 100 66     62 if (! defined($value) || $value !~ $rx) {
561 2         12 push @errors, [$field_prefix.$field, $type, $field_val, $ifs_match];
562             }
563             } else {
564 15 50       115 if ($rx !~ m/^(!\s*|)m([^\s\w])(.*)\2([eigsmx]*)$/s) {
565 0         0 die "Not sure how to parse that match ($rx)";
566             }
567 15         70 my ($not, $pat, $opt) = ($1, $3, $4);
568 15         29 $opt =~ tr/g//d;
569 15 50       35 die "The e option cannot be used on validation keys on field $field" if $opt =~ /e/;
570 15 100 100     799 if ( ( $not && ( defined($value) && $value =~ m/(?$opt:$pat)/))
      100        
      100        
      100        
571             || (! $not && (! defined($value) || $value !~ m/(?$opt:$pat)/)) ) {
572 6 50       21 return [] if $self->{'_check_conditional'};
573 6         68 push @errors, [$field_prefix.$field, $type, $field_val, $ifs_match];
574             }
575             }
576             }
577 17         50 $content_checked = 1;
578             } }
579              
580             # allow for comparison checks
581 245 100       633 if ($types{'compare'}) { foreach my $type (@{ $types{'compare'} }) {
  30         45  
  30         103  
582             my $ref = UNIVERSAL::isa($field_val->{$type},'ARRAY') ? $field_val->{$type}
583 30 50       237 : [split(/\s*\|\|\s*/,$field_val->{$type})];
584 30         71 foreach my $comp (@$ref) {
585 30 50       64 next if ! $comp;
586 30         62 my $test = 0;
587 30 100       292 if ($comp =~ /^\s*(>|<|[>
    50          
588 15   100     46 my $val = $value || 0;
589 15         30 $val *= 1;
590 15 100       78 if ($1 eq '>' ) { $test = ($val > $2) }
  3 100       11  
    100          
    100          
    100          
    50          
591 3         11 elsif ($1 eq '<' ) { $test = ($val < $2) }
592 2         8 elsif ($1 eq '>=') { $test = ($val >= $2) }
593 2         9 elsif ($1 eq '<=') { $test = ($val <= $2) }
594 2         8 elsif ($1 eq '!=') { $test = ($val != $2) }
595 3         11 elsif ($1 eq '==') { $test = ($val == $2) }
596              
597             } elsif ($comp =~ /^\s*(eq|ne|gt|ge|lt|le)\s+(.+?)\s*$/) {
598 15 100       39 my $val = defined($value) ? $value : '';
599 15         52 my ($op, $value2) = ($1, $2);
600 15         66 $value2 =~ s/^([\"\'])(.*)\1$/$2/;
601 15 100       74 if ($op eq 'gt') { $test = ($val gt $value2) }
  3 100       7  
    100          
    100          
    100          
    50          
602 3         10 elsif ($op eq 'lt') { $test = ($val lt $value2) }
603 2         6 elsif ($op eq 'ge') { $test = ($val ge $value2) }
604 2         6 elsif ($op eq 'le') { $test = ($val le $value2) }
605 2         8 elsif ($op eq 'ne') { $test = ($val ne $value2) }
606 3         8 elsif ($op eq 'eq') { $test = ($val eq $value2) }
607              
608             } else {
609 0         0 die "Not sure how to compare \"$comp\"";
610             }
611 30 100       75 if (! $test) {
612 16 50       39 return [] if $self->{'_check_conditional'};
613 16         71 push @errors, [$field_prefix.$field, $type, $field_val, $ifs_match];
614             }
615             }
616 30         101 $content_checked = 1;
617             } }
618              
619             # server side sql type
620 245 50       538 if ($types{'sql'}) { foreach my $type (@{ $types{'sql'} }) {
  0         0  
  0         0  
621 0         0 my $db_type = $field_val->{"${type}_db_type"};
622 0 0       0 my $dbh = ($db_type) ? $self->{dbhs}->{$db_type} : $self->{dbh};
623 0 0       0 if (! $dbh) {
    0          
624 0 0       0 die "Missing dbh for $type type on field $field" . ($db_type ? " and db_type $db_type" : "");
625             } elsif (UNIVERSAL::isa($dbh,'CODE')) {
626 0   0     0 $dbh = &$dbh($field, $self) || die "SQL Coderef did not return a dbh";
627             }
628 0         0 my $sql = $field_val->{$type};
629 0         0 my @args = ($value) x $sql =~ tr/?//;
630 0         0 my $return = $dbh->selectrow_array($sql, {}, @args); # is this right - copied from O::FORMS
631 0 0       0 $field_val->{"${type}_error_if"} = 1 if ! defined $field_val->{"${type}_error_if"};
632 0 0 0     0 if ( (! $return && $field_val->{"${type}_error_if"})
      0        
      0        
633             || ($return && ! $field_val->{"${type}_error_if"}) ) {
634 0 0       0 return [] if $self->{'_check_conditional'};
635 0         0 push @errors, [$field_prefix.$field, $type, $field_val, $ifs_match];
636             }
637 0         0 $content_checked = 1;
638             } }
639              
640             # server side custom type
641 245 100       651 if ($types{'custom'}) { foreach my $type (@{ $types{'custom'} }) {
  7         12  
  7         16  
642 7         13 my $check = $field_val->{$type};
643 7         13 my $err;
644 7 100       23 if (UNIVERSAL::isa($check, 'CODE')) {
645 3         5 my $ok;
646 3 100       7 $err = "$@" if ! eval { $ok = $check->($field, $value, $field_val, $type, $form); 1 };
  3         11  
  2         16  
647 3 100       21 next if $ok;
648 2 100 66     12 chomp($err) if !ref($@) && defined($err);
649             } else {
650 4 100       13 next if $check;
651             }
652 4 50       13 return [] if $self->{'_check_conditional'};
653 4 100       20 push @errors, [$field_prefix.$field, $type, $field_val, $ifs_match, (defined($err) ? $err : ())];
654 4         25 $content_checked = 1;
655             } }
656             }
657              
658             # allow for the data to be "untainted"
659             # this is only allowable if the user ran some other check for the datatype
660 290 100 66     815 if ($field_val->{'untaint'} && $#errors == -1) {
661 2 100       4 if (! $content_checked) {
662 1         4 push @errors, [$field_prefix.$field, 'untaint', $field_val, $ifs_match];
663             } else {
664             # generic untainter - assuming the other required content_checks did good validation
665 1 50       6 $_ = /(.*)/ ? $1 : die "Couldn't match?" foreach @$values;
666 1 50       2 if ($n_values == 1) {
667 1         3 $form->{$field} = $values->[0];
668             }
669             }
670             }
671              
672             # all done - time to return
673 290 100       1563 return @errors ? \@errors : 0;
674             }
675              
676             ###---------------------###
677              
678             ### used to validate specific types
679             sub check_type {
680 129     129 0 357 my ($self, $value, $type) = @_;
681 129 100       418 $type = ref($type) eq 'Type::Tiny' ? $type :
    50          
682             ref($type) eq 'HASH' ? 'hash' : lc $type;
683 129         228 $type = lc $type;
684 129 100 100     341 return 0 if ! defined($value) && $type ne 'code';
685 128 50       832 if ($type eq 'email') {
    100          
    50          
    100          
    100          
    50          
    50          
    100          
    100          
    100          
    100          
    100          
    50          
    0          
686 0 0       0 return 0 if ! $value;
687 0 0       0 my ($local_p,$dom) = ($value =~ /^(.+)\@(.+?)$/) ? ($1,$2) : return 0;
688 0 0       0 return 0 if length($local_p) > 60;
689 0 0       0 return 0 if length($dom) > 100;
690 0 0 0     0 return 0 if ! $self->check_type($dom,'domain') && ! $self->check_type($dom,'ip');
691 0 0       0 return 0 if ! $self->check_type($local_p,'local_part');
692             } elsif ($type eq 'hash') {
693 26 100       74 return 0 if ref $value ne 'HASH';
694             # the "username" portion of an email address - sort of arbitrary
695             } elsif ($type eq 'local_part') {
696 0 0 0     0 return 0 if ! defined($value) || ! length($value);
697             # ignoring all valid quoted string local parts
698 0 0       0 return 0 if $value =~ m/[^\w.~!\#\$%\^&*\-=+?]/;
699              
700             # standard IP address
701             } elsif ($type eq 'ip') {
702 3 50       10 return 0 if ! $value;
703 3 50       12 return (4 == grep {!/\D/ && $_ < 256} split /\./, $value, 4);
  11         61  
704              
705             # domain name - including tld and subdomains (which are all domains)
706             } elsif ($type eq 'domain') {
707 8 50 33     33 return 0 if ! $value || length($value) > 255;
708 8 100 100     81 return 0 if $value !~ /^([a-z0-9][a-z0-9\-]{0,62} \.)+ [a-z]{1,63}$/ix
709             || $value =~ m/(\.\-|\-\.|\.\.)/;
710              
711             # validate a url
712             } elsif ($type eq 'url') {
713 0 0       0 return 0 if ! $value;
714 0 0       0 $value =~ s|^https?://([^/]+)||i || return 0;
715 0         0 my $dom = $1;
716 0 0 0     0 return 0 if ! $self->check_type($dom,'domain') && ! $self->check_type($dom,'ip');
717 0 0 0     0 return 0 if $value && ! $self->check_type($value,'uri');
718              
719             # validate a uri - the path portion of a request
720             } elsif ($type eq 'uri') {
721 0 0       0 return 0 if ! $value;
722 0 0       0 return 0 if $value =~ m/\s+/;
723              
724             } elsif ($type eq 'code') {
725 3 100 100     18 return 0 if defined($value) && ref($value) ne 'CODE';
726             } elsif ($type eq 'str') {
727 15 100 66     59 return 0 if ref($value) || ! defined($value);
728             } elsif ($type eq 'int') {
729 25 100       142 return 0 if $value !~ /^-? (?: 0 | [1-9]\d*) $/x;
730 15 100       60 return 0 if ($value < 0) ? $value < -2**31 : $value > 2**31-1;
    100          
731             } elsif ($type eq 'uint') {
732 20 100       118 return 0 if $value !~ /^ (?: 0 | [1-9]\d*) $/x;
733 9 100       32 return 0 if $value > 2**32-1;
734             } elsif ($type eq 'num') {
735 19 100       110 return 0 if $value !~ /^-? (?: 0 | [1-9]\d* (?:\.\d+)? | 0?\.\d+) $/x;
736             } elsif ($type eq 'unum') {
737 9 50       50 return 0 if $value !~ /^ (?: 0 | [1-9]\d* (?:\.\d+)? | 0?\.\d+) $/x;
738              
739             } elsif ($type eq 'cc') {
740 0 0       0 return 0 if ! $value;
741 0 0       0 return 0 if $value =~ /[^\d\-\ ]/;
742 0         0 $value =~ s/\D//g;
743 0 0 0     0 return 0 if length($value) > 16 || length($value) < 13;
744              
745             # simple mod10 check
746 0         0 my $sum = 0;
747 0         0 my $switch = 0;
748 0         0 foreach my $digit (reverse split //, $value) {
749 0 0       0 $switch = 1 if ++$switch > 2;
750 0         0 my $y = $digit * $switch;
751 0 0       0 $y -= 9 if $y > 9;
752 0         0 $sum += $y;
753             }
754 0 0       0 return 0 if $sum % 10;
755              
756             }
757              
758 83         237 return 1;
759             }
760              
761             ###---------------------###
762              
763             sub get_validation {
764 1     1 1 4 my ($self, $val) = @_;
765 1         1564 require CGI::Ex::Conf;
766 1         9 return CGI::Ex::Conf::conf_read($val, {html_key => 'validation', default_ext => 'val'});
767             }
768              
769             ### returns all keys from all groups - even if group has validate_if
770             sub get_validation_keys {
771 0     0 1 0 my ($self, $val_hash, $form) = @_; # with optional form - will only return keys in validated groups
772              
773 0 0       0 if ($form) {
774 0 0       0 die "Invalid form hash or cgi object" if ! ref $form;
775 0 0       0 $form = $self->cgix->get_form($form) if ref $form ne 'HASH';
776             }
777              
778 0         0 my ($fields, $ARGS) = $self->get_ordered_fields($val_hash);
779 0 0       0 return {} if ! @$fields;
780 0 0 0     0 return {} if $form && $ARGS->{'validate_if'} && ! $self->check_conditional($form, $ARGS->{'validate_if'});
      0        
781 0   0     0 return {map { $_->{'field'} = $_->{'name'} || 1 } @$fields};
  0         0  
782             }
783              
784             ###---------------------###
785              
786             sub generate_js {
787             return ""
788 2 50 33 2 1 8 if $ENV{'HTTP_USER_AGENT'} && grep {$ENV{'HTTP_USER_AGENT'} =~ $_} @UNSUPPORTED_BROWSERS;
  0         0  
789              
790 2         7 my $self = shift;
791 2   33     7 my $val_hash = shift || croak "Missing validation hash";
792 2 50       8 if (ref $val_hash ne 'HASH') {
793 0 0 0     0 $val_hash = $self->get_validation($val_hash) if ref $val_hash ne 'SCALAR' || ! ref $val_hash;
794 0 0       0 croak "Validation groups must be a hashref" if ref $val_hash ne 'HASH';
795             }
796              
797 2         4 my ($args, $form_name, $js_uri_path);
798 2 50       5 croak "Missing args or form_name" if ! $_[0];
799 2 50       7 if (ref($_[0]) eq 'HASH') {
800 0         0 $args = shift;
801             } else {
802 2         6 ($args, $form_name, $js_uri_path) = ({}, @_);
803             }
804              
805 2   0     5 $form_name ||= $args->{'form_name'} || croak 'Missing form_name';
      33        
806 2   33     6 $js_uri_path ||= $args->{'js_uri_path'};
807              
808 2   33     9 my $js_uri_path_validate = $JS_URI_PATH_VALIDATE || do {
809             croak 'Missing js_uri_path' if ! $js_uri_path;
810             "$js_uri_path/CGI/Ex/validate.js";
811             };
812              
813 2         1421 require CGI::Ex::JSONDump;
814 2         54 my $json = CGI::Ex::JSONDump->new({pretty => 1})->dump($val_hash);
815 2         28 return qq{
816            
820             };
821             }
822              
823             sub generate_form {
824 0     0 1 0 my ($self, $val_hash, $form_name, $args) = @_;
825 0 0       0 ($args, $form_name) = ($form_name, undef) if ref($form_name) eq 'HASH';
826              
827 0         0 my ($fields, $ARGS) = $self->get_ordered_fields($val_hash);
828 0 0       0 $args = {%{ $ARGS->{'form_args'} || {}}, %{ $args || {} }};
  0 0       0  
  0         0  
829              
830 0 0 0     0 my $cols = ($args->{'no_inline_error'} || ! $args->{'columns'} || $args->{'columns'} != 3) ? 2 : 3;
831 0   0     0 $args->{'div'} ||= "
\n";
832 0   0     0 $args->{'open'} ||= "
\n";
833 0   0     0 $args->{'form_name'} ||= $form_name || 'the_form_'.int(rand * 1000);
      0        
834 0   0     0 $args->{'action'} ||= '';
835 0   0     0 $args->{'method'} ||= 'POST';
836 0   0     0 $args->{'submit'} ||= "{'submit_name'} || 'Submit')."\">";
      0        
837 0   0     0 $args->{'header'} ||= "\n"; \n" if $args->{'title'}; \n
838 0 0       0 $args->{'header'} .= "
\$title
839 0   0     0 $args->{'footer'} ||= "
\$submit
\n";
840 0   0     0 $args->{'extra_form_attrs'} ||= '';
841             $args->{'row_template'} ||= "
842             ." \$name
843             ." \$input"
844             . ($cols == 2
845 0 0 0     0 ? ($args->{'no_inline_error'} ? '' : "
[% \$field_error %]
    0          
846             : "[% \$field_error %]
847             ."
848              
849 0   0     0 my $js = ! defined($args->{'use_js_validation'}) || $args->{'use_js_validation'};
850              
851             $args->{'css'} = ".odd { background: #eee }\n"
852             . ".form_div { width: 40em; }\n"
853             . ".form_div td { padding:.5ex;}\n"
854             . ".form_div label { width: 10em }\n"
855             . ".form_div .error { color: darkred }\n"
856             . "table { border-spacing: 0px }\n"
857             . ".submit_row { text-align: right }\n"
858 0 0       0 if ! defined $args->{'css'};
859              
860 0 0       0 my $txt = ($args->{'css'} ? "\n" : '') . $args->{'div'} . $args->{'open'} . $args->{'header'};
861 0         0 s/\$(form_name|title|method|action|submit|extra_form_attrs)/$args->{$1}/g foreach $txt, $args->{'footer'};
862 0         0 my $n = 0;
863 0         0 foreach my $field (@$fields) {
864 0         0 my $input;
865 0 0       0 my $type = $field->{'htype'} ? $field->{'htype'} : $field->{'field'} =~ /^pass(?:|wd|word|\d+|_\w+)$/i ? 'password' : 'text';
    0          
866 0 0 0     0 if ($type eq 'hidden') {
    0 0        
    0 0        
    0 0        
      0        
867 0         0 $txt .= "$input\n";
868 0         0 next;
869             } elsif ($type eq 'textarea' || $field->{'rows'} || $field->{'cols'}) {
870 0 0       0 my $r = $field->{'rows'} ? " rows=\"$field->{'rows'}\"" : '';
871 0 0       0 my $c = $field->{'cols'} ? " cols=\"$field->{'cols'}\"" : '';
872 0 0       0 my $w = $field->{'wrap'} ? " wrap=\"$field->{'wrap'}\"" : '';
873 0         0 $input = "";
874             } elsif ($type eq 'radio' || $type eq 'checkbox') {
875 0   0     0 my $e = $field->{'enum'} || [];
876 0   0     0 my $l = $field->{'label'} || $e;
877 0 0       0 my $I = @$e > @$l ? $#$e : $#$l;
878 0         0 for (my $i = 0; $i <= $I; $i++) {
879 0         0 my $_e = $e->[$i];
880 0         0 $_e =~ s/\"/"/g;
881 0 0       0 $input .= "
{'field'}\" id=\"$field->{'field'}_$i\" value=\"$_e\">"
882             .(defined($l->[$i]) ? $l->[$i] : '')."\n";
883             }
884             } elsif ($type eq 'select' || $field->{'enum'} || $field->{'label'}) {
885 0         0 $input = "
886 0   0     0 my $e = $field->{'enum'} || [];
887 0   0     0 my $l = $field->{'label'} || $e;
888 0 0       0 my $I = @$e > @$l ? $#$e : $#$l;
889 0         0 for (my $i = 0; $i <= $I; $i++) {
890 0 0       0 $input .= " [$i]) ? " value=\"".do { my $_e = $e->[$i]; $_e =~ s/\"/"/g; $_e }.'"' : '').">"
  0 0       0  
  0         0  
  0         0  
891             .(defined($l->[$i]) ? $l->[$i] : '')."\n";
892             }
893 0         0 $input .= "\n";
894             } else {
895 0 0       0 my $s = $field->{'size'} ? " size=\"$field->{'size'}\"" : '';
896 0 0 0     0 my $m = $field->{'maxlength'} || $field->{'max_len'}; $m = $m ? " maxlength=\"$m\"" : '';
  0         0  
897 0         0 $input = "{'field'}\" id=\"$field->{'field'}\"$s$m value=\"\" />";
898             }
899              
900 0         0 $n++;
901 0         0 my $copy = $args->{'row_template'};
902 0         0 my $name = $field->{'field'};
903 0   0     0 $name = $field->{'name'} || do { $name =~ tr/_/ /; $name =~ s/\b(\w)/\u$1/g; $name };
904 0         0 $name = "";
905 0         0 $copy =~ s/\$field/$field->{'field'}/g;
906 0         0 $copy =~ s/\$name/$name/g;
907 0         0 $copy =~ s/\$input/$input/g;
908 0 0       0 $copy =~ s/\$oddeven/$n % 2 ? 'odd' : 'even'/eg;
  0         0  
909 0         0 $txt .= $copy;
910             }
911 0   0     0 $txt .= $args->{'footer'} . ($args->{'close'} || "\n") . ($args->{'div_close'} || "\n");
      0        
912 0 0       0 if ($js) {
913 0         0 local @{ $val_hash }{('general form_args', 'group form_args')};
  0         0  
914 0         0 delete @{ $val_hash }{('general form_args', 'group form_args')};
  0         0  
915 0         0 $txt .= $self->generate_js($val_hash, $args);
916             }
917 0         0 return $txt;
918             }
919              
920             ###---------------------###
921             ### How to handle errors
922              
923             package CGI::Ex::Validate::Error;
924              
925 9     9   117 use strict;
  9         33  
  9         403  
926 9     9   87 use overload '""' => \&as_string;
  9         20  
  9         107  
927              
928             sub new {
929 127     127   287 my ($class, $errors, $extra) = @_;
930 127 50       361 die "Missing or invalid errors arrayref" if ref $errors ne 'ARRAY';
931 127 50       302 die "Missing or invalid extra hashref" if ref $extra ne 'HASH';
932 127         621 return bless {errors => $errors, extra => $extra}, $class;
933             }
934              
935             sub as_string {
936 139     139   19999 my $self = shift;
937 139   50     513 my $extra = $self->{extra} || {};
938 139   50     540 my $extra2 = shift || {};
939              
940             # allow for formatting
941             my $join = defined($extra2->{as_string_join}) ? $extra2->{as_string_join}
942             : defined($extra->{as_string_join}) ? $extra->{as_string_join}
943 139 100       473 : "\n";
    50          
944             my $header = defined($extra2->{as_string_header}) ? $extra2->{as_string_header}
945 139 50       406 : defined($extra->{as_string_header}) ? $extra->{as_string_header} : "";
    50          
946             my $footer = defined($extra2->{as_string_footer}) ? $extra2->{as_string_footer}
947 139 50       424 : defined($extra->{as_string_footer}) ? $extra->{as_string_footer} : "";
    50          
948              
949 139         256 return $header . join($join, @{ $self->as_array($extra2) }) . $footer;
  139         361  
950             }
951              
952             sub as_array {
953 139     139   217 my $self = shift;
954 139   50     389 my $errors = $self->{errors} || die "Missing errors";
955 139   50     334 my $extra = $self->{extra} || {};
956 139   50     355 my $extra2 = shift || {};
957              
958             my $title = defined($extra2->{as_array_title}) ? $extra2->{as_array_title}
959             : defined($extra->{as_array_title}) ? $extra->{as_array_title}
960 139 100       482 : "Please correct the following items:";
    50          
961              
962             # if there are heading items then we may end up needing a prefix
963 139         213 my $has_headings;
964 139 100       297 if ($title) {
965 33         163 $has_headings = 1;
966             } else {
967 106         283 foreach (@$errors) {
968 107 50       283 next if ref;
969 0         0 $has_headings = 1;
970 0         0 last;
971             }
972             }
973              
974             my $prefix = defined($extra2->{as_array_prefix}) ? $extra2->{as_array_prefix}
975             : defined($extra->{as_array_prefix}) ? $extra->{as_array_prefix}
976 139 100       517 : $has_headings ? ' ' : '';
    50          
    50          
977              
978             # get the array ready
979 139         259 my @array = ();
980 139 100       372 push @array, $title if length $title;
981              
982             # add the errors
983 139         286 my %found = ();
984 139         285 foreach my $err (@$errors) {
985 140 50       282 if (! ref $err) {
986 0         0 push @array, $err;
987 0         0 %found = ();
988             } else {
989 140         347 my $text = $self->get_error_text($err);
990 140 50       414 next if $found{$text};
991 140         344 $found{$text} = 1;
992 140         481 push @array, "$prefix$text";
993             }
994             }
995              
996 139         1160 return \@array;
997             }
998              
999             sub as_hash {
1000 22     22   269 my $self = shift;
1001 22   50     96 my $errors = $self->{errors} || die "Missing errors";
1002 22   50     82 my $extra = $self->{extra} || {};
1003 22   100     60 my $extra2 = shift || {};
1004              
1005             my $suffix = defined($extra2->{as_hash_suffix}) ? $extra2->{as_hash_suffix}
1006 22 50       69 : defined($extra->{as_hash_suffix}) ? $extra->{as_hash_suffix} : '_error';
    100          
1007             my $join = defined($extra2->{as_hash_join}) ? $extra2->{as_hash_join}
1008 22 50       73 : defined($extra->{as_hash_join}) ? $extra->{as_hash_join} : '
';
    100          
1009              
1010 22         45 my %found;
1011             my %return;
1012 22         50 foreach my $err (@$errors) {
1013 22 50       63 next if ! ref $err;
1014              
1015 22         60 my ($field, $type, $field_val, $ifs_match) = @$err;
1016 22 50       56 die "Missing field name" if ! $field;
1017 22 50       58 if ($field_val->{delegate_error}) {
1018 0         0 $field = $field_val->{delegate_error};
1019 0 0       0 $field =~ s/\$(\d+)/defined($ifs_match->[$1]) ? $ifs_match->[$1] : ''/eg if $ifs_match;
  0 0       0  
1020             }
1021              
1022 22         53 my $text = $self->get_error_text($err);
1023 22 50       91 next if $found{$field}->{$text};
1024 22         58 $found{$field}->{$text} = 1;
1025              
1026 22         42 $field .= $suffix;
1027 22         37 push @{ $return{$field} }, $text;
  22         88  
1028             }
1029              
1030 22 50       65 if ($join) {
1031             my $header = defined($extra2->{as_hash_header}) ? $extra2->{as_hash_header}
1032 22 50       79 : defined($extra->{as_hash_header}) ? $extra->{as_hash_header} : "";
    50          
1033             my $footer = defined($extra2->{as_hash_footer}) ? $extra2->{as_hash_footer}
1034 22 50       73 : defined($extra->{as_hash_footer}) ? $extra->{as_hash_footer} : "";
    50          
1035 22         150 foreach my $key (keys %return) {
1036 22         43 $return{$key} = $header . join($join,@{ $return{$key} }) . $footer;
  22         109  
1037             }
1038             }
1039              
1040 22         215 return \%return;
1041             }
1042              
1043             ### return a user friendly error message
1044             sub get_error_text {
1045 162     162   270 my $self = shift;
1046 162         286 my $err = shift;
1047 162   50     395 my $extra = $self->{extra} || {};
1048 162         446 my ($field, $type, $field_val, $ifs_match, $custom_err) = @$err;
1049 162 100 66     436 return $custom_err if defined($custom_err) && length($custom_err);
1050 160 50       1217 my $dig = ($type =~ s/(_?\d+)$//) ? $1 : '';
1051 160         414 my $type_lc = lc($type);
1052              
1053             # allow for delegated field names - only used for defaults
1054 160 50       393 if ($field_val->{delegate_error}) {
1055 0         0 $field = $field_val->{delegate_error};
1056 0 0       0 $field =~ s/\$(\d+)/defined($ifs_match->[$1]) ? $ifs_match->[$1] : ''/eg if $ifs_match;
  0 0       0  
1057             }
1058              
1059             # the the name of this thing
1060 160         424 my $name = $field_val->{'name'};
1061 160 100 100     990 $name = "The field $field" if ! $name && ($field =~ /\W/ || ($field =~ /\d/ && $field =~ /\D/));
      66        
1062 160 100       369 if (! $name) {
1063 142         243 $name = $field;
1064 142         316 $name =~ tr/_/ /;
1065 142         1251 $name =~ s/\b(\w)/\u$1/g;
1066             }
1067 160 0       456 $name =~ s/\$(\d+)/defined($ifs_match->[$1]) ? $ifs_match->[$1] : ''/eg if $ifs_match;
  0 100       0  
1068              
1069             # type can look like "required" or "required2" or "required100023"
1070             # allow for fallback from required100023_error through required_error
1071              
1072             # look in the passed hash or self first
1073 160         294 my $return;
1074 160 50       555 foreach my $key ((length($dig) ? "${type}${dig}_error" : ()), "${type}_error", 'error') {
1075 308   50     1465 $return = $field_val->{$key} || $extra->{$key} || next;
1076 12 0       28 $return =~ s/\$(\d+)/defined($ifs_match->[$1]) ? $ifs_match->[$1] : ''/eg if $ifs_match;
  0 50       0  
1077 12         27 $return =~ s/\$field/$field/g;
1078 12         23 $return =~ s/\$name/$name/g;
1079 12 50       39 if (my $value = $field_val->{"$type$dig"}) {
1080 12 50       32 $return =~ s/\$value/$value/g if ! ref $value;
1081             }
1082 12         24 last;
1083             }
1084              
1085             # set default messages
1086 160 100       372 if (! $return) {
1087 148 100 100     1252 if ($type eq 'required' || $type eq 'required_if') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    50          
1088 27         52 $return = "$name is required.";
1089              
1090             } elsif ($type eq 'min_values') {
1091 3         9 my $n = $field_val->{"min_values${dig}"};
1092 3 50       8 my $values = ($n == 1) ? 'value' : 'values';
1093 3         10 $return = "$name had less than $n $values.";
1094              
1095             } elsif ($type eq 'max_values') {
1096 6         16 my $n = $field_val->{"max_values${dig}"};
1097 6 100       17 my $values = ($n == 1) ? 'value' : 'values';
1098 6         17 $return = "$name had more than $n $values.";
1099              
1100             } elsif ($type eq 'enum') {
1101 4         8 $return = "$name is not in the given list.";
1102              
1103             } elsif ($type eq 'equals') {
1104 5         13 my $field2 = $field_val->{"equals${dig}"};
1105 5   33     24 my $name2 = $field_val->{"equals${dig}_name"} || "the field $field2";
1106 5 50       17 $name2 =~ s/\$(\d+)/defined($ifs_match->[$1]) ? $ifs_match->[$1] : ''/eg if $ifs_match;
  1 100       8  
1107 5         13 $return = "$name did not equal $name2.";
1108              
1109             } elsif ($type eq 'min_len') {
1110 3         25 my $n = $field_val->{"min_len${dig}"};
1111 3 50       11 my $char = ($n == 1) ? 'character' : 'characters';
1112 3         7 $return = "$name was less than $n $char.";
1113              
1114             } elsif ($type eq 'max_len') {
1115 1         4 my $n = $field_val->{"max_len${dig}"};
1116 1 50       4 my $char = ($n == 1) ? 'character' : 'characters';
1117 1         4 $return = "$name was more than $n $char.";
1118              
1119             } elsif ($type eq 'max_in_set') {
1120 2         8 my $set = $field_val->{"max_in_set${dig}"};
1121 2         5 $return = "Too many fields were chosen from the set ($set)";
1122              
1123             } elsif ($type eq 'min_in_set') {
1124 3         9 my $set = $field_val->{"min_in_set${dig}"};
1125 3         8 $return = "Not enough fields were chosen from the set ($set)";
1126              
1127             } elsif ($type eq 'match') {
1128 10         23 $return = "$name contains invalid characters.";
1129              
1130             } elsif ($type eq 'compare') {
1131 16         34 $return = "$name did not fit comparison.";
1132              
1133             } elsif ($type eq 'sql') {
1134 0         0 $return = "$name did not match sql test.";
1135              
1136             } elsif ($type eq 'custom') {
1137 3         7 $return = "$name did not match custom test.";
1138              
1139             } elsif ($type eq 'type') {
1140 60         161 my $_type = $field_val->{"type${dig}"};
1141 60 100       142 $_type = 'hash' if ref($_type) eq 'HASH';
1142 60 100       136 $_type = '[]' if ref($_type) eq 'ARRAY';
1143 60         136 $return = "$name did not match type $_type.";
1144              
1145             } elsif ($type eq 'untaint') {
1146 1         2 $return = "$name cannot be untainted without one of the following checks: enum, equals, match, compare, sql, type, custom";
1147              
1148             } elsif ($type eq 'no_extra_fields') {
1149 4         11 $return = "$name should not be passed to validate.";
1150             }
1151             }
1152              
1153 160 50       353 die "Missing error on field $field for type $type$dig" if ! $return;
1154 160         546 return $return;
1155              
1156             }
1157              
1158             1;
1159              
1160             ### See the perldoc in CGI/Ex/Validate.pod