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