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   66006 use Mojo::Base 'Mojo::Message';
  58         115  
  58         418  
3              
4 58     58   464 use Digest::SHA qw(sha1_base64);
  58         150  
  58         3169  
5 58     58   6982 use Mojo::Cookie::Request;
  58         246  
  58         605  
6 58     58   329 use Mojo::Util qw(b64_encode b64_decode sha1_sum);
  58         552  
  58         3014  
7 58     58   8072 use Mojo::URL;
  58         157  
  58         548  
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 46 my $self = shift;
25              
26             # Dynamic requests cannot be cloned
27 19 100       52 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       57 $clone->{proxy} = $self->{proxy}->clone if $self->{proxy};
31              
32 15         46 return $clone;
33             }
34              
35             sub cookies {
36 400     400 1 789 my $self = shift;
37              
38             # Parse cookies
39 400         1145 my $headers = $self->headers;
40 400 100       1559 return [map { @{Mojo::Cookie::Request->parse($_)} } $headers->cookie] unless @_;
  175         326  
  175         1018  
41              
42             # Add cookies
43 225 100 100     770 my @cookies = map { ref $_ eq 'HASH' ? Mojo::Cookie::Request->new($_) : $_ } $headers->cookie || (), @_;
  392         1405  
44 225         1665 $headers->cookie(join '; ', @cookies);
45              
46 225         1264 return $self;
47             }
48              
49 298     298 1 856 sub every_param { shift->params->every_param(@_) }
50              
51             sub extract_start_line {
52 1048     1048 1 2495 my ($self, $bufref) = @_;
53              
54             # Ignore any leading empty lines
55 1048 100       11783 return undef unless $$bufref =~ s/^\s*(.*?)\x0d?\x0a//;
56              
57             # We have a (hopefully) full request-line
58 1023 100       7467 return !$self->error({message => 'Bad request start-line'}) unless $1 =~ /^(\S+)\s+(\S+)\s+HTTP\/(\d\.\d)$/;
59 1022         3268 my $url = $self->method($1)->version($3)->url;
60 1022         2902 my $target = $2;
61 1022 100       3145 return !!$url->host_port($target) if $1 eq 'CONNECT';
62 1020 100       2994 return !!$url->parse($target)->fragment(undef) if $target =~ /^[^:\/?#]+:/;
63 1013         2761 return !!$url->path_query($target);
64             }
65              
66             sub fix_headers {
67 1941     1941 1 3148 my $self = shift;
68 1941 100       7498 $self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
69              
70             # Empty
71 971         2662 my $headers = $self->headers;
72 971 100 100     2614 $headers->remove('Content-Length') if ($headers->content_length // '') eq '0' && $self->method eq 'GET';
      100        
73              
74             # Host
75 971         2753 my $url = $self->url;
76 971 100       2909 $headers->host($url->host_port) unless $headers->host;
77              
78             # Basic authentication
79 971 100 66     3039 if ((my $info = $url->userinfo) && !$headers->authorization) {
80 6         50 $headers->authorization('Basic ' . b64_encode($info, ''));
81             }
82              
83             # Basic proxy authentication
84 971 100 100     2531 return $self unless (my $proxy = $self->proxy) && $self->via_proxy;
85 14 100       45 return $self unless my $info = $proxy->userinfo;
86 6 50       26 $headers->proxy_authorization('Basic ' . b64_encode($info, '')) unless $headers->proxy_authorization;
87 6         18 return $self;
88             }
89              
90             sub get_start_line_chunk {
91 1002     1002 1 2119 my ($self, $offset) = @_;
92 1002         2346 $self->_start_line->emit(progress => 'start_line', $offset);
93 1002         3899 return substr $self->{start_buffer}, $offset, 131072;
94             }
95              
96 4688   100 4688 1 12699 sub is_handshake { lc($_[0]->headers->upgrade // '') eq 'websocket' }
97              
98             sub is_secure {
99 3     3 1 14 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 743 sub param { shift->params->param(@_) }
106              
107 723   66 723 1 3361 sub params { $_[0]->{params} ||= $_[0]->body_params->clone->append($_[0]->query_params) }
108              
109             sub parse {
110 1526 100   1526 1 16950 my ($self, $env, $chunk) = (shift, ref $_[0] ? (shift, '') : (undef, shift));
111              
112             # Parse CGI environment
113 1526 100       3676 $self->env($env)->_parse_env($env) if $env;
114              
115             # Parse normal message
116 1526 100 100     6115 if (($self->{state} // '') ne 'cgi') { $self->SUPER::parse($chunk) }
  1471         4319  
117              
118             # Parse CGI content
119 55         169 else { $self->content($self->content->parse_body($chunk))->SUPER::parse('') }
120              
121             # Check if we can fix things that require all headers
122 1526 100       4714 return $self unless $self->is_finished;
123              
124             # Base URL
125 1077         3217 my $base = $self->url->base;
126 1077 100       3159 $base->scheme('http') unless $base->scheme;
127 1077         2819 my $headers = $self->headers;
128 1077 100 100     3040 if (!$base->host && (my $host = $headers->host)) { $base->host_port($host) }
  978         2896  
129              
130             # Basic authentication
131 1077 100       3777 if (my $basic = _basic($headers->authorization)) { $base->userinfo($basic) }
  7         29  
132              
133             # Basic proxy authentication
134 1077         2965 my $basic = _basic($headers->proxy_authorization);
135 1077 100       2686 $self->proxy(Mojo::URL->new->userinfo($basic)) if $basic;
136              
137             # "X-Forwarded-Proto"
138 1077 100 100     3032 $base->scheme('https') if $self->reverse_proxy && ($headers->header('X-Forwarded-Proto') // '') eq 'https';
      100        
139              
140 1077         3320 return $self;
141             }
142              
143 319     319 1 1404 sub query_params { shift->url->query }
144              
145 936     936 1 3100 sub start_line_size { length shift->_start_line->{start_buffer} }
146              
147 2154 100 66 2154   7146 sub _basic { $_[0] && $_[0] =~ /Basic (.+)$/ ? b64_decode $1 : undef }
148              
149             sub _parse_env {
150 32     32   75 my ($self, $env) = @_;
151              
152             # Bypass normal message parser
153 32         95 $self->{state} = 'cgi';
154              
155             # Extract headers
156 32         120 my $headers = $self->headers;
157 32         105 my $url = $self->url;
158 32         95 my $base = $url->base;
159 32         159 for my $name (keys %$env) {
160 330         532 my $value = $env->{$name};
161 330 100       901 next unless $name =~ s/^HTTP_//i;
162 59         146 $name =~ y/_/-/;
163 59         224 $headers->header($name => $value);
164              
165             # Host/Port
166 59 100       343 $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       182 $headers->content_type($env->{CONTENT_TYPE}) if $env->{CONTENT_TYPE};
171              
172             # Content-Length is a special case on some servers
173 32 100       126 $headers->content_length($env->{CONTENT_LENGTH}) if $env->{CONTENT_LENGTH};
174              
175             # Query
176 32 100       142 $url->query->parse($env->{QUERY_STRING}) if $env->{QUERY_STRING};
177              
178             # Method
179 32 50       276 $self->method($env->{REQUEST_METHOD}) if $env->{REQUEST_METHOD};
180              
181             # Scheme/Version
182 32 50 33     281 $base->scheme($1) and $self->version($2) if ($env->{SERVER_PROTOCOL} // '') =~ m!^([^/]+)/([^/]+)$!;
      50        
183              
184             # HTTPS
185 32 100 100     208 $base->scheme('https') if uc($env->{HTTPS} // '') eq 'ON';
186              
187             # Path
188 32 100       106 my $path = $url->path->parse($env->{PATH_INFO} ? $env->{PATH_INFO} : '');
189              
190             # Base path
191 32 100       119 if (my $value = $env->{SCRIPT_NAME}) {
192              
193             # Make sure there is a trailing slash (important for merging)
194 30 100       179 $base->path->parse($value =~ m!/$! ? $value : "$value/");
195              
196             # Remove SCRIPT_NAME prefix if necessary
197 30         97 my $buffer = $path->to_string;
198 30         147 $value =~ s!^/|/$!!g;
199 30         401 $buffer =~ s!^/?\Q$value\E/?!!;
200 30         80 $buffer =~ s!^/!!;
201 30         97 $path->parse($buffer);
202             }
203             }
204              
205             sub _start_line {
206 1938     1938   3256 my $self = shift;
207              
208 1938 100       7360 return $self if defined $self->{start_buffer};
209              
210             # Path
211 968         2517 my $url = $self->url;
212 968         3318 my $path = $url->path_query;
213 968 100       4388 $path = "/$path" unless $path =~ m!^/!;
214              
215             # CONNECT
216 968         3109 my $method = uc $self->method;
217 968 100 100     4204 if ($method eq 'CONNECT') {
    100 100        
218 3 0 33     14 my $port = $url->port // ($url->protocol eq 'https' ? '443' : '80');
219 3         12 $path = $url->ihost . ":$port";
220             }
221              
222             # Proxy
223             elsif ($self->proxy && $self->via_proxy && $url->protocol ne 'https') {
224 8 100       27 $path = $url->clone->userinfo(undef) unless $self->is_handshake;
225             }
226              
227 968         3131 $self->{start_buffer} = "$method $path HTTP/@{[$self->version]}\x0d\x0a";
  968         2905  
228              
229 968         5126 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