File Coverage

blib/lib/CGI/Easy/Util.pm
Criterion Covered Total %
statement 143 153 93.4
branch 44 56 78.5
condition 4 6 66.6
subroutine 27 27 100.0
pod 6 9 66.6
total 224 251 89.2


line stmt bran cond sub pod time code
1             package CGI::Easy::Util;
2              
3 4     4   21 use warnings;
  4         7  
  4         108  
4 4     4   19 use strict;
  4         7  
  4         100  
5 4     4   19 use Carp;
  4         6  
  4         386  
6              
7 4     4   27 use version; our $VERSION = qv('1.0.0'); # REMINDER: update Changes
  4         7  
  4         23  
8              
9             # REMINDER: update dependencies in Makefile.PL
10 4     4   6910 use Perl6::Export::Attrs;
  4         45258  
  4         45  
11 4     4   3841 use URI::Escape qw( uri_unescape uri_escape_utf8 );
  4         5687  
  4         434  
12              
13              
14             sub date_http :Export {
15 6     6 1 11 my ($tick) = @_;
16 6         16 return _date($tick, 'http');
17 4     4   23 }
  4         9  
  4         30  
18              
19             sub date_cookie :Export {
20 9     9 1 13 my ($tick) = @_;
21 9         16 return _date($tick, 'cookie');
22 4     4   841 }
  4         8  
  4         13  
23              
24             sub _date {
25 15     15   20 my ($tick, $format) = @_;
26 15 100       38 my $sp = $format eq 'cookie' ? q{-} : q{ };
27 15         65 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime $tick;
28 15         28 my $wkday = qw(Sun Mon Tue Wed Thu Fri Sat)[$wday];
29 15         23 my $month = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$mon];
30 15         118 return sprintf "%s, %02d$sp%s$sp%s %02d:%02d:%02d GMT",
31             $wkday, $mday, $month, $year+1900, $hour, $min, $sec; ## no critic(ProhibitMagicNumbers)
32             }
33              
34             sub make_cookie :Export {
35 12     12 1 15 my ($opt) = @_;
36 12 50       35 return q{} if !defined $opt->{name};
37              
38 12         20 my $name = $opt->{name};
39 12 50       27 my $value = defined $opt->{value} ? $opt->{value} : q{};
40 12         18 my $domain = $opt->{domain};
41 12 50       25 my $path = defined $opt->{path} ? $opt->{path} : q{/}; # IE require it
42 12 100 66     97 my $expires = defined $opt->{expires} && $opt->{expires} =~ /\A\d+\z/xms ?
43             date_cookie($opt->{expires}) : $opt->{expires};
44 12         19 my $set_cookie = 'Set-Cookie: ';
45 12         36 $set_cookie .= uri_escape_utf8($name) . q{=} . uri_escape_utf8($value);
46 12 100       439 $set_cookie .= "; domain=$domain" if defined $domain; ## no critic(ProhibitPostfixControls)
47 12         23 $set_cookie .= "; path=$path";
48 12 100       37 $set_cookie .= "; expires=$expires" if defined $expires;## no critic(ProhibitPostfixControls)
49 12 50       27 $set_cookie .= '; secure' if $opt->{secure}; ## no critic(ProhibitPostfixControls)
50 12         15 $set_cookie .= "\r\n";
51 12         51 return $set_cookie;
52 4     4   2035 }
  4         30  
  4         20  
53              
54             sub uri_unescape_plus :Export {
55 112     112 1 154 my ($s) = @_;
56 112         3118 $s =~ s/[+]/ /xmsg;
57 112         256 return uri_unescape($s);
58 4     4   872 }
  4         7  
  4         14  
