File Coverage

blib/lib/Web/Dispatch/ParamParser.pm
Criterion Covered Total %
statement 69 73 94.5
branch 20 26 76.9
condition 19 34 55.8
subroutine 9 9 100.0
pod 0 4 0.0
total 117 146 80.1


line stmt bran cond sub pod time code
1             package Web::Dispatch::ParamParser;
2              
3 6     6   19113 use strict;
  6         12  
  6         183  
4 6     6   26 use warnings FATAL => 'all';
  6         11  
  6         298  
5              
6 6     6   5047 use Encode 'decode_utf8';
  6         70903  
  6         8383  
7              
8             sub UNPACKED_QUERY () { __PACKAGE__.'.unpacked_query' }
9             sub UNPACKED_BODY () { __PACKAGE__.'.unpacked_body' }
10             sub UNPACKED_BODY_OBJECT () { __PACKAGE__.'.unpacked_body_object' }
11             sub UNPACKED_UPLOADS () { __PACKAGE__.'.unpacked_uploads' }
12             sub ORIG_ENV () { 'Web::Dispatch.original_env' }
13              
14             sub get_unpacked_query_from {
15 101   66 101 0 451 return ($_[0]->{+ORIG_ENV}||$_[0])->{+UNPACKED_QUERY} ||= do {
      33        
16             _unpack_params($_[0]->{QUERY_STRING})
17 101         232 };
18             }
19              
20             sub get_unpacked_body_from {
21 26   66 26 0 152 return ($_[0]->{+ORIG_ENV}||$_[0])->{+UNPACKED_BODY} ||= do {
      66        
22 13   100     52 my $ct = lc($_[0]->{CONTENT_TYPE}||'');
23 13 100       71 if (!$_[0]->{CONTENT_LENGTH}) {
    100          
    50          
24             {}
25 2         11 } elsif (index($ct, 'application/x-www-form-urlencoded') >= 0) {
26 6         48 $_[0]->{'psgi.input'}->read(my $buf, $_[0]->{CONTENT_LENGTH});
27 6         68 _unpack_params($buf);
28             } elsif (index($ct, 'multipart/form-data') >= 0) {
29 5         20 my $p = get_unpacked_body_object_from($_[0])->param;
30             # forcible arrayification (functional, $p does not belong to us,
31             # do NOT replace this with a side-effect ridden "simpler" version)
32             +{
33             map +(ref($p->{$_}) eq 'ARRAY'
34             ? ($_ => $p->{$_})
35 5 100       102 : ($_ => [ $p->{$_} ])
36             ), keys %$p
37             };
38             } else {
39             {}
40 0         0 }
41             };
42             }
43              
44             sub get_unpacked_body_object_from {
45             # we may have no object at all - so use a single element arrayref for ||=
46 12   33 12 0 86 return (($_[0]->{+ORIG_ENV}||$_[0])->{+UNPACKED_BODY_OBJECT} ||= do {
      66        
47 8 100 50     50 if (!$_[0]->{CONTENT_LENGTH}) {
    100          
48 2         16 [ undef ]
49             } elsif (index(lc($_[0]->{CONTENT_TYPE}||''),'multipart/form-data')==-1) {
50 2         15 [ undef ]
51             } else {
52 4         14 [ _make_http_body($_[0]) ]
53             }
54             })->[0];
55             }
56              
57             sub get_unpacked_uploads_from {
58 7   33 7 0 28 $_[0]->{+UNPACKED_UPLOADS} ||= do {
59 7         564 require Web::Dispatch::Upload; require HTTP::Headers;
  7         31  
60 7         16 my ($final, $reason) = (
61             {}, "field %s exists with value %s but body was not multipart/form-data"
62             );
63 7 100       22 if (my $body = get_unpacked_body_object_from($_[0])) {
64 3         14 my $u = $body->upload;
65 3         27 $reason = "field %s exists with value %s but was not an upload";
66 3         13 foreach my $k (keys %$u) {
67 1 50       85 foreach my $v (ref($u->{$k}) eq 'ARRAY' ? @{$u->{$k}} : $u->{$k}) {
  0         0  
68 1   50     8 push(@{$final->{$k}||=[]}, Web::Dispatch::Upload->new(
69 1         11 %{$v},
70             headers => HTTP::Headers->new($v->{headers})
71 1         3 ));
72             }
73             }
74             }
75 7         91 my $b = get_unpacked_body_from($_[0]);
76 7         25 foreach my $k (keys %$b) {
77 5 50       17 next if $final->{$k};
78 5         8 foreach my $v (@{$b->{$k}}) {
  5         17  
79 5 50       19 next unless $v;
80 5   50     7 push(@{$final->{$k}||=[]}, Web::Dispatch::NotAnUpload->new(
  5         86  
81             filename => $v,
82             reason => sprintf($reason, $k, $v)
83             ));
84             }
85             }
86 7         41 $final;
87             };
88             }
89              
90             {
91             # shamelessly stolen from HTTP::Body::UrlEncoded by Christian Hansen
92              
93             my $DECODE = qr/%([0-9a-fA-F]{2})/;
94              
95             my %hex_chr;
96              
97             foreach my $num ( 0 .. 255 ) {
98             my $h = sprintf "%02X", $num;
99             $hex_chr{ lc $h } = $hex_chr{ uc $h } = chr $num;
100             }
101              
102             sub _unpack_params {
103 108     108   128 my %unpack;
104 108         375 (my $params = $_[0]) =~ s/\+/ /g;
105 108         148 my ($name, $value);
106 108         846 foreach my $pair (split(/[&;](?:\s+)?/, $params)) {
107 853 100       3033 $value = 1 unless (($name, $value) = split(/=/, $pair, 2)) == 2;
108              
109 853         4791 s/$DECODE/$hex_chr{$1}/gs for ($name, $value);
110 853         2336 $_ = decode_utf8 $_ for ($name, $value);
111              
112 853   100     25485 push(@{$unpack{$name}||=[]}, $value);
  853         4475  
113             }
114 108         1131 \%unpack;
115             }
116             }
117              
118             {
119             # shamelessly stolen from Plack::Request by miyagawa
120              
121             sub _make_http_body {
122              
123             # Can't actually do this yet, since Plack::Request deletes the
124             # header structure out of the uploads in its copy of the body.
125             # I suspect I need to supply miyagawa with a failing test.
126              
127             #if (my $plack_body = $_[0]->{'plack.request.http.body'}) {
128             # # Plack already constructed one; probably wasteful to do it again
129             # return $plack_body;
130             #}
131              
132 4     4   1222 require HTTP::Body;
133 4         25476 my $body = HTTP::Body->new(@{$_[0]}{qw(CONTENT_TYPE CONTENT_LENGTH)});
  4         30  
134 4         418 $body->cleanup(1);
135 4         26 my $spin = 0;
136 4         12 my $input = $_[0]->{'psgi.input'};
137 4         8 my $cl = $_[0]->{CONTENT_LENGTH};
138 4         13 while ($cl) {
139 4 50       28 $input->read(my $chunk, $cl < 8192 ? $cl : 8192);
140 4         34 my $read = length $chunk;
141 4         7 $cl -= $read;
142 4         19 $body->add($chunk);
143              
144 4 50 33     2235 if ($read == 0 && $spin++ > 2000) {
145 0         0 require Carp;
146 0         0 Carp::croak("Bad Content-Length: maybe client disconnect? ($cl bytes remaining)");
147             }
148             }
149 4         32 return $body;
150             }
151             }
152              
153             1;