File Coverage

blib/lib/CGI/Easy/Request.pm
Criterion Covered Total %
statement 141 141 100.0
branch 56 68 82.3
condition 12 14 85.7
subroutine 16 16 100.0
pod 2 2 100.0
total 227 241 94.1


line stmt bran cond sub pod time code
1             package CGI::Easy::Request;
2 3     3   110553 use 5.010001;
  3         23  
3 3     3   12 use warnings;
  3         5  
  3         105  
4 3     3   25 use strict;
  3         6  
  3         80  
5 3     3   895 use utf8;
  3         28  
  3         12  
6 3     3   66 use Carp;
  3         4  
  3         179  
7              
8             our $VERSION = 'v2.0.1';
9              
10 3     3   1085 use CGI::Easy::Util qw( uri_unescape_plus burst_urlencoded burst_multipart );
  3         6  
  3         10  
11 3     3   3859 use URI::Escape qw( uri_unescape );
  3         6  
  3         113  
12 3     3   1112 use MIME::Base64;
  3         1385  
  3         154  
13              
14 3     3   22 use constant MiB => 1024*1024; ## no critic(Capitalization)
  3         3  
  3         3995  
15              
16             my $MAX_POST = MiB;
17              
18             sub new {
19 40     40 1 57328 my ($class, $opt) = @_;
20             my $self = {
21             scheme => $ENV{HTTPS} ? 'https' : 'http',
22             host => undef,
23             port => $ENV{SERVER_PORT},
24             path => undef,
25             GET => {}, # for GET, HEAD, DELETE, …
26             POST => {}, # for POST, PUT
27             filename => {},
28             mimetype => {},
29             cookie => {},
30             REMOTE_ADDR => $ENV{REMOTE_ADDR},
31             REMOTE_PORT => $ENV{REMOTE_PORT},
32             AUTH_TYPE => $ENV{AUTH_TYPE},
33             REMOTE_USER => $ENV{REMOTE_USER},
34 40 100       537 REMOTE_PASS => undef,
35             ENV => { %ENV },
36             STDIN => q{},
37             error => q{},
38             };
39 40         85 bless $self, $class;
40              
41 40         52 my $pre = $opt->{frontend_prefix};
42 40 100       100 if (defined $pre) {
43 2         4 $pre = uc $pre;
44 2         6 $pre =~ s/-/_/xmsg;
45 2 100       8 if (defined $ENV{"HTTP_${pre}REMOTE_ADDR"}) {
46 1         3 $self->{REMOTE_ADDR} = $ENV{"HTTP_${pre}REMOTE_ADDR"};
47 1         3 $self->{REMOTE_PORT} = $ENV{"HTTP_${pre}REMOTE_PORT"};
48 1 50       4 $self->{scheme} = $ENV{"HTTP_${pre}HTTPS"} ? 'https' : 'http';
49             }
50             }
51              
52 40         88 my $host = $ENV{HTTP_HOST};
53 40         56 my $path = $ENV{REQUEST_URI};
54 40 100       92 if ($path =~ s{\A\w+://(?:[^/@]*@)?([^/]+)}{}xms) {
55 2         4 $host = $1;
56             }
57 40         52 $host =~ s{:\d+\z}{}xms;
58 40         74 $path =~ s{[?].*}{}xms;
59 40         98 $path = uri_unescape($path); # WARNING nginx allow %2F, apache didn't
60 40 100       297 if (!length $path) {
61 1         1 $path = q{/};
62             }
63 40         67 $self->{host} = $host;
64 40         51 $self->{path} = $path;
65              
66 40 100       65 if ($ENV{HTTP_AUTHORIZATION}) {
67 3 100       25 if ($ENV{HTTP_AUTHORIZATION} =~ /\ABasic\s+(\S+)\z/xms) {
68 2         17 my ($user, $pass) = split /:/xms, decode_base64($1), 2;
69 2 50       7 if (defined $pass) {
70 2         4 $self->{AUTH_TYPE} = 'Basic';
71 2         4 $self->{REMOTE_USER} = $user;
72 2         5 $self->{REMOTE_PASS} = $pass;
73             }
74             }
75 3 100       9 if (!defined $self->{REMOTE_PASS}) {
76 1         2 $self->{error} = 'failed to parse HTTP_AUTHORIZATION';
77             }
78             }
79              
80 40         86 $self->_read_cookie();
81              
82 40 100 100     114 if ($ENV{REQUEST_METHOD} eq 'POST' || $ENV{REQUEST_METHOD} eq 'PUT') {
83 14         43 $self->_read_post($opt->{max_post});
84 14 100       35 if ($opt->{post_with_get}) {
85 2         8 $self->_read_get();
86             }
87             } else {
88 26         59 $self->_read_get();
89             }
90              
91 40 100       70 if (!$opt->{keep_all_values}) {
92 39         68 $self->_force_scalar_params();
93             }
94              
95 40 100       81 if (!$opt->{raw}) {
96 37         68 $self->_decode_utf8();
97             }
98              
99 40         1224 return $self;
100             }
101              
102             sub param {
103 14     14 1 32 my ($self, $name) = @_;
104 14 100       31 if (defined $name) {
105 12         16 my @result;
106 12         19 for my $method (qw( POST GET )) {
107 24 100       62 if (exists $self->{$method}{$name}) {
108 16         20 my $value = $self->{$method}{$name};
109 16 100       34 push @result, ref $value ? @{$value} : $value;
  6         12  
110             }
111             }
112 12 100       62 return wantarray ? @result : $result[0];
113             }
114             else {
115 2 50       4 my %p = map { $_ => 1 } keys %{$self->{POST} || {}}, keys %{$self->{GET} || {}};
  8 50       13  
  2         6  
  2         7  
116 2         19 return keys %p;
117             }
118             }
119              
120             sub _force_scalar_params {
121 39     39   61 my ($self) = @_;
122 39         65 for my $p ($self->{GET}, $self->{POST}, $self->{filename}, $self->{mimetype}) {
123 156 50       158 for my $name (keys %{ $p || {} }) {
  156         315  
124 54 100       226 if ($name !~ /\[\]\z/xms) {
125 44         90 $p->{ $name } = $p->{ $name }[0];
126             }
127             }
128             }
129 39         51 return;
130             }
131              
132             sub _decode_utf8 {
133 37     37   54 my ($self) = @_;
134 37         83 utf8::decode($self->{path});
135 37         48 for my $key (qw( GET POST filename mimetype cookie )) {
136 185         179 my %tmp;
137 185 50       172 for my $name (keys %{ $self->{$key} || {} }) {
  185         341  
138 63 100       95 if (ref $self->{$key}{$name}) {
139 16         19 for my $i (0 .. $#{ $self->{$key}{$name} }) {
  16         38  
140 22 50 66     49 if (!($key eq 'POST' && defined $self->{mimetype}{$name}[$i])) {
141 22         42 utf8::decode($self->{$key}{$name}[$i]);
142             }
143             }
144             }
145             else {
146 47 100 100     127 if (!($key eq 'POST' && defined $self->{mimetype}{$name})) {
147 45         217 utf8::decode($self->{$key}{$name});
148             }
149             }
150 63         91 my $namestr = $name; utf8::decode($namestr);
  63         4240  
151 63         1159 $tmp{ $namestr } = $self->{$key}{$name};
152             }
153 185         321 $self->{$key} = \%tmp;
154             }
155 37         47 return;
156             }
157              
158             sub _read_cookie {
159 40     40   58 my ($self) = @_;
160 40   100     156 foreach (split /;\s?/xms, $self->{ENV}{HTTP_COOKIE} || q{}) {
161 9         33 s/\s*(.*?)\s*/$1/xms;
162 9         38 my ($key, $value) = split /=/xms, $_, 2;
163             # Some foreign cookies are not in name=value format, so ignore them.
164 9 50       18 next if !defined $value;
165 9         21 $key = uri_unescape_plus($key);
166 9         116 $value = uri_unescape_plus($value);
167             # A bug in Netscape can cause several cookies with same name to
168             # appear. The FIRST one in HTTP_COOKIE is the most recent version.
169 9 50       140 next if exists $self->{cookie}{$key};
170 9         19 $self->{cookie}{$key} = $value;
171             }
172 40         59 return;
173             }
174              
175             sub _read_get {
176 28     28   45 my $self = shift;
177 28         70 $self->{GET} = burst_urlencoded($self->{ENV}{QUERY_STRING});
178 28         40 return;
179             }
180              
181             sub _read_post {
182 14     14   35 my ($self, $max_post) = @_;
183              
184 14   66     46 $max_post ||= $MAX_POST;
185 14 100       35 if ($self->{ENV}{CONTENT_LENGTH} > $max_post) {
186 1         2 $self->{error} = 'POST body too large';
187 1         2 return;
188             }
189              
190 13         18 my $buffer = q{};
191 13 50       26 if ($self->{ENV}{CONTENT_LENGTH} > 0) {
192 13         32 binmode STDIN;
193 13         1098 my $n = read STDIN, $buffer, $self->{ENV}{CONTENT_LENGTH}, 0;
194 13         24 $self->{STDIN} = $buffer;
195 13 100       26 if ($n != $self->{ENV}{CONTENT_LENGTH}) {
196 1         1 $self->{error} = 'POST body incomplete';
197 1         18 return;
198             }
199             }
200              
201             # Boundaries are supposed to consist of only the following
202             # (1-70 of them, not ending in ' ') A-Za-z0-9 '()+,_-./:=?
203 12         52 my $multipart = qr{\Amultipart/form-data;\s+boundary=(.*)\z}xmsi;
204 12 50       24 if ($self->{ENV}{CONTENT_TYPE}) {
205 12 100       63 if ($self->{ENV}{CONTENT_TYPE} =~ m/$multipart/xms) {
    50          
206 2         6 my $boundary = $1;
207 2         8 @{$self}{'POST','filename','mimetype'}
  2         7  
208             = burst_multipart($buffer, $boundary);
209             }
210             elsif ($self->{ENV}{CONTENT_TYPE} eq 'application/x-www-form-urlencoded') {
211 10         24 $self->{POST} = burst_urlencoded($buffer);
212             }
213             }
214 12         30 return;
215             }
216              
217              
218             1; # Magic true value required at end of module
219             __END__