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   129977 use Mojo::Base 'Mojo::Message';
  62         142  
  62         563  
3              
4 62     62   564 use Digest::SHA qw(sha1_base64);
  62         171  
  62         4746  
5 62     62   8585 use Mojo::Cookie::Request;
  62         171  
  62         661  
6 62     62   393 use Mojo::Util qw(b64_encode b64_decode sha1_sum);
  62         129  
  62         3920  
7 62     62   9845 use Mojo::URL;
  62         166  
  62         858  
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 47 my $self = shift;
25              
26             # Dynamic requests cannot be cloned
27 19 100       100 return undef unless my $content = $self->content->clone;
28 15         66 my $clone
29             = $self->new(content => $content, method => $self->method, url => $self->url->clone, version => $self->version);
30 15 100       71 $clone->{proxy} = $self->{proxy}->clone if $self->{proxy};
31              
32 15         64 return $clone;
33             }
34              
35             sub cookies {
36 371     371 1 925 my $self = shift;
37              
38             # Parse cookies
39 371         1382 my $headers = $self->headers;
40 371 100       1855 return [map { @{Mojo::Cookie::Request->parse($_)} } $headers->cookie] unless @_;
  194         372  
  194         1430  
41              
42             # Add cookies
43 177 100 100     860 my @cookies = map { ref $_ eq 'HASH' ? Mojo::Cookie::Request->new($_) : $_ } $headers->cookie || (), @_;
  344         1321  
44 177         1911 $headers->cookie(join '; ', @cookies);
45              
46 177         1294 return $self;
47             }
48              
49 301     301 1 1063 sub every_param { shift->params->every_param(@_) }
50              
51             sub extract_start_line {
52 1126     1126 1 7104 my ($self, $bufref) = @_;
53              
54             # Ignore any leading empty lines
55 1126 100       19326 return undef unless $$bufref =~ s/^\s*(.*?)\x0d?\x0a//;
56              
57             # We have a (hopefully) full request-line
58 1101 100       9931 return !$self->error({message => 'Bad request start-line'}) unless $1 =~ /^(\S+)\s+(\S+)\s+HTTP\/(\d\.\d)$/;
59 1100         5444 my $url = $self->method($1)->version($3)->url;
60 1100         3698 my $target = $2;
61 1100 100       4128 return !!$url->host_port($target) if $1 eq 'CONNECT';
62 1098 100       5848 return !!$url->parse($target)->fragment(undef) if $target =~ /^[^:\/?#]+:/;
63 1091         4063 return !!$url->path_query($target);
64             }
65              
66             sub fix_headers {
67 2098     2098 1 3612 my $self = shift;
68 2098 100       10775 $self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
69              
70             # Empty
71 1050         3181 my $headers = $self->headers;
72 1050 100 100     3523 $headers->remove('Content-Length') if ($headers->content_length // '') eq '0' && $self->method eq 'GET';
      100        
73              
74             # Host
75 1050         3602 my $url = $self->url;
76 1050 100 100     4076 $headers->host($url->host_port // '') unless defined $headers->host;
77              
78             # Basic authentication
79 1050 100 66     4080 if ((my $info = $url->userinfo) && !$headers->authorization) {
80 6         76 $headers->authorization('Basic ' . b64_encode($info, ''));
81             }
82              
83             # Basic proxy authentication
84 1050 100 100     3159 return $self unless (my $proxy = $self->proxy) && $self->via_proxy;
85 14 100       37 return $self unless my $info = $proxy->userinfo;
86 6 50       23 $headers->proxy_authorization('Basic ' . b64_encode($info, '')) unless $headers->proxy_authorization;
87 6         22 return $self;
88             }
89              
90             sub get_start_line_chunk {
91 1080     1080 1 2863 my ($self, $offset) = @_;
92 1080         3211 $self->_start_line->emit(progress => 'start_line', $offset);
93 1080         8689 return substr $self->{start_buffer}, $offset, 131072;
94             }
95              
96 5049   100 5049 1 16756 sub is_handshake { lc($_[0]->headers->upgrade // '') eq 'websocket' }
97              
98             sub is_secure {
99 3     3 1 15 my $url = shift->url;
100 3   33     13 return ($url->protocol || $url->base->protocol) eq 'https';
101             }
102              
103 2   100 2 1 10 sub is_xhr { (shift->headers->header('X-Requested-With') // '') =~ /XMLHttpRequest/i }
104              
105 285     285 1 1032 sub param { shift->params->param(@_) }
106              
107 736   66 736 1 4513 sub params { $_[0]->{params} ||= $_[0]->body_params->clone->append($_[0]->query_params) }
108              
109             sub parse {
110 1565 100   1565 1 26211 my ($self, $env, $chunk) = (shift, ref $_[0] ? (shift, '') : (undef, shift));
111              
112             # Parse CGI environment
113 1565 100       4772 $self->env($env)->_parse_env($env) if $env;
114              
115             # Parse normal message
116 1565 100 100     15616 if (($self->{state} // '') ne 'cgi') { $self->SUPER::parse($chunk) }
  1508         6146  
117              
118             # Parse CGI content
119             else {
120 57 100       174 $self->{raw_size} += length $chunk unless defined $env;
121 57         206 $self->content($self->content->parse_body($chunk))->SUPER::parse('');
122             }
123              
124             # Check if we can fix things that require all headers
125 1565 100       6159 return $self unless $self->is_finished;
126              
127             # Base URL
128 1157         11213 my $base = $self->url->base;
129 1157 100       4100 $base->scheme('http') unless $base->scheme;
130 1157         3846 my $headers = $self->headers;
131 1157 100 100     3779 if (!$base->host && (my $host = $headers->host)) { $base->host_port($host) }
  1056         3982  
132              
133             # Basic authentication
134 1157 100       5620 if (my $basic = _basic($headers->authorization)) { $base->userinfo($basic) }
  7         29  
135              
136             # Basic proxy authentication
137 1157         4519 my $basic = _basic($headers->proxy_authorization);
138 1157 100       3322 $self->proxy(Mojo::URL->new->userinfo($basic)) if $basic;
139              
140             # "X-Forwarded-Proto"
141 1157 100 100     4280 $base->scheme('https') if $self->reverse_proxy && ($headers->header('X-Forwarded-Proto') // '') eq 'https';
      100        
142              
143 1157         4688 return $self;
144             }
145              
146 322     322 1 1179 sub query_params { shift->url->query }
147              
148 1014     1014 1 4665 sub start_line_size { length shift->_start_line->{start_buffer} }
149              
150 2314 100 66 2314   10306 sub _basic { $_[0] && $_[0] =~ /Basic (.+)$/ ? b64_decode $1 : undef }
151              
152             sub _parse_env {
153 33     33   104 my ($self, $env) = @_;
154              
155             # Bypass normal message parser
156 33         122 $self->{state} = 'cgi';
157              
158             # Extract headers
159 33         189 my $headers = $self->headers;
160 33         137 my $url = $self->url;
161 33         145 my $base = $url->base;
162 33         180 for my $name (keys %$env) {
163 339         607 my $value = $env->{$name};
164 339 100       1039 next unless $name =~ s/^HTTP_//i;
165 61         148 $name =~ y/_/-/;
166 61         320 $headers->header($name => $value);
167              
168             # Host/Port
169 61 100       456 $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       186 $headers->content_type($env->{CONTENT_TYPE}) if $env->{CONTENT_TYPE};
174              
175             # Content-Length is a special case on some servers
176 33 100       157 $headers->content_length($env->{CONTENT_LENGTH}) if $env->{CONTENT_LENGTH};
177              
178             # Query
179 33 100       166 $url->query->parse($env->{QUERY_STRING}) if $env->{QUERY_STRING};
180              
181             # Method
182 33 50       231 $self->method($env->{REQUEST_METHOD}) if $env->{REQUEST_METHOD};
183              
184             # Scheme/Version
185 33 50 33     354 $base->scheme($1) and $self->version($2) if ($env->{SERVER_PROTOCOL} // '') =~ m!^([^/]+)/([^/]+)$!;
      50        
186              
187             # HTTPS
188 33 100 100     226 $base->scheme('https') if uc($env->{HTTPS} // '') eq 'ON';
189              
190             # Path
191 33 100       121 my $path = $url->path->parse($env->{PATH_INFO} ? $env->{PATH_INFO} : '');
192              
193             # Base path
194 33 100       155 if (my $value = $env->{SCRIPT_NAME}) {
195              
196             # Make sure there is a trailing slash (important for merging)
197 31 100       112 $base->path->parse($value =~ m!/$! ? $value : "$value/");
198              
199             # Remove SCRIPT_NAME prefix if necessary
200 31         114 my $buffer = $path->to_string;
201 31         186 $value =~ s!^/|/$!!g;
202 31         477 $buffer =~ s!^/?\Q$value\E/?!!;
203 31         93 $buffer =~ s!^/!!;
204 31         140 $path->parse($buffer);
205             }
206             }
207              
208             sub _start_line {
209 2094     2094   3785 my $self = shift;
210              
211 2094 100       9687 return $self if defined $self->{start_buffer};
212              
213             # Path
214 1046         4575 my $url = $self->url;
215 1046         5101 my $path = $url->path_query;
216 1046 100       5032 $path = "/$path" unless $path =~ m!^/!;
217              
218             # CONNECT
219 1046         4332 my $method = uc $self->method;
220 1046 100 100     7723 if ($method eq 'CONNECT') {
    100 100        
221 3 0 33     13 my $port = $url->port // ($url->protocol eq 'https' ? '443' : '80');
222 3         11 $path = $url->ihost . ":$port";
223             }
224              
225             # Proxy
226             elsif ($self->proxy && $self->via_proxy && $url->protocol ne 'https') {
227 8 100       31 $path = $url->clone->userinfo(undef) unless $self->is_handshake;
228             }
229              
230 1046         2567 $self->{start_buffer} = "$method $path HTTP/@{[$self->version]}\x0d\x0a";
  1046         4282  
231              
232 1046         8751 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