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   29339 use strict;
  6         12  
  6         327  
4 6     6   50 use warnings FATAL => 'all';
  6         10  
  6         354  
5              
6 6     6   5705 use Encode 'decode_utf8';
  6         76962  
  6         11671  
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 532 return ($_[0]->{+ORIG_ENV}||$_[0])->{+UNPACKED_QUERY} ||= do {
      33        
16             _unpack_params($_[0]->{QUERY_STRING})
17 101         188 };
18             }
19              
20             sub get_unpacked_body_from {
21 26   66 26 0 183 return ($_[0]->{+ORIG_ENV}||$_[0])->{+UNPACKED_BODY} ||= do {
      66        
22 13   100     60 my $ct = lc($_[0]->{CONTENT_TYPE}||'');
23 13 100       107 if (!$_[0]->{CONTENT_LENGTH}) {
    100          
    50          
24             {}
25 2         20 } elsif (index($ct, 'application/x-www-form-urlencoded') >= 0) {
26 6         66 $_[0]->{'psgi.input'}->read(my $buf, $_[0]->{CONTENT_LENGTH});
27 6         85 _unpack_params($buf);
28             } elsif (index($ct, 'multipart/form-data') >= 0) {
29 5         19 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       99 : ($_ => [ $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 91 return (($_[0]->{+ORIG_ENV}||$_[0])->{+UNPACKED_BODY_OBJECT} ||= do {
      66        
47 8 100 50     56 if (!$_[0]->{CONTENT_LENGTH}) {
    100          
48 2         29 [ undef ]
49             } elsif (index(lc($_[0]->{CONTENT_TYPE}||''),'multipart/form-data')==-1) {
50 2         15 [ undef ]
51             } else {
52 4         15 [ _make_http_body($_[0]) ]
53             }
54             })->[0];
55             }
56              
57             sub get_unpacked_uploads_from {
58 7   33 7 0 35 $_[0]->{+UNPACKED_UPLOADS} ||= do {
59 7         821 require Web::Dispatch::Upload; require HTTP::Headers;
  7         34  
60 7         22 my ($final, $reason) = (
61             {}, "field %s exists with value %s but body was not multipart/form-data"
62             );
63 7 100       24 if (my $body = get_unpacked_body_object_from($_[0])) {
64 3         16 my $u = $body->upload;
65 3         22 $reason = "field %s exists with value %s but was not an upload";
66 3         15 foreach my $k (keys %$u) {
67 1 50       9 foreach my $v (ref($u->{$k}) eq 'ARRAY' ? @{$u->{$k}} : $u->{$k}) {
  0         0  
68 1   50     12 push(@{$final->{$k}||=[]}, Web::Dispatch::Upload->new(
69 1         16 %{$v},
70             headers => HTTP::Headers->new($v->{headers})
71 1         3 ));
72             }
73             }
74             }
75 7         130 my $b = get_unpacked_body_from($_[0]);
76 7         29 foreach my $k (keys %$b) {
77 5 50       13 next if $final->{$k};
78 5         7 foreach my $v (@{$b->{$k}}) {
  5         16  
79 5 50       15 next unless $v;
80 5   50     5 push(@{$final->{$k}||=[]}, Web::Dispatch::NotAnUpload->new(
  5         86  
81             filename => $v,
82             reason => sprintf($reason, $k, $v)
83             ));
84             }
85             }
86 7         45 $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   152 my %unpack;
104 108         438 (my $params = $_[0]) =~ s/\+/ /g;
105 108         139 my ($name, $value);
106 108         908 foreach my $pair (split(/[&;](?:\s+)?/, $params)) {
107 853 100       2910 $value = 1 unless (($name, $value) = split(/=/, $pair, 2)) == 2;
108              
109 853         4437 s/$DECODE/$hex_chr{$1}/gs for ($name, $value);
110 853         1899 $_ = decode_utf8 $_ for ($name, $value);
111              
112 853   100     19643 push(@{$unpack{$name}||=[]}, $value);
  853         4264  
113             }
114 108         706 \%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   860 require HTTP::Body;
133 4         26933 my $body = HTTP::Body->new(@{$_[0]}{qw(CONTENT_TYPE CONTENT_LENGTH)});
  4         39  
134 4         505 $body->cleanup(1);
135 4         25 my $spin = 0;
136 4         12 my $input = $_[0]->{'psgi.input'};
137 4         8 my $cl = $_[0]->{CONTENT_LENGTH};
138 4         16 while ($cl) {
139 4 50       29 $input->read(my $chunk, $cl < 8192 ? $cl : 8192);
140 4         35 my $read = length $chunk;
141 4         6 $cl -= $read;
142 4         16 $body->add($chunk);
143              
144 4 50 33     2564 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         38 return $body;
150             }
151             }
152              
153             1;