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__ |