59              
60             sub burst_urlencoded :Export {
61 38     38 1 80 my ($buffer) = @_;
62 38         48 my %param;
63 38 50       90 if (defined $buffer) {
64 38         17443 foreach my $pair (split /[&;]/xms, $buffer) {
65 49         5083 my ($name, $data) = split /=/xms, $pair, 2;
66 49 50       156 $name = !defined $name ? q{} : uri_unescape_plus($name);
67 49 100       3335 $data = !defined $data ? q{} : uri_unescape_plus($data);
68 49         510 push @{ $param{$name} }, $data;
  49         11632  
69             }
70             }
71 38         290 return \%param;
72 4     4   1165 }
  4         4  
  4         17  
73              
74             # This function derived from CGI::Minimal (1.29) by
75             # Benjamin Franz <snowhare@nihongo.org>
76             # Copyright (c) Benjamin Franz. All rights reserved.
77             sub burst_multipart :Export {
78             ## no critic
79 2     2 1 6 my ($buffer, $bdry) = @_;
80              
81             # Special case boundaries causing problems with 'split'
82 2 50       11 if ($bdry =~ m#[^A-Za-z0-9',-./:=]#s) {
83 0         0 my $nbdry = $bdry;
84 0         0 $nbdry =~ s/([^A-Za-z0-9',-.\/:=])/ord($1)/egs;
  0         0  
85 0         0 my $quoted_boundary = quotemeta ($nbdry);
86 0         0 while ($buffer =~ m/$quoted_boundary/s) {
87 0         0 $nbdry .= chr(int(rand(25))+65);
88 0         0 $quoted_boundary = quotemeta ($nbdry);
89             }
90 0         0 my $old_boundary = quotemeta($bdry);
91 0         0 $buffer =~ s/$old_boundary/$nbdry/gs;
92 0         0 $bdry = $nbdry;
93             }
94              
95 2         9 $bdry = "--$bdry(--)?\015\012";
96 2         70 my @pairs = split(/$bdry/, $buffer);
97              
98 2         6 my (%param, %filename, %mimetype);
99 2         4 foreach my $pair (@pairs) {
100 16 100       31 next if (! defined $pair);
101 10         15 chop $pair; # Trailing \015
102 10         11 chop $pair; # Trailing \012
103 10 50       23 last if ($pair eq "--");
104 10 100       20 next if (! $pair);
105              
106 6         20 my ($header, $data) = split(/\015\012\015\012/s,$pair,2);
107              
108             # parse the header
109 6         14 $header =~ s/\015\012/\012/osg;
110 6         15 my @headerlines = split(/\012/so,$header);
111 6         7 my ($name, $filename, $mimetype);
112              
113 6         9 foreach my $headfield (@headerlines) {
114 8         16 my ($fname,$fdata) = split(/: /,$headfield,2);
115 8 100       24 if ($fname =~ m/^Content-Type$/io) {
116 2         4 $mimetype=$fdata;
117             }
118 8 100       27 if ($fname =~ m/^Content-Disposition$/io) {
119 6         22 my @dispositionlist = split(/; /,$fdata);
120 6         11 foreach my $dispitem (@dispositionlist) {
121 14 100       26 next if ($dispitem eq 'form-data');
122 8         16 my ($dispfield,$dispdata) = split(/=/,$dispitem,2);
123 8         21 $dispdata =~ s/^\"//o;
124 8         19 $dispdata =~ s/\"$//o;
125 8 100       20 $name = $dispdata if ($dispfield eq 'name');
126 8 100       35 $filename = $dispdata if ($dispfield eq 'filename');
127             }
128             }
129             }
130 6 50       14 next if !defined $name;
131 6 50       13 next if !defined $data;
132              
133 6         12 push @{ $param{$name} }, $data;
  6         18  
134 6         9 push @{ $filename{$name} }, $filename;
  6         10  
135 6         8 push @{ $mimetype{$name} }, $mimetype;
  6         16  
136             }
137 2         11 return (\%param, \%filename, \%mimetype);
138 4     4   2970 }
  4         7  
  4         15  
139              
140              
141             ### Unrelated to CGI, and thus internal/undocumented
142              
143             sub _quote {
144 10     10   17 my ($s) = @_;
145 10 50       58 croak 'can\'t quote undefined value' if !defined $s;
146 10 100       40 if ($s =~ / \s | ' | \A\z /xms) {
147 2         4 $s =~ s/'/''/xmsg;
148 2         5 $s = "'$s'";
149             }
150 10         43 return $s;
151             }
152              
153             sub _unquote {
154 12     12   15 my ($s) = @_;
155 12 100       31 if ($s =~ s/\A'(.*)'\z/$1/xms) {
156 1         3 $s =~ s/''/'/xmsg;
157             }
158 12         56 return $s;
159             }
160              
161             sub quote_list :Export { ## no critic(RequireArgUnpacking)
162 4     4 0 8 return join q{ }, map {_quote($_)} @_;
  10         18  
163 4     4   1383 }
  4         7  
  4         15  
164              
165             sub unquote_list :Export {
166 22     22 0 37 my ($s) = @_;
167 22 100       59 return if !defined $s;
168 3         5 my @w;
169 3         16 while ($s =~ /\G ( [^'\s]+ | '[^']*(?:''[^']*)*' ) (?:\s+|\z)/xmsgc) {
170 12         23 my $w = $1;
171 12         23 push @w, _unquote($w);
172             }
173 3 50       14 return if $s !~ /\G\z/xmsg;
174 3         8 return \@w;
175 4     4   1062 }
  4         7  
  4         20  
176              
177             sub unquote_hash :Export { ## no critic(RequireArgUnpacking)
178 22     22 0 48 my $w = unquote_list(@_);
179 22 100 66     140 return $w && $#{$w} % 2 ? { @{$w} } : undef;
  3         23  
180 4     4   781 }
  4         15  
  4         17  
181              
182              
183             1; # Magic true value required at end of module
184             __END__
185              
186             =encoding utf8
187              
188             =head1 NAME
189              
190             CGI::Easy::Util - low-level helpers for HTTP/CGI
191              
192              
193             =head1 SYNOPSIS
194              
195             use CGI::Easy::Util qw( date_http date_cookie make_cookie );
196              
197             my $mtime = (stat '/some/file')[9];
198             printf "Last-Modified: %s\r\n", date_http($mtime);
199              
200             printf "Set-Cookie: a=5; expires=%s\r\n", date_cookie(time+86400);
201              
202             printf make_cookie({ name=>'a', value=>5, expires=>time+86400 });
203              
204              
205             use CGI::Easy::Util qw( uri_unescape_plus
206             burst_urlencoded burst_multipart );
207              
208             my $s = uri_unescape_plus('a+b%20c'); # $s is 'a b c'
209              
210             my %param = %{ burst_urlencoded($ENV{QUERY_STRING}) };
211             my $a = $param{a}[0];
212              
213             ($params, $filenames, $mimetypes) = burst_multipart($STDIN_data, $1)
214             if $ENV{CONTENT_TYPE} =~ m/;\s+boundary=(.*)/xms;
215             my $avatar_image = $params->{avatar}[0];
216             my $avatar_filename = $filenames->{avatar}[0];
217             my $avatar_mimetype = $mimetypes->{avatar}[0];
218              
219              
220             =head1 DESCRIPTION
221              
222             This module contain low-level function which you usually doesn't need -
223             use L<CGI::Easy::Request> and L<CGI::Easy::Headers> instead.
224              
225              
226             =head1 EXPORTS
227              
228             Nothing by default, but all documented functions can be explicitly imported.
229              
230              
231             =head1 INTERFACE
232              
233             =over
234              
235             =item date_http( $seconds )
236              
237             Convert given time into text format suitable for sending in HTTP headers.
238              
239             Return date string.
240              
241              
242             =item date_cookie( $seconds )
243              
244             Convert given time into text format suitable for sending in HTTP header
245             Set-Cookie's "expires" option.
246              
247             Return date string.
248              
249              
250             =item make_cookie( \%cookie )
251              
252             Convert HASHREF with cookie properties to "Set-Cookie: ..." HTTP header.
253              
254             Possible keys in %cookie:
255              
256             name REQUIRED STRING
257             value OPTIONAL STRING (default "")
258             domain OPTIONAL STRING (default "")
259             path OPTIONAL STRING (default "/")
260             expires OPTIONAL STRING or SECONDS
261             secure OPTIONAL BOOL
262              
263             Format for "expires" should be either correct date
264             'Thu, 01-Jan-1970 00:00:00 GMT' or time in seconds.
265              
266             Return HTTP header string.
267              
268              
269             =item uri_unescape_plus( $uri_escaped_value )
270              
271             Same as uri_unescape from L<URI::Escape> but additionally replace '+' with space.
272              
273             Return unescaped string.
274              
275             =item burst_urlencoded( $url_encoded_name_value_pairs )
276              
277             Unpack name/value pairs from url-encoded string (like $ENV{QUERY_STRING}
278             or STDIN content for non-multipart forms sent using POST method).
279              
280             Return HASHREF with params, each param's value will be ARRAYREF
281             (because there can be more than one value for any parameter in source string).
282              
283             =item burst_multipart( $buffer, $boundary )
284              
285             Unpack buffer with name/value pairs in multipart/form-data format.
286             This format usually used to upload files from forms, and each name/value
287             pair may additionally contain 'file name' and 'mime type' properties.
288              
289             Return three HASHREF (with param's values, with param's file names, and
290             with param's mime types), all values in all three HASHREF are ARRAYREF
291             (because there can be more than one value for any parameter in source string).
292             For non-file-upload parameters corresponding values in last two hashes
293             (with file names and mime types) will be undef().
294              
295             =back
296              
297              
298             =head1 BUGS AND LIMITATIONS
299              
300             No bugs have been reported.
301              
302              
303             =head1 SUPPORT
304              
305             Please report any bugs or feature requests through the web interface at
306             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CGI-Easy>.
307             I will be notified, and then you'll automatically be notified of progress
308             on your bug as I make changes.
309              
310             You can also look for information at:
311              
312             =over
313              
314             =item * RT: CPAN's request tracker
315              
316             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=CGI-Easy>
317              
318             =item * AnnoCPAN: Annotated CPAN documentation
319              
320             L<http://annocpan.org/dist/CGI-Easy>
321              
322             =item * CPAN Ratings
323              
324             L<http://cpanratings.perl.org/d/CGI-Easy>
325              
326             =item * Search CPAN
327              
328             L<http://search.cpan.org/dist/CGI-Easy/>
329              
330             =back
331              
332              
333             =head1 AUTHOR
334              
335             Alex Efros C<< <powerman-asdf@ya.ru> >>
336              
337              
338             =head1 LICENSE AND COPYRIGHT
339              
340             Copyright 2009-2010 Alex Efros <powerman-asdf@ya.ru>.
341              
342             This module also include some code derived from
343              
344             =over
345              
346             =item CGI::Minimal (1.29)
347              
348             by Benjamin Franz <snowhare@nihongo.org>.
349             Copyright (c) Benjamin Franz. All rights reserved.
350              
351             =back
352              
353             This program is distributed under the MIT (X11) License:
354             L<http://www.opensource.org/licenses/mit-license.php>
355              
356             Permission is hereby granted, free of charge, to any person
357             obtaining a copy of this software and associated documentation
358             files (the "Software"), to deal in the Software without
359             restriction, including without limitation the rights to use,
360             copy, modify, merge, publish, distribute, sublicense, and/or sell
361             copies of the Software, and to permit persons to whom the
362             Software is furnished to do so, subject to the following
363             conditions:
364              
365             The above copyright notice and this permission notice shall be
366             included in all copies or substantial portions of the Software.
367              
368             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
369             EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
370             OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
371             NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
372             HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
373             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
374             FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
375             OTHER DEALINGS IN THE SOFTWARE.
376