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 58     58   54519 use Mojo::Base 'Mojo::Message';
  58         131  
  58         436  
3              
4 58     58   488 use Digest::SHA qw(sha1_base64);
  58         138  
  58         3423  
5 58     58   7370 use Mojo::Cookie::Request;
  58         285  
  58         674  
6 58     58   381 use Mojo::Util qw(b64_encode b64_decode sha1_sum);
  58         616  
  58         3149  
7 58     58   8377 use Mojo::URL;
  58         174  
  58         622  
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 43 my $self = shift;
25              
26             # Dynamic requests cannot be cloned
27 19 100       53 return undef unless my $content = $self->content->clone;
28 15         55 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         64 return $clone;
33             }
34              
35             sub cookies {
36 400     400 1 806 my $self = shift;
37              
38             # Parse cookies
39 400         1262 my $headers = $self->headers;
40 400 100       1679 return [map { @{Mojo::Cookie::Request->parse($_)} } $headers->cookie] unless @_;
  175         338  
  175         1065  
41              
42             # Add cookies
43 225 100 100     943 my @cookies = map { ref $_ eq 'HASH' ? Mojo::Cookie::Request->new($_) : $_ } $headers->cookie || (), @_;
  392         1395  
44 225         1838 $headers->cookie(join '; ', @cookies);
45              
46 225         1334 return $self;
47             }
48              
49 298     298 1 871 sub every_param { shift->params->every_param(@_) }
50              
51             sub extract_start_line {
52 1048     1048 1 2647 my ($self, $bufref) = @_;
53              
54             # Ignore any leading empty lines
55 1048 100       12143 return undef unless $$bufref =~ s/^\s*(.*?)\x0d?\x0a//;
56              
57             # We have a (hopefully) full request-line
58 1023 100       7667 return !$self->error({message => 'Bad request start-line'}) unless $1 =~ /^(\S+)\s+(\S+)\s+HTTP\/(\d\.\d)$/;
59 1022         3859 my $url = $self->method($1)->version($3)->url;
60 1022         3040 my $target = $2;
61 1022 100       3420 return !!$url->host_port($target) if $1 eq 'CONNECT';
62 1020 100       3172 return !!$url->parse($target)->fragment(undef) if $target =~ /^[^:\/?#]+:/;
63 1013         2807 return !!$url->path_query($target);
64             }
65              
66             sub fix_headers {
67 1941     1941 1 3218 my $self = shift;
68 1941 100       7684 $self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
69              
70             # Empty
71 971         2732 my $headers = $self->headers;
72 971 100 100     2663 $headers->remove('Content-Length') if ($headers->content_length // '') eq '0' && $self->method eq 'GET';
      100        
73              
74             # Host
75 971         2829 my $url = $self->url;
76 971 100       3123 $headers->host($url->host_port) unless $headers->host;
77              
78             # Basic authentication
79 971 100 66     3421 if ((my $info = $url->userinfo) && !$headers->authorization) {
80 6         55 $headers->authorization('Basic ' . b64_encode($info, ''));
81             }
82              
83             # Basic proxy authentication
84 971 100 100     2631 return $self unless (my $proxy = $self->proxy) && $self->via_proxy;
85 14 100       42 return $self unless my $info = $proxy->userinfo;
86 6 50       36 $headers->proxy_authorization('Basic ' . b64_encode($info, '')) unless $headers->proxy_authorization;
87 6         19 return $self;
88             }
89              
90             sub get_start_line_chunk {
91 1002     1002 1 2685 my ($self, $offset) = @_;
92 1002         2321 $self->_start_line->emit(progress => 'start_line', $offset);
93 1002         4175 return substr $self->{start_buffer}, $offset, 131072;
94             }
95              
96 4688   100 4688 1 13317 sub is_handshake { lc($_[0]->headers->upgrade // '') eq 'websocket' }
97              
98             sub is_secure {
99 3     3 1 13 my $url = shift->url;
100 3   33     9 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 797 sub param { shift->params->param(@_) }
106              
107 723   66 723 1 3467 sub params { $_[0]->{params} ||= $_[0]->body_params->clone->append($_[0]->query_params) }
108              
109             sub parse {
110 1497 100   1497 1 17096 my ($self, $env, $chunk) = (shift, ref $_[0] ? (shift, '') : (undef, shift));
111              
112             # Parse CGI environment
113 1497 100       3812 $self->env($env)->_parse_env($env) if $env;
114              
115             # Parse normal message
116 1497 100 100     6110 if (($self->{state} // '') ne 'cgi') { $self->SUPER::parse($chunk) }
  1442         4396  
117              
118             # Parse CGI content
119 55         152 else { $self->content($self->content->parse_body($chunk))->SUPER::parse('') }
120              
121             # Check if we can fix things that require all headers
122 1497 100       4815 return $self unless $self->is_finished;
123              
124             # Base URL
125 1077         3294 my $base = $self->url->base;
126 1077 100       3462 $base->scheme('http') unless $base->scheme;
127 1077         2910 my $headers = $self->headers;
128 1077 100 100     3273 if (!$base->host && (my $host = $headers->host)) { $base->host_port($host) }
  978         3044  
129              
130             # Basic authentication
131 1077 100       4200 if (my $basic = _basic($headers->authorization)) { $base->userinfo($basic) }
  7         25  
132              
133             # Basic proxy authentication
134 1077         3160 my $basic = _basic($headers->proxy_authorization);
135 1077 100       2579 $self->proxy(Mojo::URL->new->userinfo($basic)) if $basic;
136              
137             # "X-Forwarded-Proto"
138 1077 100 100     3209 $base->scheme('https') if $self->reverse_proxy && ($headers->header('X-Forwarded-Proto') // '') eq 'https';
      100        
139              
140 1077         3402 return $self;
141             }
142              
143 319     319 1 1477 sub query_params { shift->url->query }
144              
145 936     936 1 3142 sub start_line_size { length shift->_start_line->{start_buffer} }
146              
147 2154 100 66 2154   7239 sub _basic { $_[0] && $_[0] =~ /Basic (.+)$/ ? b64_decode $1 : undef }
148              
149             sub _parse_env {
150 32     32   64 my ($self, $env) = @_;
151              
152             # Bypass normal message parser
153 32         78 $self->{state} = 'cgi';
154              
155             # Extract headers
156 32         103 my $headers = $self->headers;
157 32         84 my $url = $self->url;
158 32         88 my $base = $url->base;
159 32         145 for my $name (keys %$env) {
160 330         510 my $value = $env->{$name};
161 330 100       786 next unless $name =~ s/^HTTP_//i;
162 59         151 $name =~ y/_/-/;
163 59         199 $headers->header($name => $value);
164              
165             # Host/Port
166 59 100       310 $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       129 $headers->content_type($env->{CONTENT_TYPE}) if $env->{CONTENT_TYPE};
171              
172             # Content-Length is a special case on some servers
173 32 100       115 $headers->content_length($env->{CONTENT_LENGTH}) if $env->{CONTENT_LENGTH};
174              
175             # Query
176 32 100       103 $url->query->parse($env->{QUERY_STRING}) if $env->{QUERY_STRING};
177              
178             # Method
179 32 50       265 $self->method($env->{REQUEST_METHOD}) if $env->{REQUEST_METHOD};
180              
181             # Scheme/Version
182 32 50 33     248 $base->scheme($1) and $self->version($2) if ($env->{SERVER_PROTOCOL} // '') =~ m!^([^/]+)/([^/]+)$!;
      50        
183              
184             # HTTPS
185 32 100 100     183 $base->scheme('https') if uc($env->{HTTPS} // '') eq 'ON';
186              
187             # Path
188 32 100       108 my $path = $url->path->parse($env->{PATH_INFO} ? $env->{PATH_INFO} : '');
189              
190             # Base path
191 32 100       96 if (my $value = $env->{SCRIPT_NAME}) {
192              
193             # Make sure there is a trailing slash (important for merging)
194 30 100       202 $base->path->parse($value =~ m!/$! ? $value : "$value/");
195              
196             # Remove SCRIPT_NAME prefix if necessary
197 30         88 my $buffer = $path->to_string;
198 30         125 $value =~ s!^/|/$!!g;
199 30         342 $buffer =~ s!^/?\Q$value\E/?!!;
200 30         65 $buffer =~ s!^/!!;
201 30         88 $path->parse($buffer);
202             }
203             }
204              
205             sub _start_line {
206 1938     1938   3470 my $self = shift;
207              
208 1938 100       7580 return $self if defined $self->{start_buffer};
209              
210             # Path
211 968         2697 my $url = $self->url;
212 968         3809 my $path = $url->path_query;
213 968 100       4458 $path = "/$path" unless $path =~ m!^/!;
214              
215             # CONNECT
216 968         3234 my $method = uc $self->method;
217 968 100 100     4244 if ($method eq 'CONNECT') {
    100 100        
218 3 0 33     15 my $port = $url->port // ($url->protocol eq 'https' ? '443' : '80');
219 3         11 $path = $url->ihost . ":$port";
220             }
221              
222             # Proxy
223             elsif ($self->proxy && $self->via_proxy && $url->protocol ne 'https') {
224 8 100       24 $path = $url->clone->userinfo(undef) unless $self->is_handshake;
225             }
226              
227 968         3150 $self->{start_buffer} = "$method $path HTTP/@{[$self->version]}\x0d\x0a";
  968         3022  
228              
229 968         5502 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