File Coverage

blib/lib/CGI/Pure.pm
Criterion Covered Total %
statement 160 291 54.9
branch 51 120 42.5
condition 14 24 58.3
subroutine 26 32 81.2
pod 10 10 100.0
total 261 477 54.7


line stmt bran cond sub pod time code
1             package CGI::Pure;
2              
3 14     14   75734 use strict;
  14         115  
  14         397  
4 14     14   66 use warnings;
  14         28  
  14         437  
5              
6 14     14   6340 use CGI::Deurl::XS qw(parse_query_string);
  14         43371  
  14         802  
7 14     14   6288 use Class::Utils qw(set_params);
  14         398894  
  14         259  
8 14     14   9004 use Encode qw(decode_utf8);
  14         143121  
  14         953  
9 14     14   108 use English qw(-no_match_vars);
  14         24  
  14         103  
10 14     14   4755 use Error::Pure qw(err);
  14         29  
  14         606  
11 14     14   81 use List::MoreUtils qw(none);
  14         25  
  14         88  
12 14     14   9083 use Readonly;
  14         28  
  14         622  
13 14     14   6310 use URI::Escape qw(uri_escape uri_escape_utf8 uri_unescape);
  14         20659  
  14         47046  
14              
15             # Constants.
16             Readonly::Scalar my $EMPTY_STR => q{};
17             Readonly::Scalar my $POST_MAX => 102_400;
18             Readonly::Scalar my $POST_MAX_NO_LIMIT => -1;
19             Readonly::Scalar my $BLOCK_SIZE => 4_096;
20             Readonly::Array my @PAR_SEP => (q{&}, q{;});
21              
22             our $VERSION = 0.08;
23              
24             # Constructor.
25             sub new {
26 40     40 1 24960 my ($class, @params) = @_;
27              
28             # Create object.
29 40         114 my $self = bless {}, $class;
30              
31             # CRLF separator.
32 40         133 $self->{'crlf'} = undef;
33              
34             # Disable upload.
35 40         83 $self->{'disable_upload'} = 1;
36              
37             # Init.
38 40         72 $self->{'init'} = undef;
39              
40             # Parameter separator.
41 40         80 $self->{'par_sep'} = q{&};
42              
43             # Use a post max of 100K ($POST_MAX),
44             # set to -1 ($POST_MAX_NO_LIMIT) for no limits.
45 40         75 $self->{'post_max'} = $POST_MAX;
46              
47             # Save query data from server.
48 40         68 $self->{'save_query_data'} = 0;
49              
50             # UTF8 CGI params.
51 40         68 $self->{'utf8'} = 1;
52              
53             # Process params.
54 40         152 set_params($self, @params);
55              
56             # Check to parameter separator.
57 39 100   41   606 if (none { $_ eq $self->{'par_sep'} } @PAR_SEP) {
  41         483  
58 1         11 err "Bad parameter separator '$self->{'par_sep'}'.";
59             }
60              
61             # Global object variables.
62 38         470 $self->_global_variables;
63              
64             # Initialization.
65 38         60 my $init = $self->{'init'};
66 38         66 delete $self->{'init'};
67 38         104 $self->_initialize($init);
68              
69             # Object.
70 38         158 return $self;
71             }
72              
73             # Append param value.
74             sub append_param {
75 14     14 1 1312 my ($self, $param, @values) = @_;
76              
77             # Clean from undefined values.
78 14         37 my @new_values = _remove_undef(@values);
79              
80             # Process scalars, arrays, err on other.
81 14         24 my @values_to_add;
82 14         42 foreach my $value (@new_values) {
83 15 100       50 if (ref $value eq 'ARRAY') {
    100          
84 2         3 push @values_to_add, @{$value};
  2         5  
85             } elsif (ref $value eq '') {
86 11         30 push @values_to_add, $value;
87             } else {
88 2         10 err "Parameter '$param' has bad value.";
89             }
90             }
91 12         44 $self->_add_param($param, [@values_to_add]);
92              
93 12         36 return $self->param($param);
94             }
95              
96             # Clone class to my class.
97             sub clone {
98 2     2 1 9 my ($self, $class) = @_;
99 2         7 foreach my $param ($class->param) {
100 3         19 $self->param($param, $class->param($param));
101             }
102 2         5 return;
103             }
104              
105             # Delete param.
106             sub delete_param {
107 4     4 1 2927 my ($self, $param) = @_;
108 4 100       16 if (! defined $self->{'.parameters'}->{$param}) {
109 1         4 return;
110             }
111 3         8 delete $self->{'.parameters'}->{$param};
112 3         7 return 1;
113             }
114              
115             # Delete all params.
116             sub delete_all_params {
117 2     2 1 820 my $self = shift;
118 2         6 delete $self->{'.parameters'};
119 2         5 $self->{'.parameters'} = {};
120 2         4 return;
121             }
122              
123             # Return param[s]. If sets parameters, than overwrite.
124             sub param {
125 79     79 1 9914 my ($self, $param, @values) = @_;
126              
127             # Return list of all params.
128 79 100       179 if (! defined $param) {
129 25         38 return sort keys %{$self->{'.parameters'}};
  25         162  
130             }
131              
132             # Clean from undefined values.
133 54         119 my @new_values = _remove_undef(@values);
134              
135             # Return values for $param.
136 54 100       116 if (! @new_values) {
137 45 100       111 if (! exists $self->{'.parameters'}->{$param}) {
138 4         14 return ();
139             }
140              
141             # Values exists, than sets them.
142             } else {
143 9 100       41 $self->_add_param($param, (ref $new_values[0] eq 'ARRAY'
144             ? $new_values[0] : [@new_values]), 'overwrite');
145             }
146              
147             # Return values of param, or first value of param.
148 25         120 return wantarray ? sort @{$self->{'.parameters'}->{$param}}
149 50 100       190 : $self->{'.parameters'}->{$param}->[0];
150             }
151              
152             # Gets query data from server.
153             sub query_data {
154 5     5 1 21 my $self = shift;
155 5 100       11 if ($self->{'save_query_data'}) {
156 4         12 return $self->{'.query_data'};
157             } else {
158 1         3 return 'Not saved query data.';
159             }
160             }
161              
162             # Return actual query string.
163             sub query_string {
164 4     4 1 16 my $self = shift;
165 4         7 my @pairs;
166 4         7 foreach my $param ($self->param) {
167 5         11 foreach my $value ($self->param($param)) {
168 11         23 push @pairs, $self->_uri_escape($param).q{=}.
169             $self->_uri_escape($value);
170             }
171             }
172 4         17 return join $self->{'par_sep'}, @pairs;
173             }
174              
175             # Upload file from tmp.
176             sub upload {
177 0     0 1 0 my ($self, $filename, $writefile) = @_;
178 0 0       0 if ($ENV{'CONTENT_TYPE'} !~ m/^multipart\/form-data/ismx) {
179 0         0 err 'File uploads only work if you specify '.
180             'enctype="multipart/form-data" in your form.';
181             }
182 0 0       0 if (! $filename) {;
183 0 0       0 if ($writefile) {
184 0         0 err 'No filename submitted for upload to '.
185             "'$writefile'.";
186             }
187             return $self->{'.filehandles'}
188 0 0       0 ? keys %{$self->{'.filehandles'}} : ();
  0         0  
189             }
190 0         0 my $fh = $self->{'.filehandles'}->{$filename};
191 0 0       0 if ($fh) {
192              
193             # Get ready for reading.
194 0         0 seek $fh, 0, 0;
195              
196 0 0       0 if (! $writefile) {
197 0         0 return $fh;
198             }
199 0         0 binmode $fh;
200 0         0 my $buffer;
201             my $out;
202 0 0       0 if (! open $out, '>', $writefile) {
203 0         0 err "Cannot write file '$writefile': $!.";
204             }
205 0         0 binmode $out;
206 0         0 while (read $fh, $buffer, $BLOCK_SIZE) {
207 0         0 print {$out} $buffer;
  0         0  
208             }
209 0 0       0 if (! close $out) {
210 0         0 err "Cannot close file '$writefile': $!.";
211             }
212 0         0 $self->{'.filehandles'}->{$filename} = undef;
213 0         0 undef $fh;
214             } else {
215 0         0 err "No filehandle for '$filename'. ".
216             'Are uploads enabled (disable_upload = 0)? '.
217             'Is post_max big enough?';
218             }
219 0         0 return;
220             }
221              
222             # Return informations from uploaded files.
223             sub upload_info {
224 0     0 1 0 my ($self, $filename, $info) = @_;
225 0 0       0 if ($ENV{'CONTENT_TYPE'} !~ m/^multipart\/form-data/ismx) {
226 0         0 err 'File uploads only work if you '.
227             'specify enctype="multipart/form-data" in your '.
228             'form.';
229             }
230 0 0       0 if (! $filename) {
231 0         0 return keys %{$self->{'.tmpfiles'}};
  0         0  
232             }
233 0 0       0 if ($info =~ m/mime/ims) {
234 0         0 return $self->{'.tmpfiles'}->{$filename}->{'mime'}
235             }
236 0         0 return $self->{'.tmpfiles'}->{$filename}->{'size'};
237             }
238              
239             # Adding param.
240             sub _add_param {
241 51     51   131 my ($self, $param, $value, $overwrite) = @_;
242 51 50       144 if (! defined $param) {
243 0         0 return ();
244             }
245 51 100 100     232 if ($overwrite
246             || ! exists $self->{'.parameters'}->{$param}) {
247              
248 48         120 $self->{'.parameters'}->{$param} = [];
249             }
250 51 100       157 my @values = ref $value eq 'ARRAY' ? @{$value} : ($value);
  35         90  
251 51         94 foreach my $value (@values) {
252 85         109 push @{$self->{'.parameters'}->{$param}}, $value;
  85         207  
253             }
254 51         116 return;
255             }
256              
257             # Common parsing from any methods..
258             sub _common_parse {
259 26     26   41 my $self = shift;
260 26         37 my $data;
261              
262             # Information from server.
263 26   50     128 my $type = $ENV{'CONTENT_TYPE'} || 'No CONTENT_TYPE received';
264 26   100     93 my $length = $ENV{'CONTENT_LENGTH'} || 0;
265 26   100     91 my $method = $ENV{'REQUEST_METHOD'} || 'No REQUEST_METHOD received';
266              
267             # Multipart form data.
268 26 50 66     198 if ($length && $type =~ m/^multipart\/form-data/imsx) {
    100 66        
    100          
269              
270             # Get data_length, store data to internal structure.
271 0         0 my $got_data_length = $self->_parse_multipart;
272              
273             # Bad data length vs content_length.
274 0 0       0 if ($length != $got_data_length) {
275 0         0 err "500 Bad read! wanted $length, got ".
276             "$got_data_length.";
277             }
278              
279 0         0 return;
280              
281             # POST method.
282             } elsif ($method eq 'POST') {
283              
284             # Maximal post length is above my length.
285 2 50 33     17 if ($self->{'post_max'} != $POST_MAX_NO_LIMIT
    50          
286             and $length > $self->{'post_max'}) {
287              
288 0         0 err '413 Request entity too large: '.
289             "$length bytes on STDIN exceeds ".
290             'post_max !';
291              
292             # Get data.
293             } elsif ($length) {
294 2         12 read STDIN, $data, $length;
295             }
296              
297             # Save data for post.
298 2 100       54 if ($self->{'save_query_data'}) {
299 1         3 $self->{'.query_data'} = $data;
300             }
301              
302             # Bad length of data.
303 2 50       8 if ($length != length $data) {
304 0         0 err "500 Bad read! wanted $length, got ".
305             (length $data).q{.};
306             }
307              
308             # GET/HEAD method.
309             } elsif ($method eq 'GET' || $method eq 'HEAD') {
310 5   33     20 $data = $ENV{'QUERY_STRING'} || $EMPTY_STR;
311 5 100       19 if ($self->{'save_query_data'}) {
312 1         2 $self->{'.query_data'} .= $data;
313             }
314             }
315              
316             # Parse params.
317 26 100       63 if ($data) {
318 7         21 $self->_parse_params($data);
319             }
320 26         50 return;
321             }
322              
323             # Define the CRLF sequence.
324             sub _crlf {
325 0     0   0 my $self = shift;
326              
327             # If not defined.
328 0 0       0 if (! defined $self->{'crlf'}) {
329              
330             # VMS.
331 0 0       0 if ($OSNAME =~ m/VMS/ims) {
332 0         0 $self->{'crlf'} = "\n";
333              
334             # EBCDIC systems.
335             } elsif ("\t" eq "\011") {
336 0         0 $self->{'crlf'} = "\015\012";
337              
338             # Other.
339             } else {
340             $self->{'crlf'} = "\r\n";
341             }
342             }
343              
344             # Return sequence.
345 0         0 return $self->{'crlf'};
346             }
347              
348             # Sets global object variables.
349             sub _global_variables {
350 38     38   66 my $self = shift;
351 38         110 $self->{'.parameters'} = {};
352 38         81 $self->{'.query_data'} = $EMPTY_STR;
353 38         62 return;
354             }
355              
356             # Initializating CGI::Pure with something input methods.
357             sub _initialize {
358 38     38   83 my ($self, $init) = @_;
359              
360             # Initialize from QUERY_STRING, STDIN or @ARGV.
361 38 100       129 if (! defined $init) {
    100          
    100          
362 26         66 $self->_common_parse;
363              
364             # Initialize from param hash.
365             } elsif (ref $init eq 'HASH') {
366 7         14 foreach my $param (keys %{$init}) {
  7         25  
367 13         40 $self->_add_param($param, $init->{$param});
368             }
369              
370             # Inicialize from CGI::Pure object.
371             # XXX Mod_perl?
372 5         39 } elsif (eval { $init->isa('CGI::Pure') }) {
373 1         5 $self->clone($init);
374              
375             # Initialize from a query string.
376             } else {
377 4         16 $self->_parse_params($init);
378             }
379              
380 38         56 return;
381             }
382              
383             # Parse multipart data.
384             sub _parse_multipart {
385 0     0   0 my $self = shift;
386 0         0 my ($boundary) = $ENV{'CONTENT_TYPE'}
387             =~ /
388             boundary=
389             \"?([^\";,]+)\"?
390             /msx;
391 0 0       0 if (! $boundary) {
392 0         0 err '400 No boundary supplied for multipart/form-data.';
393             }
394              
395             # BUG: IE 3.01 on the Macintosh uses just the boundary, forgetting
396             # the --
397 0 0 0     0 if (! exists $ENV{'HTTP_USER_AGENT'} || $ENV{'HTTP_USER_AGENT'} !~ m/
398             MSIE\s+
399             3\.0[12];
400             \s*
401             Mac
402             /imsx) {
403              
404 0         0 $boundary = q{--}.$boundary;
405             }
406              
407 0         0 $boundary = quotemeta $boundary;
408 0         0 my $got_data_length = 0;
409 0         0 my $data = $EMPTY_STR;
410 0         0 my $read;
411 0         0 my $CRLF = $self->_crlf;
412              
413             READ:
414 0         0 while (read STDIN, $read, $BLOCK_SIZE) {
415              
416             # Adding post data.
417 0 0       0 if ($self->{'save_query_data'}) {
418 0         0 $self->{'.query_data'} .= $read;
419             }
420              
421 0         0 $data .= $read;
422 0         0 $got_data_length += length $read;
423              
424             BOUNDARY:
425 0         0 while ($data =~ m/^$boundary$CRLF/ms) {
426 0         0 my $header;
427              
428             # Get header, delimited by first two CRLFs we see.
429 0 0       0 if ($data !~ m/^([\040-\176$CRLF]+?$CRLF$CRLF)/ms) {
430 0         0 next READ;
431             }
432             # XXX Proc tohle nemuze byt? /x tam dela nejake potize.
433             # if ($data !~ m/^(
434             # [\040-\176$CRLF]+?
435             # $CRLF
436             # $CRLF
437             # )/msx) {
438             #
439             # next READ;
440             # }
441 0         0 $header = $1;
442              
443             # Unhold header per RFC822.
444 0         0 (my $unfold = $1) =~ s/$CRLF\s+/\ /gms;
445              
446 0         0 my ($param) = $unfold =~ m/
447             form-data;
448             \s+
449             name="?([^\";]*)"?
450             /msx;
451 0         0 my ($filename) = $unfold =~ m/
452             name="?\Q$param\E"?;
453             \s+
454             filename="?([^\"]*)"?
455             /msx;
456 0 0       0 if ($filename) {
457 0         0 my ($mime) = $unfold =~ m/
458             Content-Type:
459             \s+
460             ([-\w\/]+)
461             /imsx;
462              
463             # Trim off header.
464 0         0 $data =~ s/^\Q$header\E//ms;
465              
466 0         0 ($got_data_length, $data, my $fh, my $size)
467             = $self->_save_tmpfile($boundary,
468             $filename, $got_data_length, $data);
469              
470 0         0 $self->_add_param($param, $filename);
471              
472             # Filehandle.
473 0 0       0 if ($fh) {
474 0         0 $self->{'.filehandles'}->{$filename}
475             = $fh;
476             }
477              
478             # Information about file.
479 0 0       0 if ($size) {
480 0         0 $self->{'.tmpfiles'}->{$filename} = {
481             'size' => $size,
482             'mime' => $mime,
483             };
484             }
485 0         0 next BOUNDARY;
486             }
487 0 0       0 if ($data !~ s/^\Q$header\E(.*?)$CRLF(?=$boundary)//s) {
488 0         0 next READ;
489             }
490             # XXX /x
491             # if ($data !~ s/^
492             # \Q$header\E
493             # (.*?)
494             # $CRLF
495             # (?=$boundary)
496             # //msx) {
497             #
498             # next READ;
499             # }
500 0         0 my $param_value;
501 0 0       0 if ($self->{'utf8'}) {
502 0         0 $param_value = decode_utf8($1);
503             } else {
504 0         0 $param_value = $1;
505             }
506 0         0 $self->_add_param($param, $param_value);
507             }
508             }
509              
510             # Length of data.
511 0         0 return $got_data_length;
512             }
513              
514             # Parse params from data.
515             sub _parse_params {
516 13     13   31 my ($self, $data) = @_;
517 13 50       35 if (! defined $data) {
518 0         0 return ();
519             }
520              
521             # Parse params.
522 13         115 my $pairs_hr = parse_query_string($data);
523 13         42 foreach my $key (keys %{$pairs_hr}) {
  13         46  
524              
525             # Value processing.
526 17         24 my $value;
527 17 100       39 if ($self->{'utf8'}) {
528 15 100       42 if (ref $pairs_hr->{$key} eq 'ARRAY') {
529 7         14 my @decoded = ();
530 7         8 foreach my $val (@{$pairs_hr->{$key}}) {
  7         16  
531 21         337 push @decoded, decode_utf8($val);
532             }
533 7         125 $value = \@decoded;
534             } else {
535 8         33 $value = decode_utf8($pairs_hr->{$key});
536             }
537             } else {
538 2         4 $value = $pairs_hr->{$key};
539             }
540              
541             # Add parameter.
542 17         350 $self->_add_param($key, $value);
543             }
544 13         45 return;
545             }
546              
547             # Remove undefined values.
548             sub _remove_undef {
549 68     68   120 my (@values) = @_;
550              
551 68         110 my @new_values = grep { defined $_ } @values;
  28         80  
552              
553 68         125 return @new_values;
554             }
555              
556             # Save file from multiform.
557             sub _save_tmpfile {
558 0     0   0 my ($self, $boundary, $filename, $got_data_length, $data) = @_;
559 0         0 my $fh;
560 0         0 my $CRLF = $self->_crlf;
561 0         0 my $file_size = 0;
562 0 0       0 if ($self->{'disable_upload'}) {
    0          
563 0         0 err '405 Not Allowed - File uploads are disabled.';
564             } elsif ($filename) {
565 0         0 eval {
566 0         0 require IO::File;
567             };
568 0 0       0 if ($EVAL_ERROR) {
569 0         0 err "500 IO::File is not available $EVAL_ERROR.";
570             }
571 0         0 $fh = new_tmpfile IO::File;
572 0 0       0 if (! $fh) {
573 0         0 err '500 IO::File can\'t create new temp_file.';
574             }
575             }
576 0         0 binmode $fh;
577 0         0 while (1) {
578 0         0 my $buffer = $data;
579 0         0 read STDIN, $data, $BLOCK_SIZE;
580 0 0       0 if (! $data) {
581 0         0 $data = $EMPTY_STR;
582             }
583 0         0 $got_data_length += length $data;
584 0 0       0 if ("$buffer$data" =~ m/$boundary/ms) {
585 0         0 $data = $buffer.$data;
586 0         0 last;
587             }
588              
589             # BUG: Fixed hanging bug if browser terminates upload part way.
590 0 0       0 if (! $data) {
591 0         0 undef $fh;
592 0         0 err '400 Malformed multipart, no terminating '.
593             'boundary.';
594             }
595              
596             # We do not have partial boundary so print to file if valid $fh.
597 0         0 print {$fh} $buffer;
  0         0  
598 0         0 $file_size += length $buffer;
599             }
600 0         0 $data =~ s/^
601             (.*?)
602             $CRLF
603             (?=$boundary)
604             //smx;
605              
606             # Print remainder of file if value $fh.
607 0 0       0 if ($1) {
608 0         0 print {$fh} $1;
  0         0  
609 0         0 $file_size += length $1;
610             }
611              
612 0         0 return $got_data_length, $data, $fh, $file_size;
613             }
614              
615             # Escapes uri.
616             sub _uri_escape {
617 22     22   39 my ($self, $string) = @_;
618 22 50       37 if ($self->{'utf8'}) {
619 22         38 $string = uri_escape_utf8($string);
620             } else {
621 0         0 $string = uri_escape($string);
622             }
623 22         458 $string =~ s/\ /\+/gsm;
624 22         56 return $string;
625             }
626              
627             # Unescapes uri.
628             sub _uri_unescape {
629 0     0     my ($self, $string) = @_;
630 0           $string =~ s/\+/\ /gsm;
631 0           return uri_unescape($string);
632             }
633              
634             1;
635              
636             __END__