File Coverage

blib/lib/CGI/Pure.pm
Criterion Covered Total %
statement 155 286 54.2
branch 50 120 41.6
condition 15 27 55.5
subroutine 26 32 81.2
pod 10 10 100.0
total 256 475 53.8


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