File Coverage

blib/lib/CGI/Validate.pm
Criterion Covered Total %
statement 45 95 47.3
branch 10 48 20.8
condition 3 17 17.6
subroutine 7 13 53.8
pod 0 8 0.0
total 65 181 35.9


line stmt bran cond sub pod time code
1             package CGI::Validate;
2              
3             =head1 NAME
4              
5             CGI::Validate - Advanced CGI form parser and type validation
6              
7             =head1 SYNOPSIS
8              
9             use CGI::Validate; # GetFormData() only
10             use CGI::Validate qw(:standard); # Normal use
11             use CGI::Validate qw(:subs); # Just functions
12             use CGI::Validate qw(:vars); # Just exception vars
13              
14             ## If you don't want it to check that every requested
15             ## element arrived you can use this. But I don't recommend it
16             ## for most users.
17             $CGI::Validate::Complete = 0;
18              
19             ## If you don't care that some fields in the form don't
20             ## actually match what you asked for. -I don't recommend
21             ## this unless you REALLY know what you're doing because this
22             ## normally meens you've got typo's in your HTML and we can't
23             ## catch them if you set this.
24             ## $CGI::Validate::IgnoreNonMatchingFields = 1;
25              
26             my $FieldOne = 'Default String';
27             my $FieldTwo = 8;
28             my $FieldThree = 'some default string';
29             my @FieldFour = (); ## For multi-select field
30             my @FieldFive = (); ## Ditto
31             my $EmailAddress= '';
32              
33             ## Try...
34             my $Query = GetFormData (
35             'FieldOne=s' => \$FieldOne, ## Required string
36             'FieldTwo=i' => \$FieldTwo, ## Required int
37             'FieldThree' => \$FieldThree, ## Auto converted to the ":s" type
38             'FieldFour=s' => \@FieldFour, ## Multi-select field of strings
39             'FieldFive=f' => \@FieldFive, ## Multi-select field of floats
40             'Email=e' => \$EmailAddress, ## Must 'look' like an email address
41             ) or do {
42             ## Catch... (wouldn't you just love a case statement here?)
43             if (%Missing) {
44             die "Missing form elements: " . join (' ', keys %Missing);
45             } elsif (%Invalid) {
46             die "Invalid form elements: " . join (' ', keys %Invalid);
47             } elsif (%Blank) {
48             die "Blank form elements: " . join (' ', keys %Blank);
49             } elsif (%InvalidType) {
50             die "Invalid data types for fields: " . join (' ', keys %InvalidType);
51             } else {
52             die "GetFormData() exception: $CGI::Validate::Error";
53             }
54             };
55              
56             ## If you only want to check the form data, but don't want to
57             ## have CGI::Validate set anything use this. -You still have full
58             ## access to the data via the normal B object that is returned.
59              
60             use CGI::Validate qw(CheckFormData); # not exported by default
61             my $Query = CheckFormData (
62             'FieldOne=s', 'FieldTwo=i', 'FieldThree', 'FieldFour',
63             'FieldFive', 'Email',
64             ) or do {
65             ... Same exceptions available as GetFormData above ...
66             };
67              
68             ## Need some of your own validation code to be used? Here is how you do it.
69             addExtensions (
70             myType => sub { $_[0] =~ /test/ },
71             fooBar => \&fooBar,
72             i_modify_the_actual_data => sub {
73             if ($_[0] =~ /test/) { ## data validation
74             $_[0] = 'whatever'; ## modify the data by alias
75             return 1;
76             } else {
77             return 0;
78             }
79             },
80             );
81             my $Query = GetFormData (
82             'foo=xmyType' => \$foo,
83             'bar=xfooBar' => \$bar,
84             'cat=xi_modify_the_actual_data' => \$cat,
85             );
86              
87              
88             ## Builtin data type checks available are:
89             s string # Any non-zero length value
90             w word # Must have at least one \w char
91             i integer # Integer value
92             f float # Float value
93             e email # Must match m/^\s*]+@[^@.<>]+(?:\.[^@.<>]+)+>?\s*$/
94             x extension # User extension type. See EXTENSIONS below.
95              
96              
97             =cut
98              
99 1     1   1115 BEGIN { require 5.004 }
100 1     1   6 use strict;
  1         2  
  1         39  
101 1         445 use vars qw(
102             @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION @ISA
103             $Complete $IgnoreNonMatchingFields $Error
104             %Missing %Invalid %Blank %InvalidType
105 1     1   6 );
  1         2  
