add files
|
1 |
package Mojo::URL; |
2 |
use Mojo::Base -base; |
|
3 |
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1; |
|
4 | ||
5 |
use Mojo::Parameters; |
|
6 |
use Mojo::Path; |
|
7 |
use Mojo::Util qw(punycode_decode punycode_encode url_escape url_unescape); |
|
8 | ||
9 |
has base => sub { Mojo::URL->new }; |
|
10 |
has [qw(fragment host port scheme userinfo)]; |
|
11 | ||
12 |
sub new { shift->SUPER::new->parse(@_) } |
|
13 | ||
14 |
sub authority { |
|
15 |
my $self = shift; |
|
16 | ||
17 |
# New authority |
|
18 |
if (@_) { |
|
19 |
return $self unless defined(my $authority = shift); |
|
20 | ||
21 |
# Userinfo |
|
22 |
$authority =~ s/^([^\@]+)\@// and $self->userinfo(url_unescape $1); |
|
23 | ||
24 |
# Port |
|
25 |
$authority =~ s/:(\d+)$// and $self->port($1); |
|
26 | ||
27 |
# Host |
|
28 |
my $host = url_unescape $authority; |
|
29 |
return $host =~ /[^\x00-\x7f]/ ? $self->ihost($host) : $self->host($host); |
|
30 |
} |
|
31 | ||
32 |
# Build authority |
|
33 |
return undef unless defined(my $authority = $self->ihost); |
|
34 |
if (my $userinfo = $self->userinfo) { |
|
35 |
$userinfo = url_escape $userinfo, '^A-Za-z0-9\-._~!$&\'()*+,;=:'; |
|
36 |
$authority = $userinfo . '@' . $authority; |
|
37 |
} |
|
38 |
if (my $port = $self->port) { $authority .= ":$port" } |
|
39 | ||
40 |
return $authority; |
|
41 |
} |
|
42 | ||
43 |
sub clone { |
|
44 |
my $self = shift; |
|
45 | ||
46 |
my $clone = $self->new; |
|
47 |
$clone->$_($self->$_) for qw(scheme userinfo host port fragment); |
|
48 |
$clone->path($self->path->clone); |
|
49 |
$clone->query($self->query->clone); |
|
50 |
$clone->base($self->base->clone) if $self->{base}; |
|
51 | ||
52 |
return $clone; |
|
53 |
} |
|
54 | ||
55 |
sub ihost { |
|
56 |
my $self = shift; |
|
57 | ||
58 |
# Decode |
|
59 |
return $self->host(join '.', |
|
60 |
map { /^xn--(.+)$/ ? punycode_decode($_) : $_ } split /\./, shift) |
|
61 |
if @_; |
|
62 | ||
63 |
# Check if host needs to be encoded |
|
64 |
return undef unless defined(my $host = $self->host); |
|
65 |
return lc $host unless $host =~ /[^\x00-\x7f]/; |
|
66 | ||
67 |
# Encode |
|
68 |
return lc join '.', |
|
69 |
map { /[^\x00-\x7f]/ ? ('xn--' . punycode_encode $_) : $_ } split /\./, |
|
70 |
$host; |
|
71 |
} |
|
72 | ||
73 |
sub is_abs { !!shift->scheme } |
|
74 | ||
75 |
sub parse { |
|
76 |
my ($self, $url) = @_; |
|
77 |
return $self unless $url; |
|
78 | ||
79 |
# Official regex from RFC 3986 |
|
80 |
$url =~ m!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!; |
|
81 |
return $self->scheme($2)->authority($4)->path($5)->query($7)->fragment($9); |
|
82 |
} |
|
83 | ||
84 |
sub path { |
|
85 |
my $self = shift; |
|
86 | ||
87 |
# Old path |
|
88 |
$self->{path} ||= Mojo::Path->new; |
|
89 |
return $self->{path} unless @_; |
|
90 | ||
91 |
# New path |
|
92 |
my $path = shift; |
|
93 |
$self->{path} = ref $path ? $path : $self->{path}->merge($path); |
|
94 | ||
95 |
return $self; |
|
96 |
} |
|
97 | ||
98 |
sub protocol { lc(shift->scheme // '') } |
|
99 | ||
100 |
sub query { |
|
101 |
my $self = shift; |
|
102 | ||
103 |
# Old parameters |
|
104 |
my $q = $self->{query} ||= Mojo::Parameters->new; |
|
105 |
return $q unless @_; |
|
106 | ||
107 |
# Replace with list |
|
108 |
if (@_ > 1) { $q->params([])->parse(@_) } |
|
109 | ||
110 |
# Merge with array |
|
111 |
elsif (ref $_[0] eq 'ARRAY') { |
|
112 |
while (my $name = shift @{$_[0]}) { |
|
113 |
my $value = shift @{$_[0]}; |
|
114 |
defined $value ? $q->param($name => $value) : $q->remove($name); |
|
115 |
} |
|
116 |
} |
|
117 | ||
118 |
# Append hash |
|
119 |
elsif (ref $_[0] eq 'HASH') { $q->append(%{$_[0]}) } |
|
120 | ||
121 |
# Replace with string |
|
122 |
else { $q->parse($_[0]) } |
|
123 | ||
124 |
return $self; |
|
125 |
} |
|
126 | ||
127 |
sub to_abs { |
|
128 |
my $self = shift; |
|
129 | ||
130 |
my $abs = $self->clone; |
|
131 |
return $abs if $abs->is_abs; |
|
132 | ||
133 |
# Scheme |
|
134 |
my $base = shift || $abs->base; |
|
135 |
$abs->base($base)->scheme($base->scheme); |
|
136 | ||
137 |
# Authority |
|
138 |
return $abs if $abs->authority; |
|
139 |
$abs->authority($base->authority); |
|
140 | ||
141 |
# Absolute path |
|
142 |
my $path = $abs->path; |
|
143 |
return $abs if $path->leading_slash; |
|
144 | ||
145 |
# Inherit path |
|
146 |
my $base_path = $base->path; |
|
147 |
if (!@{$path->parts}) { |
|
148 |
$path |
|
149 |
= $abs->path($base_path->clone)->path->trailing_slash(0)->canonicalize; |
|
150 | ||
151 |
# Query |
|
152 |
return $abs if length $abs->query->to_string; |
|
153 |
$abs->query($base->query->clone); |
|
154 |
} |
|
155 | ||
156 |
# Merge paths |
|
157 |
else { $abs->path($base_path->clone->merge($path)->canonicalize) } |
|
158 | ||
159 |
return $abs; |
|
160 |
} |
|
161 | ||
162 |
sub to_rel { |
|
163 |
my $self = shift; |
|
164 | ||
165 |
my $rel = $self->clone; |
|
166 |
return $rel unless $rel->is_abs; |
|
167 | ||
168 |
# Scheme and authority |
|
169 |
my $base = shift || $rel->base; |
|
170 |
$rel->base($base)->scheme(undef); |
|
171 |
$rel->userinfo(undef)->host(undef)->port(undef) if $base->authority; |
|
172 | ||
173 |
# Path |
|
174 |
my @parts = @{$rel->path->parts}; |
|
175 |
my $base_path = $base->path; |
|
176 |
my @base_parts = @{$base_path->parts}; |
|
177 |
pop @base_parts unless $base_path->trailing_slash; |
|
178 |
while (@parts && @base_parts && $parts[0] eq $base_parts[0]) { |
|
179 |
shift @$_ for \@parts, \@base_parts; |
|
180 |
} |
|
181 |
my $path = $rel->path(Mojo::Path->new)->path; |
|
182 |
$path->leading_slash(1) if $rel->authority; |
|
183 |
$path->parts([('..') x @base_parts, @parts]); |
|
184 |
$path->trailing_slash(1) if $self->path->trailing_slash; |
|
185 | ||
186 |
return $rel; |
|
187 |
} |
|
188 | ||
189 |
sub to_string { |
|
190 |
my $self = shift; |
|
191 | ||
192 |
# Scheme |
|
193 |
my $url = ''; |
|
194 |
if (my $proto = $self->protocol) { $url .= "$proto:" } |
|
195 | ||
196 |
# Authority |
|
197 |
my $authority = $self->authority; |
|
198 |
$url .= "//$authority" if defined $authority; |
|
199 | ||
200 |
# Path |
|
201 |
my $path = $self->path->to_string; |
|
202 |
$url .= !$authority || $path eq '' || $path =~ m!^/! ? $path : "/$path"; |
|
203 | ||
204 |
# Query |
|
205 |
if (length(my $query = $self->query->to_string)) { $url .= "?$query" } |
|
206 | ||
207 |
# Fragment |
|
208 |
return $url unless defined(my $fragment = $self->fragment); |
|
209 |
return $url . '#' . url_escape $fragment, '^A-Za-z0-9\-._~!$&\'()*+,;=%:@/?'; |
|
210 |
} |
|
211 | ||
212 |
1; |
|
213 | ||
214 |
=encoding utf8 |
|
215 | ||
216 |
=head1 NAME |
|
217 | ||
218 |
Mojo::URL - Uniform Resource Locator |
|
219 | ||
220 |
=head1 SYNOPSIS |
|
221 | ||
222 |
use Mojo::URL; |
|
223 | ||
224 |
# Parse |
|
225 |
my $url |
|
226 |
= Mojo::URL->new('http://sri:foobar@example.com:3000/foo/bar?foo=bar#23'); |
|
227 |
say $url->scheme; |
|
228 |
say $url->userinfo; |
|
229 |
say $url->host; |
|
230 |
say $url->port; |
|
231 |
say $url->path; |
|
232 |
say $url->query; |
|
233 |
say $url->fragment; |
|
234 | ||
235 |
# Build |
|
236 |
my $url = Mojo::URL->new; |
|
237 |
$url->scheme('http'); |
|
238 |
$url->userinfo('sri:foobar'); |
|
239 |
$url->host('example.com'); |
|
240 |
$url->port(3000); |
|
241 |
$url->path('/foo/bar'); |
|
242 |
$url->query->param(foo => 'bar'); |
|
243 |
$url->fragment(23); |
|
244 |
say "$url"; |
|
245 | ||
246 |
=head1 DESCRIPTION |
|
247 | ||
248 |
L<Mojo::URL> implements a subset of RFC 3986 and RFC 3987 for Uniform |
|
249 |
Resource Locators with support for IDNA and IRIs. |
|
250 | ||
251 |
=head1 ATTRIBUTES |
|
252 | ||
253 |
L<Mojo::URL> implements the following attributes. |
|
254 | ||
255 |
=head2 base |
|
256 | ||
257 |
my $base = $url->base; |
|
258 |
$url = $url->base(Mojo::URL->new); |
|
259 | ||
260 |
Base of this URL. |
|
261 | ||
262 |
=head2 fragment |
|
263 | ||
264 |
my $fragment = $url->fragment; |
|
265 |
$url = $url->fragment('foo'); |
|
266 | ||
267 |
Fragment part of this URL. |
|
268 | ||
269 |
=head2 host |
|
270 | ||
271 |
my $host = $url->host; |
|
272 |
$url = $url->host('127.0.0.1'); |
|
273 | ||
274 |
Host part of this URL. |
|
275 | ||
276 |
=head2 port |
|
277 | ||
278 |
my $port = $url->port; |
|
279 |
$url = $url->port(8080); |
|
280 | ||
281 |
Port part of this URL. |
|
282 | ||
283 |
=head2 scheme |
|
284 | ||
285 |
my $scheme = $url->scheme; |
|
286 |
$url = $url->scheme('http'); |
|
287 | ||
288 |
Scheme part of this URL. |
|
289 | ||
290 |
=head2 userinfo |
|
291 | ||
292 |
my $userinfo = $url->userinfo; |
|
293 |
$url = $url->userinfo('root:pass%3Bw0rd'); |
|
294 | ||
295 |
Userinfo part of this URL. |
|
296 | ||
297 |
=head1 METHODS |
|
298 | ||
299 |
L<Mojo::URL> inherits all methods from L<Mojo::Base> and implements the |
|
300 |
following new ones. |
|
301 | ||
302 |
=head2 new |
|
303 | ||
304 |
my $url = Mojo::URL->new; |
|
305 |
my $url = Mojo::URL->new('http://127.0.0.1:3000/foo?f=b&baz=2#foo'); |
|
306 | ||
307 |
Construct a new L<Mojo::URL> object and L</"parse"> URL if necessary. |
|
308 | ||
309 |
=head2 authority |
|
310 | ||
311 |
my $authority = $url->authority; |
|
312 |
$url = $url->authority('root:pass%3Bw0rd@localhost:8080'); |
|
313 | ||
314 |
Authority part of this URL. |
|
315 | ||
316 |
=head2 clone |
|
317 | ||
318 |
my $url2 = $url->clone; |
|
319 | ||
320 |
Clone this URL. |
|
321 | ||
322 |
=head2 ihost |
|
323 | ||
324 |
my $ihost = $url->ihost; |
|
325 |
$url = $url->ihost('xn--bcher-kva.ch'); |
|
326 | ||
327 |
Host part of this URL in punycode format. |
|
328 | ||
329 |
# "xn--da5b0n.net" |
|
330 |
Mojo::URL->new('http://☃.net')->ihost; |
|
331 | ||
332 |
=head2 is_abs |
|
333 | ||
334 |
my $bool = $url->is_abs; |
|
335 | ||
336 |
Check if URL is absolute. |
|
337 | ||
338 |
=head2 parse |
|
339 | ||
340 |
$url = $url->parse('http://127.0.0.1:3000/foo/bar?fo=o&baz=23#foo'); |
|
341 | ||
342 |
Parse relative or absolute URL. |
|
343 | ||
344 |
# "/test/123" |
|
345 |
$url->parse('/test/123?foo=bar')->path; |
|
346 | ||
347 |
# "example.com" |
|
348 |
$url->parse('http://example.com/test/123?foo=bar')->host; |
|
349 | ||
350 |
# "sri@example.com" |
|
351 |
$url->parse('mailto:sri@example.com')->path; |
|
352 | ||
353 |
=head2 path |
|
354 | ||
355 |
my $path = $url->path; |
|
356 |
$url = $url->path('/foo/bar'); |
|
357 |
$url = $url->path('foo/bar'); |
|
358 |
$url = $url->path(Mojo::Path->new); |
|
359 | ||
360 |
Path part of this URL, relative paths will be merged with the existing path, |
|
361 |
defaults to a L<Mojo::Path> object. |
|
362 | ||
363 |
# "http://example.com/DOM/HTML" |
|
364 |
Mojo::URL->new('http://example.com/perldoc/Mojo')->path('/DOM/HTML'); |
|
365 | ||
366 |
# "http://example.com/perldoc/DOM/HTML" |
|
367 |
Mojo::URL->new('http://example.com/perldoc/Mojo')->path('DOM/HTML'); |
|
368 | ||
369 |
# "http://example.com/perldoc/Mojo/DOM/HTML" |
|
370 |
Mojo::URL->new('http://example.com/perldoc/Mojo/')->path('DOM/HTML'); |
|
371 | ||
372 |
=head2 protocol |
|
373 | ||
374 |
my $proto = $url->protocol; |
|
375 | ||
376 |
Normalized version of L</"scheme">. |
|
377 | ||
378 |
# "http" |
|
379 |
Mojo::URL->new('HtTp://example.com')->protocol; |
|
380 | ||
381 |
=head2 query |
|
382 | ||
383 |
my $query = $url->query; |
|
384 |
$url = $url->query(replace => 'with'); |
|
385 |
$url = $url->query([merge => 'with']); |
|
386 |
$url = $url->query({append => 'to'}); |
|
387 |
$url = $url->query(Mojo::Parameters->new); |
|
388 | ||
389 |
Query part of this URL, pairs in an array will be merged and pairs in a hash |
|
390 |
appended, defaults to a L<Mojo::Parameters> object. |
|
391 | ||
392 |
# "2" |
|
393 |
Mojo::URL->new('http://example.com?a=1&b=2')->query->param('b'); |
|
394 | ||
395 |
# "http://example.com?a=2&c=3" |
|
396 |
Mojo::URL->new('http://example.com?a=1&b=2')->query(a => 2, c => 3); |
|
397 | ||
398 |
# "http://example.com?a=2&a=3" |
|
399 |
Mojo::URL->new('http://example.com?a=1&b=2')->query(a => [2, 3]); |
|
400 | ||
401 |
# "http://example.com?a=2&b=2&c=3" |
|
402 |
Mojo::URL->new('http://example.com?a=1&b=2')->query([a => 2, c => 3]); |
|
403 | ||
404 |
# "http://example.com?b=2" |
|
405 |
Mojo::URL->new('http://example.com?a=1&b=2')->query([a => undef]); |
|
406 | ||
407 |
# "http://example.com?a=1&b=2&a=2&c=3" |
|
408 |
Mojo::URL->new('http://example.com?a=1&b=2')->query({a => 2, c => 3}); |
|
409 | ||
410 |
=head2 to_abs |
|
411 | ||
412 |
my $abs = $url->to_abs; |
|
413 |
my $abs = $url->to_abs(Mojo::URL->new('http://example.com/foo')); |
|
414 | ||
415 |
Clone relative URL and turn it into an absolute one using L</"base"> or |
|
416 |
provided base URL. |
|
417 | ||
418 |
# "http://example.com/foo/baz.xml?test=123" |
|
419 |
Mojo::URL->new('baz.xml?test=123') |
|
420 |
->to_abs(Mojo::URL->new('http://example.com/foo/bar.html')); |
|
421 | ||
422 |
# "http://example.com/baz.xml?test=123" |
|
423 |
Mojo::URL->new('/baz.xml?test=123') |
|
424 |
->to_abs(Mojo::URL->new('http://example.com/foo/bar.html')); |
|
425 | ||
426 |
# "http://example.com/foo/baz.xml?test=123" |
|
427 |
Mojo::URL->new('//example.com/foo/baz.xml?test=123') |
|
428 |
->to_abs(Mojo::URL->new('http://example.com/foo/bar.html')); |
|
429 | ||
430 |
=head2 to_rel |
|
431 | ||
432 |
my $rel = $url->to_rel; |
|
433 |
my $rel = $url->to_rel(Mojo::URL->new('http://example.com/foo')); |
|
434 | ||
435 |
Clone absolute URL and turn it into a relative one using L</"base"> or |
|
436 |
provided base URL. |
|
437 | ||
438 |
# "foo/bar.html?test=123" |
|
439 |
Mojo::URL->new('http://example.com/foo/bar.html?test=123') |
|
440 |
->to_rel(Mojo::URL->new('http://example.com')); |
|
441 | ||
442 |
# "bar.html?test=123" |
|
443 |
Mojo::URL->new('http://example.com/foo/bar.html?test=123') |
|
444 |
->to_rel(Mojo::URL->new('http://example.com/foo/')); |
|
445 | ||
446 |
# "//example.com/foo/bar.html?test=123" |
|
447 |
Mojo::URL->new('http://example.com/foo/bar.html?test=123') |
|
448 |
->to_rel(Mojo::URL->new('http://')); |
|
449 | ||
450 |
=head2 to_string |
|
451 | ||
452 |
my $str = $url->to_string; |
|
453 |
my $str = "$url"; |
|
454 | ||
455 |
Turn URL into a string. |
|
456 | ||
457 |
=head1 SEE ALSO |
|
458 | ||
459 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
460 | ||
461 |
=cut |