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   79879 use strict;
  14         116  
  14         409  
4 14     14   69 use warnings;
  14         31  
  14         430  
5              
6 14     14   6936 use CGI::Deurl::XS qw(parse_query_string);
  14         45335  
  14         810  
7 14     14   6984 use Class::Utils qw(set_params);
  14         434586  
  14         274  
8 14     14   9815 use Encode qw(decode_utf8);
  14         150572  
  14         978  
9 14     14   113 use English qw(-no_match_vars);
  14         25  
  14         94  
10 14     14   4873 use Error::Pure qw(err);
  14         34  
  14         586  
11 14     14   80 use List::MoreUtils qw(none);
  14         22  
  14         90  
12 14     14   9264 use Readonly;
  14         27  
  14         776  
13 14     14   7322 use URI::Escape qw(uri_escape uri_escape_utf8 uri_unescape);
  14         21046  
  14         49328  
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.09;
23              
24             # Constructor.
25             sub new {
26 40     40 1 28920 my ($class, @params) = @_;
27              
28             # Create object.
29 40         106 my $self = bless {}, $class;
30              
31             # CRLF separator.
32 40         136 $self->{'crlf'} = undef;
33              
34             # Disable upload.
35 40         80 $self->{'disable_upload'} = 1;
36              
37             # Init.
38 40         73 $self->{'init'} = undef;
39              
40             # Parameter separator.
41 40         83 $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         76 $self->{'post_max'} = $POST_MAX;
46              
47             # Save query data from server.
48 40         65 $self->{'save_query_data'} = 0;
49              
50             # UTF8 CGI params.
51 40         70 $self->{'utf8'} = 1;
52              
53             # Process params.
54 40         160 set_params($self, @params);
55              
56             # Check to parameter separator.
57 39 100   41   633 if (none { $_ eq $self->{'par_sep'} } @PAR_SEP) {
  41         456  
58 1         13 err "Bad parameter separator '$self->{'par_sep'}'.";
59             }
60              
61             # Global object variables.
62 38         476 $self->_global_variables;
63              
64             # Initialization.
65 38         58 my $init = $self->{'init'};
66 38         74 delete $self->{'init'};
67 38         99 $self->_initialize($init);
68              
69             # Object.
70 38         162 return $self;
71             }
72              
73             # Append param value.
74             sub append_param {
75 14     14 1 1548 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         31 my @values_to_add;
82 14         32 foreach my $value (@new_values) {
83 15 100       47 if (ref $value eq 'ARRAY') {
    100          
84 2         5 push @values_to_add, @{$value};
  2         3  
85             } elsif (ref $value eq '') {
86 11         31 push @values_to_add, $value;
87             } else {
88 2         11 err "Parameter '$param' has bad value.";
89             }
90             }
91 12         48 $self->_add_param($param, [@values_to_add]);
92              
93 12         45 return $self->param($param);
94             }
95              
96             # Clone class to my class.
97             sub clone {
98 2     2 1 10 my ($self, $class) = @_;
99 2         19 foreach my $param ($class->param) {
100 3         9 $self->param($param, $class->param($param));
101             }
102 2         6 return;
103             }
104              
105             # Delete param.
106             sub delete_param {
107 4     4 1 3015 my ($self, $param) = @_;
108 4 100       17 if (! defined $self->{'.parameters'}->{$param}) {
109 1         7 return;
110             }
111 3         7 delete $self->{'.parameters'}->{$param};
112 3         18 return 1;
113             }
114              
115             # Delete all params.
116             sub delete_all_params {
117 2     2 1 1256 my $self = shift;
118 2         8 delete $self->{'.parameters'};
119 2         4 $self->{'.parameters'} = {};
120 2         5 return;
121             }
122              
123             # Return param[s]. If sets parameters, than overwrite.
124             sub param {
125 79     79 1 12173 my ($self, $param, @values) = @_;
126              
127             # Return list of all params.
128 79 100       228 if (! defined $param) {
129 25         41 return sort keys %{$self->{'.parameters'}};
  25         155  
130             }
131              
132             # Clean from undefined values.
133 54         137 my @new_values = _remove_undef(@values);
134              
135             # Return values for $param.
136 54 100       135 if (! @new_values) {
137 45 100       124 if (! exists $self->{'.parameters'}->{$param}) {
138 4         14 return ();
139             }
140              
141             # Values exists, than sets them.
142             } else {
143 9 100       57 $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         119 return wantarray ? sort @{$self->{'.parameters'}->{$param}}
149 50 100       181 : $self->{'.parameters'}->{$param}->[0];
150             }
151              
152             # Gets query data from server.
153             sub query_data {
154 5     5 1 23 my $self = shift;
155 5 100       10 if ($self->{'save_query_data'}) {
156 4         10 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 18 my $self = shift;
165 4         7 my @pairs;
166 4         10 foreach my $param ($self->param) {
167 5         11 foreach my $value ($self->param($param)) {
168 11         25 push @pairs, $self->_uri_escape($param).q{=}.
169             $self->_uri_escape($value);
170             }
171             }
172 4         28 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   116 my ($self, $param, $value, $overwrite) = @_;
242 51 50       134 if (! defined $param) {
243 0         0 return ();
244             }
245 51 100 100     220 if ($overwrite
246             || ! exists $self->{'.parameters'}->{$param}) {
247              
248 48         117 $self->{'.parameters'}->{$param} = [];
249             }
250 51 100       137 my @values = ref $value eq 'ARRAY' ? @{$value} : ($value);
  35         89  
251 51         93 foreach my $value (@values) {
252 85         107 push @{$self->{'.parameters'}->{$param}}, $value;
  85         196  
253             }
254 51         120 return;
255             }
256              
257             # Common parsing from any methods..
258             sub _common_parse {
259 26     26   50 my $self = shift;
260 26         39 my $data;
261              
262             # Information from server.
263 26   50     124 my $type = $ENV{'CONTENT_TYPE'} || 'No CONTENT_TYPE received';
264 26   100     92 my $length = $ENV{'CONTENT_LENGTH'} || 0;
265 26   100     88 my $method = $ENV{'REQUEST_METHOD'} || 'No REQUEST_METHOD received';
266              
267             # Multipart form data.
268 26 50 66     214 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     18 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         13 read STDIN, $data, $length;
295             }
296              
297             # Save data for post.
298 2 100       55 if ($self->{'save_query_data'}) {
299 1         2 $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       17 if ($self->{'save_query_data'}) {
312 1         3 $self->{'.query_data'} .= $data;
313             }
314             }
315              
316             # Parse params.
317 26 100       64 if ($data) {
318 7         21 $self->_parse_params($data);
319             }
320 26         51 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   65 my $self = shift;
351 38         120 $self->{'.parameters'} = {};
352 38         78 $self->{'.query_data'} = $EMPTY_STR;
353 38         64 return;
354             }
355              
356             # Initializating CGI::Pure with something input methods.
357             sub _initialize {
358 38     38   85 my ($self, $init) = @_;
359              
360             # Initialize from QUERY_STRING, STDIN or @ARGV.
361 38 100       125 if (! defined $init) {
    100          
    100          
362 26         64 $self->_common_parse;
363              
364             # Initialize from param hash.
365             } elsif (ref $init eq 'HASH') {
366 7         14 foreach my $param (keys %{$init}) {
  7         27  
367 13         41 $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         6 $self->clone($init);
374              
375             # Initialize from a query string.
376             } else {
377 4         17 $self->_parse_params($init);
378             }
379              
380 38         58 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   37 my ($self, $data) = @_;
517 13 50       33 if (! defined $data) {
518 0         0 return ();
519             }
520              
521             # Parse params.
522 13         112 my $pairs_hr = parse_query_string($data);
523 13         23 foreach my $key (keys %{$pairs_hr}) {
  13         45  
524              
525             # Value processing.
526 17         24 my $value;
527 17 100       50 if ($self->{'utf8'}) {
528 15 100       43 if (ref $pairs_hr->{$key} eq 'ARRAY') {
529 7         14 my @decoded = ();
530 7         11 foreach my $val (@{$pairs_hr->{$key}}) {
  7         16  
531 21         346 push @decoded, decode_utf8($val);
532             }
533 7         110 $value = \@decoded;
534             } else {
535 8         33 $value = decode_utf8($pairs_hr->{$key});
536             }
537             } else {
538 2         5 $value = $pairs_hr->{$key};
539             }
540              
541             # Add parameter.
542 17         418 $self->_add_param($key, $value);
543             }
544 13         42 return;
545             }
546              
547             # Remove undefined values.
548             sub _remove_undef {
549 68     68   119 my (@values) = @_;
550              
551 68         120 my @new_values = grep { defined $_ } @values;
  28         83  
552              
553 68         131 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   35 my ($self, $string) = @_;
618 22 50       38 if ($self->{'utf8'}) {
619 22         40 $string = uri_escape_utf8($string);
620             } else {
621 0         0 $string = uri_escape($string);
622             }
623 22         416 $string =~ s/\ /\+/gsm;
624 22         61 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__