File Coverage

blib/lib/Mojo/Message/Request.pm
Criterion Covered Total %
statement 118 118 100.0
branch 83 88 94.3
condition 45 55 81.8
subroutine 22 22 100.0
pod 14 14 100.0
total 282 297 94.9


line stmt bran cond sub pod time code
1             package Mojo::Message::Request;
2 62     62   95893 use Mojo::Base 'Mojo::Message';
  62         132  
  62         437  
3              
4 62     62   550 use Digest::SHA qw(sha1_base64);
  62         124  
  62         4246  
5 62     62   8525 use Mojo::Cookie::Request;
  62         170  
  62         1002  
6 62     62   331 use Mojo::Util qw(b64_encode b64_decode sha1_sum);
  62         132  
  62         3663  
7 62     62   9934 use Mojo::URL;
  62         175  
  62         836  
8              
9             has env => sub { {} };
10             has method => 'GET';
11             has [qw(proxy reverse_proxy)];
12             has request_id => sub {
13             state $seed = $$ . time . rand;
14             state $counter = int rand 0xffffff;
15             my $b64 = substr(sha1_base64($seed . ($counter = ($counter + 1) % 0xffffff)), 0, 12);
16             $b64 =~ tr!+/!-_!;
17             return $b64;
18             };
19             has trusted_proxies => sub { [] };
20             has url => sub { Mojo::URL->new };
21             has via_proxy => 1;
22              
23             sub clone {
24 19     19 1 59 my $self = shift;
25              
26             # Dynamic requests cannot be cloned
27 19 100       85 return undef unless my $content = $self->content->clone;
28 15         82 my $clone
29             = $self->new(content => $content, method => $self->method, url => $self->url->clone, version => $self->version);
30 15 100       80 $clone->{proxy} = $self->{proxy}->clone if $self->{proxy};
31              
32 15         70 return $clone;
33             }
34              
35             sub cookies {
36 371     371 1 851 my $self = shift;
37              
38             # Parse cookies
39 371         1429 my $headers = $self->headers;
40 371 100       1762 return [map { @{Mojo::Cookie::Request->parse($_)} } $headers->cookie] unless @_;
  194         376  
  194         1440  
41              
42             # Add cookies
43 177 100 100     2006 my @cookies = map { ref $_ eq 'HASH' ? Mojo::Cookie::Request->new($_) : $_ } $headers->cookie || (), @_;
  344         1305  
44 177         1822 $headers->cookie(join '; ', @cookies);
45              
46 177         1283 return $self;
47             }
48              
49 301     301 1 1068 sub every_param { shift->params->every_param(@_) }
50              
51             sub extract_start_line {
52 1126     1126 1 3049 my ($self, $bufref) = @_;
53              
54             # Ignore any leading empty lines
55 1126 100       18797 return undef unless $$bufref =~ s/^\s*(.*?)\x0d?\x0a//;
56              
57             # We have a (hopefully) full request-line
58 1101 100       9678 return !$self->error({message => 'Bad request start-line'}) unless $1 =~ /^(\S+)\s+(\S+)\s+HTTP\/(\d\.\d)$/;
59 1100         4778 my $url = $self->method($1)->version($3)->url;
60 1100         3519 my $target = $2;
61 1100 100       3880 return !!$url->host_port($target) if $1 eq 'CONNECT';
62 1098 100       4170 return !!$url->parse($target)->fragment(undef) if $target =~ /^[^:\/?#]+:/;
63 1091         4167 return !!$url->path_query($target);
64             }
65              
66             sub fix_headers {
67 2098     2098 1 3673 my $self = shift;
68 2098 100       10565 $self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
69              
70             # Empty
71 1050         3526 my $headers = $self->headers;
72 1050 100 100     2982 $headers->remove('Content-Length') if ($headers->content_length // '') eq '0' && $self->method eq 'GET';
      100        
73              
74             # Host
75 1050         3451 my $url = $self->url;
76 1050 100 100     3929 $headers->host($url->host_port // '') unless defined $headers->host;
77              
78             # Basic authentication
79 1050 100 66     4071 if ((my $info = $url->userinfo) && !$headers->authorization) {
80 6         77 $headers->authorization('Basic ' . b64_encode($info, ''));
81             }
82              
83             # Basic proxy authentication
84 1050 100 100     3180 return $self unless (my $proxy = $self->proxy) && $self->via_proxy;
85 14 100       46 return $self unless my $info = $proxy->userinfo;
86 6 50       23 $headers->proxy_authorization('Basic ' . b64_encode($info, '')) unless $headers->proxy_authorization;
87 6         31 return $self;
88             }
89              
90             sub get_start_line_chunk {
91 1080     1080 1 2663 my ($self, $offset) = @_;
92 1080         3246 $self->_start_line->emit(progress => 'start_line', $offset);
93 1080         4788 return substr $self->{start_buffer}, $offset, 131072;
94             }
95              
96 5049   100 5049 1 16455 sub is_handshake { lc($_[0]->headers->upgrade // '') eq 'websocket' }
97              
98             sub is_secure {
99 3     3 1 19 my $url = shift->url;
100 3   33     19 return ($url->protocol || $url->base->protocol) eq 'https';
101             }
102              
103 2   100 2 1 9 sub is_xhr { (shift->headers->header('X-Requested-With') // '') =~ /XMLHttpRequest/i }
104              
105 285     285 1 972 sub param { shift->params->param(@_) }
106              
107 736   66 736 1 4872 sub params { $_[0]->{params} ||= $_[0]->body_params->clone->append($_[0]->query_params) }
108              
109             sub parse {
110 1574 100   1574 1 19852 my ($self, $env, $chunk) = (shift, ref $_[0] ? (shift, '') : (undef, shift));
111              
112             # Parse CGI environment
113 1574 100       4303 $self->env($env)->_parse_env($env) if $env;
114              
115             # Parse normal message
116 1574 100 100     8278 if (($self->{state} // '') ne 'cgi') { $self->SUPER::parse($chunk) }
  1517         5975  
117              
118             # Parse CGI content
119             else {
120 57 100       172 $self->{raw_size} += length $chunk unless defined $env;
121 57         217 $self->content($self->content->parse_body($chunk))->SUPER::parse('');
122             }
123              
124             # Check if we can fix things that require all headers
125 1574 100       5395 return $self unless $self->is_finished;
126              
127             # Base URL
128 1157         3900 my $base = $self->url->base;
129 1157 100       3801 $base->scheme('http') unless $base->scheme;
130 1157         3403 my $headers = $self->headers;
131 1157 100 100     3813 if (!$base->host && (my $host = $headers->host)) { $base->host_port($host) }
  1056         4143  
132              
133             # Basic authentication
134 1157 100       4809 if (my $basic = _basic($headers->authorization)) { $base->userinfo($basic) }
  7         29  
135              
136             # Basic proxy authentication
137 1157         4195 my $basic = _basic($headers->proxy_authorization);
138 1157 100       3097 $self->proxy(Mojo::URL->new->userinfo($basic)) if $basic;
139              
140             # "X-Forwarded-Proto"
141 1157 100 100     4271 $base->scheme('https') if $self->reverse_proxy && ($headers->header('X-Forwarded-Proto') // '') eq 'https';
      100        
142              
143 1157         4542 return $self;
144             }
145              
146 322     322 1 1237 sub query_params { shift->url->query }
147              
148 1014     1014 1 4466 sub start_line_size { length shift->_start_line->{start_buffer} }
149              
150 2314 100 66 2314   27569 sub _basic { $_[0] && $_[0] =~ /Basic (.+)$/ ? b64_decode $1 : undef }
151              
152             sub _parse_env {
153 33     33   85 my ($self, $env) = @_;
154              
155             # Bypass normal message parser
156 33         95 $self->{state} = 'cgi';
157              
158             # Extract headers
159 33         150 my $headers = $self->headers;
160 33         115 my $url = $self->url;
161 33         111 my $base = $url->base;
162 33         184 for my $name (keys %$env) {
163 339         623 my $value = $env->{$name};
164 339 100       1041 next unless $name =~ s/^HTTP_//i;
165 61         177 $name =~ y/_/-/;
166 61         299 $headers->header($name => $value);
167              
168             # Host/Port
169 61 100       405 $value =~ s/:(\d+)$// ? $base->host($value)->port($1) : $base->host($value) if $name eq 'HOST';
    100          
170             }
171              
172             # Content-Type is a special case on some servers
173 33 100       223 $headers->content_type($env->{CONTENT_TYPE}) if $env->{CONTENT_TYPE};
174              
175             # Content-Length is a special case on some servers
176 33 100       170 $headers->content_length($env->{CONTENT_LENGTH}) if $env->{CONTENT_LENGTH};
177              
178             # Query
179 33 100       141 $url->query->parse($env->{QUERY_STRING}) if $env->{QUERY_STRING};
180              
181             # Method
182 33 50       196 $self->method($env->{REQUEST_METHOD}) if $env->{REQUEST_METHOD};
183              
184             # Scheme/Version
185 33 50 33     357 $base->scheme($1) and $self->version($2) if ($env->{SERVER_PROTOCOL} // '') =~ m!^([^/]+)/([^/]+)$!;
      50        
186              
187             # HTTPS
188 33 100 100     209 $base->scheme('https') if uc($env->{HTTPS} // '') eq 'ON';
189              
190             # Path
191 33 100       120 my $path = $url->path->parse($env->{PATH_INFO} ? $env->{PATH_INFO} : '');
192              
193             # Base path
194 33 100       244 if (my $value = $env->{SCRIPT_NAME}) {
195              
196             # Make sure there is a trailing slash (important for merging)
197 31 100       166 $base->path->parse($value =~ m!/$! ? $value : "$value/");
198              
199             # Remove SCRIPT_NAME prefix if necessary
200 31         106 my $buffer = $path->to_string;
201 31         193 $value =~ s!^/|/$!!g;
202 31         478 $buffer =~ s!^/?\Q$value\E/?!!;
203 31         116 $buffer =~ s!^/!!;
204 31         115 $path->parse($buffer);
205             }
206             }
207              
208             sub _start_line {
209 2094     2094   3825 my $self = shift;
210              
211 2094 100       9492 return $self if defined $self->{start_buffer};
212              
213             # Path
214 1046         3356 my $url = $self->url;
215 1046         4905 my $path = $url->path_query;
216 1046 100       5164 $path = "/$path" unless $path =~ m!^/!;
217              
218             # CONNECT
219 1046         4135 my $method = uc $self->method;
220 1046 100 100     5330 if ($method eq 'CONNECT') {
    100 100        
221 3 0 33     17 my $port = $url->port // ($url->protocol eq 'https' ? '443' : '80');
222 3         16 $path = $url->ihost . ":$port";
223             }
224              
225             # Proxy
226             elsif ($self->proxy && $self->via_proxy && $url->protocol ne 'https') {
227 8 100       32 $path = $url->clone->userinfo(undef) unless $self->is_handshake;
228             }
229              
230 1046         2625 $self->{start_buffer} = "$method $path HTTP/@{[$self->version]}\x0d\x0a";
  1046         4275  
231              
232 1046         8186 return $self;
233             }
234              
235             1;
236              
237             =encoding utf8
238              
239             =head1 NAME
240              
241             Mojo::Message::Request - HTTP request
242              
243             =head1 SYNOPSIS
244              
245             use Mojo::Message::Request;
246              
247             # Parse
248             my $req = Mojo::Message::Request->new;
249             $req->parse("GET /foo HTTP/1.0\x0d\x0a");
250             $req->parse("Content-Length: 12\x0d\x0a");
251             $req->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a");
252             $req->parse('Hello World!');
253             say $req->method;
254             say $req->headers->content_type;
255             say $req->body;
256              
257             # Build
258             my $req = Mojo::Message::Request->new;
259             $req->url->parse('http://127.0.0.1/foo/bar');
260             $req->method('GET');
261             say $req->to_string;
262              
263             =head1 DESCRIPTION
264              
265             L is a container for HTTP requests, based on L,
266             L, L and L
267             2817|https://tools.ietf.org/html/rfc2817>.
268              
269             =head1 EVENTS
270              
271             L inherits all events from L.
272              
273             =head1 ATTRIBUTES
274              
275             L inherits all attributes from L and implements the following new ones.
276              
277             =head2 env
278              
279             my $env = $req->env;
280             $req = $req->env({PATH_INFO => '/'});
281              
282             Direct access to the C or C environment hash if available.
283              
284             # Check CGI version
285             my $version = $req->env->{GATEWAY_INTERFACE};
286              
287             # Check PSGI version
288             my $version = $req->env->{'psgi.version'};
289              
290             =head2 method
291              
292             my $method = $req->method;
293             $req = $req->method('POST');
294              
295             HTTP request method, defaults to C.
296              
297             =head2 proxy
298              
299             my $url = $req->proxy;
300             $req = $req->proxy(Mojo::URL->new('http://127.0.0.1:3000'));
301              
302             Proxy URL for request.
303              
304             =head2 reverse_proxy
305              
306             my $bool = $req->reverse_proxy;
307             $req = $req->reverse_proxy($bool);
308              
309             Request has been performed through a reverse proxy.
310              
311             =head2 trusted_proxies
312              
313             my $proxies = $req->trusted_proxies;
314             $req = $req->trusted_proxies(['10.0.0.0/8', '127.0.0.1', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7']);
315              
316             Trusted reverse proxies, addresses or networks in CIDR form.
317              
318             =head2 request_id
319              
320             my $id = $req->request_id;
321             $req = $req->request_id('aee7d5d8');
322              
323             Request ID, defaults to a reasonably unique value.
324              
325             =head2 url
326              
327             my $url = $req->url;
328             $req = $req->url(Mojo::URL->new);
329              
330             HTTP request URL, defaults to a L object.
331              
332             # Get request information
333             my $info = $req->url->to_abs->userinfo;
334             my $host = $req->url->to_abs->host;
335             my $path = $req->url->to_abs->path;
336              
337             =head2 via_proxy
338              
339             my $bool = $req->via_proxy;
340             $req = $req->via_proxy($bool);
341              
342             Request can be performed through a proxy server.
343              
344             =head1 METHODS
345              
346             L inherits all methods from L and implements the following new ones.
347              
348             =head2 clone
349              
350             my $clone = $req->clone;
351              
352             Return a new L object cloned from this request if possible, otherwise return C.
353              
354             =head2 cookies
355              
356             my $cookies = $req->cookies;
357             $req = $req->cookies(Mojo::Cookie::Request->new);
358             $req = $req->cookies({name => 'foo', value => 'bar'});
359              
360             Access request cookies, usually L objects.
361              
362             # Names of all cookies
363             say $_->name for @{$req->cookies};
364              
365             =head2 every_param
366              
367             my $values = $req->every_param('foo');
368              
369             Similar to L, but returns all values sharing the same name as an array reference.
370              
371             # Get first value
372             say $req->every_param('foo')->[0];
373              
374             =head2 extract_start_line
375              
376             my $bool = $req->extract_start_line(\$str);
377              
378             Extract request-line from string.
379              
380             =head2 fix_headers
381              
382             $req = $req->fix_headers;
383              
384             Make sure request has all required headers.
385              
386             =head2 get_start_line_chunk
387              
388             my $bytes = $req->get_start_line_chunk($offset);
389              
390             Get a chunk of request-line data starting from a specific position. Note that this method finalizes the request.
391              
392             =head2 is_handshake
393              
394             my $bool = $req->is_handshake;
395              
396             Check C header for C value.
397              
398             =head2 is_secure
399              
400             my $bool = $req->is_secure;
401              
402             Check if connection is secure.
403              
404             =head2 is_xhr
405              
406             my $bool = $req->is_xhr;
407              
408             Check C header for C value.
409              
410             =head2 param
411              
412             my $value = $req->param('foo');
413              
414             Access C and C parameters extracted from the query string and C or
415             C message body. If there are multiple values sharing the same name, and you want to access more
416             than just the last one, you can use L. Note that this method caches all data, so it should not be
417             called before the entire request body has been received. Parts of the request body need to be loaded into memory to
418             parse C parameters, so you have to make sure it is not excessively large. There's a 16MiB limit for requests by
419             default.
420              
421             =head2 params
422              
423             my $params = $req->params;
424              
425             All C and C parameters extracted from the query string and C or
426             C message body, usually a L object. Note that this method caches all data, so it
427             should not be called before the entire request body has been received. Parts of the request body need to be loaded into
428             memory to parse C parameters, so you have to make sure it is not excessively large. There's a 16MiB limit for
429             requests by default.
430              
431             # Get parameter names and values
432             my $hash = $req->params->to_hash;
433              
434             =head2 parse
435              
436             $req = $req->parse('GET /foo/bar HTTP/1.1');
437             $req = $req->parse({PATH_INFO => '/'});
438              
439             Parse HTTP request chunks or environment hash.
440              
441             =head2 query_params
442              
443             my $params = $req->query_params;
444              
445             All C parameters, usually a L object.
446              
447             # Turn GET parameters to hash and extract value
448             say $req->query_params->to_hash->{foo};
449              
450             =head2 start_line_size
451              
452             my $size = $req->start_line_size;
453              
454             Size of the request-line in bytes. Note that this method finalizes the request.
455              
456             =head1 SEE ALSO
457              
458             L, L, L.
459              
460             =cut