106             require Exporter;
107             @ISA = qw(Exporter);
108             @EXPORT = qw(GetFormData);
109             @EXPORT_OK = qw(%Missing %Invalid %Blank %InvalidType addExtensions GetFormData CheckFormData);
110             %EXPORT_TAGS = (
111             standard => [ @EXPORT_OK ],
112             all => [ @EXPORT_OK ], # depreciated
113             vars => [ qw(%Missing %Invalid %Blank %InvalidType) ],
114             subs => [ qw(addExtensions GetFormData CheckFormData) ],
115             );
116             $VERSION = do { my @r = (q$Revision: 2.0 $ =~ /\d+/g); sprintf '%d.%03d'.'%02d' x ($#r-1), @r};
117              
118 1     1   6397 use CGI 2.30;
  1         19827  
  1         8  
119 1     1   60 use Carp;
  1         2  
  1         1575  
120              
121             ## User settable globals
122             $Complete = 1;
123             $IgnoreNonMatchingFields = 0;
124              
125             ## Code settable globals
126             my %TYPES = (
127             's' => [ 'string', \&CheckString ],
128             'w' => [ 'word', \&CheckWord ],
129             'i' => [ 'integer', \&CheckInt ],
130             'f' => [ 'float', \&CheckFloat ],
131             'e' => [ 'email', \&CheckEmail ],
132             'x' => [ 'extension', sub { confess q(PANIC: Can't Happen[tm]: Sorry, but type 'x' is not supported in raw form. See EXTENSIONS in perldoc ) . __FILE__ . '. ' } ],
133             );
134              
135             sub addExtensions {
136 1 50   1 0 97 my %exts = @_
137             or confess qq(usage: addExtentions ('name' => sub { validation code }));
138 1         6 while (my ($ext, $sub) = each %exts) {
139 2 50       6 ref $sub eq 'CODE'
140             or confess qq($sub is not a CODE ref for extension type '$ext');
141 2         16 $TYPES{"x$ext"} = [ "x$ext", $sub ];
142             }
143             }
144              
145             sub GetFormData {
146 1     1 0 27 my %fields = (); ## We load this latter from @_
147 1         3 my %form = (); ## Values from the form actually gave us
148              
149             ## Damn CGI changed it's frigging interface... :-(
150 1         9 my $query = new CGI;
151 1         5809 %form = %{ $query };
  1         6  
152              
153             ## Use this code below if the CGI object form gets changed. Yes, we're breaking OO rules, so kill me I need the speed!
154             # foreach my $name ($query->param) {
155             # $form{$name} = [ $query->param ($name) ];
156             # }
157              
158 1         3 %Missing = (); ## We use these to do our $Complete testing
159 1         2 %Invalid = (); ## Fields they didn't ask for
160 1         3 %Blank = (); ## Fields left blank, that have a required modifier
161 1         2 %InvalidType = (); ## Fields with data not matching there type defs
162              
163             ## Program's validation spec part
164             ## Load %fields, and add :s type to fields that don't contain one
165 1         7 for (my $arg=0; $arg <= $#_; $arg += 2) {
166             ## Split field in to name, if it's optional, and it's required type
167 22         74 my ($field, $optional, $type) = ($_[$arg] =~ /^([^:=]+)([:=]?)(\w*)/);
168              
169             ## Moron check...
170 22 50       44 unless ($field) { $Error = qq(Invalid arg "$_[$arg]" given to GetFormData(): No field name???); return }
  0         0  
  0         0  
171              
172             ## Optional argument, or required? Default optional
173 22 50       29 $optional = $optional eq '=' ? 0 : 1;
174              
175 22   50     42 $type ||= 's'; ## Default optional string
176              
177 22 50       41 $TYPES{$type}
178             or ($Error = qq(Invalid type "$type" given for field "$field"), return);
179 22         27 $type = $TYPES{$type};
180              
181 22 50       70 $fields{$field}{reference} = $_[ $arg + 1 ]
182             or ($Error = qq(No place given to stick the value for "$field"), return);
183             ## Check for correct reference type
184 22 50 66     66 (ref $fields{$field}{reference} eq 'SCALAR') || (ref $fields{$field}{reference} eq 'ARRAY')
185 0         0 or ($Error = qq(Invalid reference type "@{[ ref ($fields{$field}{reference}) ]}" given for "$field". Must be SCALAR or ARRAY), return);
186              
187 22         35 $fields{$field}{optional} = $optional;
188 22         63 $fields{$field}{type} = $type;
189             }
190              
191             ## $Complete checking:
192 1 50       4 if ($Complete) {
193 1         6 foreach my $field (keys %fields) {
194             ## Make sure we have it
195 22 50       46 unless (exists $form{$field}) {
196 22         56 $Missing{$field} = qq(Missing required form element "$field");
197             }
198             }
199             }
200              
201             ## Form's data
202             ## Check all form fields for type et al...
203 1         5 foreach my $field ($query->param) {
204             ## Did we get a bad field from the form?
205 1 50       21 unless (exists $fields{$field}) {
206             ## Do we care?
207 0 0       0 unless ($IgnoreNonMatchingFields) {
208             # push @Invalid, "Non-matching field: $field";
209 0         0 $Invalid{$field} = "Non-matching field: $field";
210             }
211 0         0 next;
212             }
213              
214             # my @values = $query->param ($field);
215              
216 1 0 0     1 unless (scalar @{ $form{$field} } or $fields{$field}{optional}) {
  1         50  
217 0           $Blank{$field} = qq(Required field "$field" contains no data);
218 0           next;
219             }
220              
221             ## Type checking
222 0           my $argNum = 0;
223 0           foreach my $arg (@{ $form{$field} }) {
  0            
224 0           $argNum++;
225              
226             ## Hmm, is the field empty?
227 0 0         if (length $arg > 0) {
228             ## Check the data to make sure it's the right type.
229             ## Since $arg is aliased from @values, the sub can modify the
230             ## actual data if it wants to (filter type check).
231 0 0         unless ( $fields{$field}{type}[1]->($arg) ) {
232 0 0         if (scalar @{ $form{$field} } > 1) {
  0            
233 0           $InvalidType{$field} = qq(Invalid data type found for array field $field, indices $argNum ($fields{$field}{type}[0] expected, found "$arg"));
234             } else {
235 0           $InvalidType{$field} = qq(Invalid data type found for field $field ($fields{$field}{type}[0] expected, found "$arg"));
236             }
237             }
238             } else {
239 0 0         unless ($fields{$field}{optional}) {
240             ## Hmm, blank field in multi-select? Odd if that's the case
241 0 0         if (scalar @{ $form{$field} } > 1) {
  0            
242 0           $Blank{$field} = qq(Required field "$field" contains no data in $argNum segment);
243             } else {
244 0           $Blank{$field} = qq(Required field "$field" contains no data);
245             }
246             }
247             }
248             }
249 0 0         if (ref $fields{$field}{reference} eq 'ARRAY') {
250 0           @{ $fields{$field}{reference} } = @{ $form{$field} };
  0            
  0            
251             } else {
252 0           ${ $fields{$field}{reference} } = $form{$field}->[0];
  0            
253             }
254             }
255              
256             ## Ok, did all that go well?
257 0 0 0       if (%Missing or %Invalid or %Blank or %InvalidType) {
      0        
      0        
258 0           $Error = join ",\n",
259             values %Missing,
260             values %Invalid,
261             values %Blank,
262             values %InvalidType;
263 0           return;
264             } else {
265 0           return $query;
266             }
267             }
268              
269             ## Default type handlers
270              
271             sub CheckString {
272 0     0 0   my $value = shift;
273             ## Any non-zero length string is valid
274 0 0         return 1 if (length $value > 0);
275 0           return;
276             }
277              
278             sub CheckWord {
279 0     0 0   my $value = shift;
280             ## Must have at least \w char
281 0 0         return 1 if ($value =~ /\w/);
282 0           return;
283             }
284              
285             sub CheckInt {
286 0     0 0   my $value = shift;
287 0 0         return 1 if ($value =~ /^\d+$/);
288 0           return;
289             }
290              
291             sub CheckFloat {
292 0     0 0   my $value = shift;
293              
294             ## Must be in a "3.0" or "30" format
295             # return 1 if ($value =~ /^\d+\.\d+$/);
296 0 0         return 1 if ($value =~ /^\d+.?\d*$/);
297 0           return;
298             }
299              
300             sub CheckEmail {
301 0     0 0   my $value = shift;
302             ## Must look like a "standard" email address. White space
303             ## is permitted on the ends though.
304 0 0         return 1 if ($value =~ m/^\s*]+@[^@.<>]+(?:\.[^@.<>]+)+>?\s*$/);
305             }
306              
307             sub CheckFormData {
308 0     0 0   my %types = ();
309 0           @types{@_} = \(0 .. $#_); # Black magic, beware...
310 0           return GetFormData (%types);
311             };
312              
313             1;
314              
315             __END__