add files
|
1 |
package Mojo::Message::Request; |
2 |
use Mojo::Base 'Mojo::Message'; |
|
3 | ||
4 |
use Mojo::Cookie::Request; |
|
5 |
use Mojo::Util qw(b64_encode b64_decode get_line); |
|
6 |
use Mojo::URL; |
|
7 | ||
8 |
has env => sub { {} }; |
|
9 |
has method => 'GET'; |
|
10 |
has url => sub { Mojo::URL->new }; |
|
11 | ||
12 |
my $START_LINE_RE = qr/ |
|
13 |
^ |
|
14 |
([a-zA-Z]+) # Method |
|
15 |
\s+ |
|
16 |
([0-9a-zA-Z!#\$\%&'()*+,\-.\/:;=?\@[\\\]^_`\{|\}~]+) # URL |
|
17 |
(?:\s+HTTP\/(\d\.\d))? # Version |
|
18 |
$ |
|
19 |
/x; |
|
20 | ||
21 |
sub clone { |
|
22 |
my $self = shift; |
|
23 | ||
24 |
# Dynamic requests cannot be cloned |
|
25 |
return undef unless my $content = $self->content->clone; |
|
26 |
my $clone = $self->new( |
|
27 |
content => $content, |
|
28 |
method => $self->method, |
|
29 |
url => $self->url->clone, |
|
30 |
version => $self->version |
|
31 |
); |
|
32 |
$clone->{proxy} = $self->{proxy}->clone if $self->{proxy}; |
|
33 | ||
34 |
return $clone; |
|
35 |
} |
|
36 | ||
37 |
sub cookies { |
|
38 |
my $self = shift; |
|
39 | ||
40 |
# Parse cookies |
|
41 |
my $headers = $self->headers; |
|
42 |
return [map { @{Mojo::Cookie::Request->parse($_)} } $headers->cookie] |
|
43 |
unless @_; |
|
44 | ||
45 |
# Add cookies |
|
46 |
my @cookies = $headers->cookie || (); |
|
47 |
for my $cookie (@_) { |
|
48 |
$cookie = Mojo::Cookie::Request->new($cookie) if ref $cookie eq 'HASH'; |
|
49 |
push @cookies, $cookie; |
|
50 |
} |
|
51 |
$headers->cookie(join('; ', @cookies)); |
|
52 | ||
53 |
return $self; |
|
54 |
} |
|
55 | ||
56 |
sub extract_start_line { |
|
57 |
my ($self, $bufref) = @_; |
|
58 | ||
59 |
# Ignore any leading empty lines |
|
60 |
$$bufref =~ s/^\s+//; |
|
61 |
return undef unless defined(my $line = get_line $bufref); |
|
62 | ||
63 |
# We have a (hopefully) full request line |
|
64 |
$self->error('Bad request start line', 400) and return undef |
|
65 |
unless $line =~ $START_LINE_RE; |
|
66 |
my $url = $self->method($1)->version($3)->url; |
|
67 |
return !!($1 eq 'CONNECT' ? $url->authority($2) : $url->parse($2)); |
|
68 |
} |
|
69 | ||
70 |
sub fix_headers { |
|
71 |
my $self = shift; |
|
72 |
$self->{fix} ? return $self : $self->SUPER::fix_headers(@_); |
|
73 | ||
74 |
# Basic authentication |
|
75 |
my $url = $self->url; |
|
76 |
my $headers = $self->headers; |
|
77 |
my $auth = $url->userinfo; |
|
78 |
$headers->authorization('Basic ' . b64_encode($auth, '')) |
|
79 |
if $auth && !$headers->authorization; |
|
80 | ||
81 |
# Basic proxy authentication |
|
82 |
if (my $proxy = $self->proxy) { |
|
83 |
my $proxy_auth = $proxy->userinfo; |
|
84 |
$headers->proxy_authorization('Basic ' . b64_encode($proxy_auth, '')) |
|
85 |
if $proxy_auth && !$headers->proxy_authorization; |
|
86 |
} |
|
87 | ||
88 |
# Host |
|
89 |
my $host = $url->ihost; |
|
90 |
my $port = $url->port; |
|
91 |
$headers->host($port ? "$host:$port" : $host) unless $headers->host; |
|
92 | ||
93 |
return $self; |
|
94 |
} |
|
95 | ||
96 |
sub get_start_line_chunk { |
|
97 |
my ($self, $offset) = @_; |
|
98 | ||
99 |
unless (defined $self->{start_buffer}) { |
|
100 | ||
101 |
# Path |
|
102 |
my $url = $self->url; |
|
103 |
my $path = $url->path->to_string; |
|
104 |
my $query = $url->query->to_string; |
|
105 |
$path .= "?$query" if $query; |
|
106 |
$path = "/$path" unless $path =~ m!^/!; |
|
107 | ||
108 |
# CONNECT |
|
109 |
my $method = uc $self->method; |
|
110 |
if ($method eq 'CONNECT') { |
|
111 |
my $port = $url->port || ($url->protocol eq 'https' ? '443' : '80'); |
|
112 |
$path = $url->host . ":$port"; |
|
113 |
} |
|
114 | ||
115 |
# Proxy |
|
116 |
elsif ($self->proxy) { |
|
117 |
my $clone = $url = $url->clone->userinfo(undef); |
|
118 |
my $upgrade = lc($self->headers->upgrade // ''); |
|
119 |
$path = $clone |
|
120 |
unless $upgrade eq 'websocket' || $url->protocol eq 'https'; |
|
121 |
} |
|
122 | ||
123 |
$self->{start_buffer} = "$method $path HTTP/@{[$self->version]}\x0d\x0a"; |
|
124 |
} |
|
125 | ||
126 |
$self->emit(progress => 'start_line', $offset); |
|
127 |
return substr $self->{start_buffer}, $offset, 131072; |
|
128 |
} |
|
129 | ||
130 |
sub is_secure { |
|
131 |
my $url = shift->url; |
|
132 |
return ($url->protocol || $url->base->protocol) eq 'https'; |
|
133 |
} |
|
134 | ||
135 |
sub is_xhr { |
|
136 |
(shift->headers->header('X-Requested-With') // '') =~ /XMLHttpRequest/i; |
|
137 |
} |
|
138 | ||
139 |
sub param { shift->params->param(@_) } |
|
140 | ||
141 |
sub params { |
|
142 |
my $self = shift; |
|
143 |
return $self->{params} |
|
144 |
||= $self->body_params->clone->merge($self->query_params); |
|
145 |
} |
|
146 | ||
147 |
sub parse { |
|
148 |
my $self = shift; |
|
149 | ||
150 |
# Parse CGI environment |
|
151 |
my $env = @_ > 1 ? {@_} : ref $_[0] eq 'HASH' ? $_[0] : undef; |
|
152 |
$self->env($env)->_parse_env($env) if $env; |
|
153 | ||
154 |
# Parse normal message |
|
155 |
my @args = $env ? () : @_; |
|
156 |
if (($self->{state} // '') ne 'cgi') { $self->SUPER::parse(@args) } |
|
157 | ||
158 |
# Parse CGI content |
|
159 |
else { $self->content($self->content->parse_body(@args))->SUPER::parse } |
|
160 | ||
161 |
# Check if we can fix things that require all headers |
|
162 |
return $self unless $self->is_finished; |
|
163 | ||
164 |
# Base URL |
|
165 |
my $base = $self->url->base; |
|
166 |
$base->scheme('http') unless $base->scheme; |
|
167 |
my $headers = $self->headers; |
|
168 |
if (!$base->host && (my $host = $headers->host)) { $base->authority($host) } |
|
169 | ||
170 |
# Basic authentication |
|
171 |
my $auth = _parse_basic_auth($headers->authorization); |
|
172 |
$base->userinfo($auth) if $auth; |
|
173 | ||
174 |
# Basic proxy authentication |
|
175 |
my $proxy_auth = _parse_basic_auth($headers->proxy_authorization); |
|
176 |
$self->proxy(Mojo::URL->new->userinfo($proxy_auth)) if $proxy_auth; |
|
177 | ||
178 |
# "X-Forwarded-HTTPS" |
|
179 |
$base->scheme('https') |
|
180 |
if $ENV{MOJO_REVERSE_PROXY} && $headers->header('X-Forwarded-HTTPS'); |
|
181 | ||
182 |
return $self; |
|
183 |
} |
|
184 | ||
185 |
sub proxy { |
|
186 |
my $self = shift; |
|
187 |
return $self->{proxy} unless @_; |
|
188 |
$self->{proxy} = !$_[0] || ref $_[0] ? shift : Mojo::URL->new(shift); |
|
189 |
return $self; |
|
190 |
} |
|
191 | ||
192 |
sub query_params { shift->url->query } |
|
193 | ||
194 |
sub _parse_basic_auth { |
|
195 |
return undef unless my $header = shift; |
|
196 |
return $header =~ /Basic (.+)$/ ? b64_decode($1) : undef; |
|
197 |
} |
|
198 | ||
199 |
sub _parse_env { |
|
200 |
my ($self, $env) = @_; |
|
201 | ||
202 |
# Extract headers |
|
203 |
my $headers = $self->headers; |
|
204 |
my $url = $self->url; |
|
205 |
my $base = $url->base; |
|
206 |
while (my ($name, $value) = each %$env) { |
|
207 |
next unless $name =~ s/^HTTP_//i; |
|
208 |
$name =~ s/_/-/g; |
|
209 |
$headers->header($name => $value); |
|
210 | ||
211 |
# Host/Port |
|
212 |
if ($name eq 'HOST') { |
|
213 |
my ($host, $port) = ($value, undef); |
|
214 |
($host, $port) = ($1, $2) if $host =~ /^([^:]*):?(.*)$/; |
|
215 |
$base->host($host)->port($port); |
|
216 |
} |
|
217 |
} |
|
218 | ||
219 |
# Content-Type is a special case on some servers |
|
220 |
$headers->content_type($env->{CONTENT_TYPE}) if $env->{CONTENT_TYPE}; |
|
221 | ||
222 |
# Content-Length is a special case on some servers |
|
223 |
$headers->content_length($env->{CONTENT_LENGTH}) if $env->{CONTENT_LENGTH}; |
|
224 | ||
225 |
# Query |
|
226 |
$url->query->parse($env->{QUERY_STRING}) if $env->{QUERY_STRING}; |
|
227 | ||
228 |
# Method |
|
229 |
$self->method($env->{REQUEST_METHOD}) if $env->{REQUEST_METHOD}; |
|
230 | ||
231 |
# Scheme/Version |
|
232 |
if (($env->{SERVER_PROTOCOL} // '') =~ m!^([^/]+)/([^/]+)$!) { |
|
233 |
$base->scheme($1); |
|
234 |
$self->version($2); |
|
235 |
} |
|
236 | ||
237 |
# HTTPS |
|
238 |
$base->scheme('https') if $env->{HTTPS}; |
|
239 | ||
240 |
# Path |
|
241 |
my $path = $url->path->parse($env->{PATH_INFO} ? $env->{PATH_INFO} : ''); |
|
242 | ||
243 |
# Base path |
|
244 |
if (my $value = $env->{SCRIPT_NAME}) { |
|
245 | ||
246 |
# Make sure there is a trailing slash (important for merging) |
|
247 |
$base->path->parse($value =~ m!/$! ? $value : "$value/"); |
|
248 | ||
249 |
# Remove SCRIPT_NAME prefix if necessary |
|
250 |
my $buffer = $path->to_string; |
|
251 |
$value =~ s!^/|/$!!g; |
|
252 |
$buffer =~ s!^/?\Q$value\E/?!!; |
|
253 |
$buffer =~ s!^/!!; |
|
254 |
$path->parse($buffer); |
|
255 |
} |
|
256 | ||
257 |
# Bypass normal message parser |
|
258 |
$self->{state} = 'cgi'; |
|
259 |
} |
|
260 | ||
261 |
1; |
|
262 | ||
263 |
=encoding utf8 |
|
264 | ||
265 |
=head1 NAME |
|
266 | ||
267 |
Mojo::Message::Request - HTTP request |
|
268 | ||
269 |
=head1 SYNOPSIS |
|
270 | ||
271 |
use Mojo::Message::Request; |
|
272 | ||
273 |
# Parse |
|
274 |
my $req = Mojo::Message::Request->new; |
|
275 |
$req->parse("GET /foo HTTP/1.0\x0d\x0a"); |
|
276 |
$req->parse("Content-Length: 12\x0d\x0a"); |
|
277 |
$req->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a"); |
|
278 |
$req->parse('Hello World!'); |
|
279 |
say $req->method; |
|
280 |
say $req->headers->content_type; |
|
281 |
say $req->body; |
|
282 | ||
283 |
# Build |
|
284 |
my $req = Mojo::Message::Request->new; |
|
285 |
$req->url->parse('http://127.0.0.1/foo/bar'); |
|
286 |
$req->method('GET'); |
|
287 |
say $req->to_string; |
|
288 | ||
289 |
=head1 DESCRIPTION |
|
290 | ||
291 |
L<Mojo::Message::Request> is a container for HTTP requests as described in RFC |
|
292 |
2616 and RFC 2817. |
|
293 | ||
294 |
=head1 EVENTS |
|
295 | ||
296 |
L<Mojo::Message::Request> inherits all events from L<Mojo::Message>. |
|
297 | ||
298 |
=head1 ATTRIBUTES |
|
299 | ||
300 |
L<Mojo::Message::Request> inherits all attributes from L<Mojo::Message> and |
|
301 |
implements the following new ones. |
|
302 | ||
303 |
=head2 env |
|
304 | ||
305 |
my $env = $req->env; |
|
306 |
$req = $req->env({}); |
|
307 | ||
308 |
Direct access to the C<CGI> or C<PSGI> environment hash if available. |
|
309 | ||
310 |
# Check CGI version |
|
311 |
my $version = $req->env->{GATEWAY_INTERFACE}; |
|
312 | ||
313 |
# Check PSGI version |
|
314 |
my $version = $req->env->{'psgi.version'}; |
|
315 | ||
316 |
=head2 method |
|
317 | ||
318 |
my $method = $req->method; |
|
319 |
$req = $req->method('POST'); |
|
320 | ||
321 |
HTTP request method, defaults to C<GET>. |
|
322 | ||
323 |
=head2 url |
|
324 | ||
325 |
my $url = $req->url; |
|
326 |
$req = $req->url(Mojo::URL->new); |
|
327 | ||
328 |
HTTP request URL, defaults to a L<Mojo::URL> object. |
|
329 | ||
330 |
# Get request information |
|
331 |
say $req->url->to_abs->userinfo; |
|
332 |
say $req->url->to_abs->host; |
|
333 |
say $req->url->to_abs->path; |
|
334 | ||
335 |
=head1 METHODS |
|
336 | ||
337 |
L<Mojo::Message::Request> inherits all methods from L<Mojo::Message> and |
|
338 |
implements the following new ones. |
|
339 | ||
340 |
=head2 clone |
|
341 | ||
342 |
my $clone = $req->clone; |
|
343 | ||
344 |
Clone request if possible, otherwise return C<undef>. |
|
345 | ||
346 |
=head2 cookies |
|
347 | ||
348 |
my $cookies = $req->cookies; |
|
349 |
$req = $req->cookies(Mojo::Cookie::Request->new); |
|
350 |
$req = $req->cookies({name => 'foo', value => 'bar'}); |
|
351 | ||
352 |
Access request cookies, usually L<Mojo::Cookie::Request> objects. |
|
353 | ||
354 |
=head2 extract_start_line |
|
355 | ||
356 |
my $bool = $req->extract_start_line(\$str); |
|
357 | ||
358 |
Extract request line from string. |
|
359 | ||
360 |
=head2 fix_headers |
|
361 | ||
362 |
$req = $req->fix_headers; |
|
363 | ||
364 |
Make sure request has all required headers. |
|
365 | ||
366 |
=head2 get_start_line_chunk |
|
367 | ||
368 |
my $bytes = $req->get_start_line_chunk($offset); |
|
369 | ||
370 |
Get a chunk of request line data starting from a specific position. |
|
371 | ||
372 |
=head2 is_secure |
|
373 | ||
374 |
my $bool = $req->is_secure; |
|
375 | ||
376 |
Check if connection is secure. |
|
377 | ||
378 |
=head2 is_xhr |
|
379 | ||
380 |
my $bool = $req->is_xhr; |
|
381 | ||
382 |
Check C<X-Requested-With> header for C<XMLHttpRequest> value. |
|
383 | ||
384 |
=head2 param |
|
385 | ||
386 |
my @names = $req->param; |
|
387 |
my $foo = $req->param('foo'); |
|
388 |
my @foo = $req->param('foo'); |
|
389 | ||
390 |
Access GET and POST parameters. Note that this method caches all data, so it |
|
391 |
should not be called before the entire request body has been received. Parts |
|
392 |
of the request body need to be loaded into memory to parse POST parameters, so |
|
393 |
you have to make sure it is not excessively large. |
|
394 | ||
395 |
=head2 params |
|
396 | ||
397 |
my $params = $req->params; |
|
398 | ||
399 |
All GET and POST parameters, usually a L<Mojo::Parameters> object. Note that |
|
400 |
this method caches all data, so it should not be called before the entire |
|
401 |
request body has been received. Parts of the request body need to be loaded |
|
402 |
into memory to parse POST parameters, so you have to make sure it is not |
|
403 |
excessively large. |
|
404 | ||
405 |
# Get parameter value |
|
406 |
say $req->params->param('foo'); |
|
407 | ||
408 |
=head2 parse |
|
409 | ||
410 |
$req = $req->parse('GET /foo/bar HTTP/1.1'); |
|
411 |
$req = $req->parse(REQUEST_METHOD => 'GET'); |
|
412 |
$req = $req->parse({REQUEST_METHOD => 'GET'}); |
|
413 | ||
414 |
Parse HTTP request chunks or environment hash. |
|
415 | ||
416 |
=head2 proxy |
|
417 | ||
418 |
my $proxy = $req->proxy; |
|
419 |
$req = $req->proxy('http://foo:bar@127.0.0.1:3000'); |
|
420 |
$req = $req->proxy(Mojo::URL->new('http://127.0.0.1:3000')); |
|
421 | ||
422 |
Proxy URL for request. |
|
423 | ||
424 |
# Disable proxy |
|
425 |
$req->proxy(0); |
|
426 | ||
427 |
=head2 query_params |
|
428 | ||
429 |
my $params = $req->query_params; |
|
430 | ||
431 |
All GET parameters, usually a L<Mojo::Parameters> object. |
|
432 | ||
433 |
# Turn GET parameters to hash and extract value |
|
434 |
say $req->query_params->to_hash->{foo}; |
|
435 | ||
436 |
=head1 SEE ALSO |
|
437 | ||
438 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
439 | ||
440 |
=cut |