File Coverage

blib/lib/Mojo/Message/Request.pm
Criterion Covered Total %
statement 117 117 100.0
branch 81 86 94.1
condition 43 53 81.1
subroutine 22 22 100.0
pod 14 14 100.0
total 277 292 94.8


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