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   73992 use Mojo::Base 'Mojo::Message';
  62         91  
  62         349  
3              
4 62     62   387 use Digest::SHA qw(sha1_base64);
  62         349  
  62         3148  
5 62     62   6396 use Mojo::Cookie::Request;
  62         142  
  62         415  
6 62     62   248 use Mojo::Util qw(b64_encode b64_decode sha1_sum);
  62         94  
  62         2615  
7 62     62   7196 use Mojo::URL;
  62         115  
  62         410  
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 22     22 1 55 my $self = shift;
25              
26             # Dynamic requests cannot be cloned
27 22 100       65 return undef unless my $content = $self->content->clone;
28 18         52 my $clone
29             = $self->new(content => $content, method => $self->method, url => $self->url->clone, version => $self->version);
30 18 100       54 $clone->{proxy} = $self->{proxy}->clone if $self->{proxy};
31              
32 18         50 return $clone;
33             }
34              
35             sub cookies {
36 371     371 1 566 my $self = shift;
37              
38             # Parse cookies
39 371         951 my $headers = $self->headers;
40 371 100       1344 return [map { @{Mojo::Cookie::Request->parse($_)} } $headers->cookie] unless @_;
  194         256  
  194         954  
41              
42             # Add cookies
43 177 100 100     613 my @cookies = map { ref $_ eq 'HASH' ? Mojo::Cookie::Request->new($_) : $_ } $headers->cookie || (), @_;
  344         924  
44 177         1220 $headers->cookie(join '; ', @cookies);
45              
46 177         835 return $self;
47             }
48              
49 301     301 1 725 sub every_param { shift->params->every_param(@_) }
50              
51             sub extract_start_line {
52 1130     1130 1 2091 my ($self, $bufref) = @_;
53              
54             # Ignore any leading empty lines
55 1130 100       13014 return undef unless $$bufref =~ s/^\s*(.*?)\x0d?\x0a//;
56              
57             # We have a (hopefully) full request-line
58 1105 100       7039 return !$self->error({message => 'Bad request start-line'}) unless $1 =~ /^(\S+)\s+(\S+)\s+HTTP\/(\d\.\d)$/;
59 1104         3080 my $url = $self->method($1)->version($3)->url;
60 1104         2357 my $target = $2;
61 1104 100       2921 return !!$url->host_port($target) if $1 eq 'CONNECT';
62 1102 100       3248 return !!$url->parse($target)->fragment(undef) if $target =~ /^[^:\/?#]+:/;
63 1095         2621 return !!$url->path_query($target);
64             }
65              
66             sub fix_headers {
67 2106     2106 1 2688 my $self = shift;
68 2106 100       6854 $self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
69              
70             # Empty
71 1054         2354 my $headers = $self->headers;
72 1054 100 100     1991 $headers->remove('Content-Length') if ($headers->content_length // '') eq '0' && $self->method eq 'GET';
      100        
73              
74             # Host
75 1054         2372 my $url = $self->url;
76 1054 100 100     2595 $headers->host($url->host_port // '') unless defined $headers->host;
77              
78             # Basic authentication
79 1054 100 66     2662 if ((my $info = $url->userinfo) && !$headers->authorization) {
80 6         39 $headers->authorization('Basic ' . b64_encode($info, ''));
81             }
82              
83             # Basic proxy authentication
84 1054 100 100     2227 return $self unless (my $proxy = $self->proxy) && $self->via_proxy;
85 14 100       30 return $self unless my $info = $proxy->userinfo;
86 6 50       14 $headers->proxy_authorization('Basic ' . b64_encode($info, '')) unless $headers->proxy_authorization;
87 6         15 return $self;
88             }
89              
90             sub get_start_line_chunk {
91 1084     1084 1 1929 my ($self, $offset) = @_;
92 1084         2115 $self->_start_line->emit(progress => 'start_line', $offset);
93 1084         3305 return substr $self->{start_buffer}, $offset, 131072;
94             }
95              
96 5068   100 5068 1 10917 sub is_handshake { lc($_[0]->headers->upgrade // '') eq 'websocket' }
97              
98             sub is_secure {
99 3     3 1 16 my $url = shift->url;
100 3   33     13 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 593 sub param { shift->params->param(@_) }
106              
107 736   66 736 1 3030 sub params { $_[0]->{params} ||= $_[0]->body_params->clone->append($_[0]->query_params) }
108              
109             sub parse {
110 1609 100   1609 1 13355 my ($self, $env, $chunk) = (shift, ref $_[0] ? (shift, '') : (undef, shift));
111              
112             # Parse CGI environment
113 1609 100       3115 $self->env($env)->_parse_env($env) if $env;
114              
115             # Parse normal message
116 1609 100 100     5546 if (($self->{state} // '') ne 'cgi') { $self->SUPER::parse($chunk) }
  1552         4139  
117              
118             # Parse CGI content
119             else {
120 57 100       109 $self->{raw_size} += length $chunk unless defined $env;
121 57         137 $self->content($self->content->parse_body($chunk))->SUPER::parse('');
122             }
123              
124             # Check if we can fix things that require all headers
125 1609 100       3662 return $self unless $self->is_finished;
126              
127             # Base URL
128 1161         2840 my $base = $self->url->base;
129 1161 100       2652 $base->scheme('http') unless $base->scheme;
130 1161         2399 my $headers = $self->headers;
131 1161 100 100     2583 if (!$base->host && (my $host = $headers->host)) { $base->host_port($host) }
  1060         2643  
132              
133             # Basic authentication
134 1161 100       3348 if (my $basic = _basic($headers->authorization)) { $base->userinfo($basic) }
  7         22  
135              
136             # Basic proxy authentication
137 1161         2829 my $basic = _basic($headers->proxy_authorization);
138 1161 100       2154 $self->proxy(Mojo::URL->new->userinfo($basic)) if $basic;
139              
140             # "X-Forwarded-Proto"
141 1161 100 100     2860 $base->scheme('https') if $self->reverse_proxy && ($headers->header('X-Forwarded-Proto') // '') eq 'https';
      100        
142              
143 1161         3211 return $self;
144             }
145              
146 322     322 1 800 sub query_params { shift->url->query }
147              
148 1018     1018 1 2967 sub start_line_size { length shift->_start_line->{start_buffer} }
149              
150 2322 100 66 2322   6327 sub _basic { $_[0] && $_[0] =~ /Basic (.+)$/ ? b64_decode $1 : undef }
151              
152             sub _parse_env {
153 33     33   58 my ($self, $env) = @_;
154              
155             # Bypass normal message parser
156 33         83 $self->{state} = 'cgi';
157              
158             # Extract headers
159 33         112 my $headers = $self->headers;
160 33         79 my $url = $self->url;
161 33         98 my $base = $url->base;
162 33         184 for my $name (keys %$env) {
163 339         408 my $value = $env->{$name};
164 339 100       700 next unless $name =~ s/^HTTP_//i;
165 61         134 $name =~ y/_/-/;
166 61         184 $headers->header($name => $value);
167              
168             # Host/Port
169 61 100       256 $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       124 $headers->content_type($env->{CONTENT_TYPE}) if $env->{CONTENT_TYPE};
174              
175             # Content-Length is a special case on some servers
176 33 100       94 $headers->content_length($env->{CONTENT_LENGTH}) if $env->{CONTENT_LENGTH};
177              
178             # Query
179 33 100       91 $url->query->parse($env->{QUERY_STRING}) if $env->{QUERY_STRING};
180              
181             # Method
182 33 50       125 $self->method($env->{REQUEST_METHOD}) if $env->{REQUEST_METHOD};
183              
184             # Scheme/Version
185 33 50 33     232 $base->scheme($1) and $self->version($2) if ($env->{SERVER_PROTOCOL} // '') =~ m!^([^/]+)/([^/]+)$!;
      50        
186              
187             # HTTPS
188 33 100 100     135 $base->scheme('https') if uc($env->{HTTPS} // '') eq 'ON';
189              
190             # Path
191 33 100       75 my $path = $url->path->parse($env->{PATH_INFO} ? $env->{PATH_INFO} : '');
192              
193             # Base path
194 33 100       75 if (my $value = $env->{SCRIPT_NAME}) {
195              
196             # Make sure there is a trailing slash (important for merging)
197 31 100       68 $base->path->parse($value =~ m!/$! ? $value : "$value/");
198              
199             # Remove SCRIPT_NAME prefix if necessary
200 31         87 my $buffer = $path->to_string;
201 31         118 $value =~ s!^/|/$!!g;
202 31         353 $buffer =~ s!^/?\Q$value\E/?!!;
203 31         59 $buffer =~ s!^/!!;
204 31         63 $path->parse($buffer);
205             }
206             }
207              
208             sub _start_line {
209 2102     2102   2629 my $self = shift;
210              
211 2102 100       6439 return $self if defined $self->{start_buffer};
212              
213             # Path
214 1050         2312 my $url = $self->url;
215 1050         3371 my $path = $url->path_query;
216 1050 100       3537 $path = "/$path" unless $path =~ m!^/!;
217              
218             # CONNECT
219 1050         2717 my $method = uc $self->method;
220 1050 100 100     3601 if ($method eq 'CONNECT') {
    100 100        
221 3 0 33     10 my $port = $url->port // ($url->protocol eq 'https' ? '443' : '80');
222 3         10 $path = $url->ihost . ":$port";
223             }
224              
225             # Proxy
226             elsif ($self->proxy && $self->via_proxy && $url->protocol ne 'https') {
227 8 100       18 $path = $url->clone->userinfo(undef) unless $self->is_handshake;
228             }
229              
230 1050         1910 $self->{start_buffer} = "$method $path HTTP/@{[$self->version]}\x0d\x0a";
  1050         2692  
231              
232 1050         5567 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