File Coverage

blib/lib/Mojo/URL.pm
Criterion Covered Total %
statement 106 106 100.0
branch 80 82 97.5
condition 21 27 77.7
subroutine 25 25 100.0
pod 15 15 100.0
total 247 255 96.8


line stmt bran cond sub pod time code
1             package Mojo::URL;
2 61     61   62941 use Mojo::Base -base;
  61         150  
  61         441  
3 61     61   572 use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
  61     2130   146  
  61     1482   674  
  2743         10797  
  1358         57619  
4              
5 61     61   24967 use Mojo::Parameters;
  61         230  
  61         445  
6 61     61   10486 use Mojo::Path;
  61         180  
  61         439  
7 61     61   443 use Mojo::Util qw(decode encode punycode_decode punycode_encode url_escape url_unescape);
  61         139  
  61         139507  
8              
9             has base => sub { Mojo::URL->new };
10             has [qw(fragment host port scheme userinfo)];
11              
12             sub clone {
13 1133     1133 1 1855 my $self = shift;
14 1133         2039 my $clone = $self->new;
15 1133         6022 @$clone{keys %$self} = values %$self;
16 1133   66     5457 $clone->{$_} && ($clone->{$_} = $clone->{$_}->clone) for qw(base path query);
17 1133         2920 return $clone;
18             }
19              
20             sub host_port {
21 4770     4770 1 9630 my ($self, $host_port) = @_;
22              
23 4770 100       10281 if (defined $host_port) {
24 2383 100       16767 $self->port($1) if $host_port =~ s/:(\d+)$//;
25 2383         7481 my $host = url_unescape $host_port;
26 2383 100       10287 return $host =~ /[^\x00-\x7f]/ ? $self->ihost($host) : $self->host($host);
27             }
28              
29 2387 100       4781 return undef unless defined(my $host = $self->ihost);
30 1355 100       3527 return $host unless defined(my $port = $self->port);
31 1023         4622 return "$host:$port";
32             }
33              
34             sub ihost {
35 4877     4877 1 7452 my $self = shift;
36              
37             # Decode
38 4877 100       10098 return $self->host(join '.', map { /^xn--(.+)$/ ? punycode_decode $1 : $_ } split(/\./, shift, -1)) if @_;
  45 100       214  
39              
40             # Check if host needs to be encoded
41 4859 100       10585 return undef unless defined(my $host = $self->host);
42 3827 100       18526 return $host unless $host =~ /[^\x00-\x7f]/;
43              
44             # Encode
45 32 100       134 return join '.', map { /[^\x00-\x7f]/ ? ('xn--' . punycode_encode $_) : $_ } split(/\./, $host, -1);
  82         437  
46             }
47              
48 1351     1351 1 3636 sub is_abs { !!shift->scheme }
49              
50 6191 100   6191 1 133702 sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
51              
52             sub parse {
53 2712     2712 1 5702 my ($self, $url) = @_;
54              
55             # Official regex from RFC 3986
56 2712         15300 $url =~ m!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!;
57 2712 100       10651 $self->scheme($2) if defined $2;
58 2712 50       10439 $self->path($5) if defined $5;
59 2712 100       7314 $self->query($7) if defined $7;
60 2712 100       6013 $self->fragment(_decode(url_unescape $9)) if defined $9;
61 2712 100       7638 if (defined(my $auth = $4)) {
62 1403 100       4871 $self->userinfo(_decode(url_unescape $1)) if $auth =~ s/^([^\@]+)\@//;
63 1403         3417 $self->host_port($auth);
64             }
65              
66 2712         7432 return $self;
67             }
68              
69 6 100 100 6 1 20 sub password { (shift->userinfo // '') =~ /:(.*)$/ ? $1 : undef }
70              
71             sub path {
72 10426     10426 1 16358 my $self = shift;
73              
74             # Old path
75 10426   66     42588 $self->{path} ||= Mojo::Path->new;
76 10426 100       32616 return $self->{path} unless @_;
77              
78             # New path
79 3814 100       14907 $self->{path} = ref $_[0] ? $_[0] : $self->{path}->merge($_[0]);
80              
81 3814         8473 return $self;
82             }
83              
84             sub path_query {
85 3427     3427 1 6792 my ($self, $pq) = @_;
86              
87 3427 100       7159 if (defined $pq) {
88 1013 50       5050 return $self unless $pq =~ /^([^?#]*)(?:\?([^#]*))?/;
89 1013 100       3904 return defined $2 ? $self->path($1)->query($2) : $self->path($1);
90             }
91              
92 2414         4988 my $query = $self->query->to_string;
93 2414 100       5688 return $self->path->to_string . (length $query ? "?$query" : '');
94             }
95              
96 4851   100 4851 1 11351 sub protocol { lc(shift->scheme // '') }
97              
98             sub query {
99 4239     4239 1 6666 my $self = shift;
100              
101             # Old parameters
102 4239   66     19660 my $q = $self->{query} ||= Mojo::Parameters->new;
103 4239 100       17404 return $q unless @_;
104              
105             # Replace with list
106 308 100       1720 if (@_ > 1) { $q->pairs([])->parse(@_) }
  3 100       16  
    100          
107              
108             # Merge with hash
109 11         36 elsif (ref $_[0] eq 'HASH') { $q->merge(%{$_[0]}) }
  11         68  
110              
111             # Append array
112 2         5 elsif (ref $_[0] eq 'ARRAY') { $q->append(@{$_[0]}) }
  2         8  
113              
114             # New parameters
115 292 100       1152 else { $self->{query} = ref $_[0] ? $_[0] : $q->parse($_[0]) }
116              
117 308         847 return $self;
118             }
119              
120             sub to_abs {
121 325     325 1 744 my $self = shift;
122              
123 325         783 my $abs = $self->clone;
124 325 100       850 return $abs if $abs->is_abs;
125              
126             # Scheme
127 261   66     1051 my $base = shift || $abs->base;
128 261         693 $abs->base($base)->scheme($base->scheme);
129              
130             # Authority
131 261 100       719 return $abs if $abs->host;
132 255         696 $abs->userinfo($base->userinfo)->host($base->host)->port($base->port);
133              
134             # Absolute path
135 255         681 my $path = $abs->path;
136 255 100       800 return $abs if $path->leading_slash;
137              
138             # Inherit path
139 52 100       107 if (!@{$path->parts}) {
  52         147  
140 10         40 $abs->path($base->path->clone->canonicalize);
141              
142             # Query
143 10 100       33 $abs->query($base->query->clone) unless length $abs->query->to_string;
144             }
145              
146             # Merge paths
147 42         117 else { $abs->path($base->path->clone->merge($path)->canonicalize) }
148              
149 52         327 return $abs;
150             }
151              
152 1439     1439 1 3775 sub to_string { shift->_string(0) }
153 3     3 1 12 sub to_unsafe_string { shift->_string(1) }
154              
155 6 100 100 6 1 20 sub username { (shift->userinfo // '') =~ /^([^:]+)/ ? $1 : undef }
156              
157 56   66 56   162 sub _decode { decode('UTF-8', $_[0]) // $_[0] }
158              
159 475     475   1384 sub _encode { url_escape encode('UTF-8', $_[0]), $_[1] }
160              
161             sub _string {
162 1442     1442   2709 my ($self, $unsafe) = @_;
163              
164             # Scheme
165 1442         2156 my $url = '';
166 1442 100       2870 if (my $proto = $self->protocol) { $url .= "$proto:" }
  416         1404  
167              
168             # Authority
169 1442         3430 my $auth = $self->host_port;
170 1442 100       3607 $auth = _encode($auth, '^A-Za-z0-9\-._~!$&\'()*+,;=:\[\]') if defined $auth;
171 1442 100 66     3474 if ($unsafe && defined(my $info = $self->userinfo)) {
172 3         11 $auth = _encode($info, '^A-Za-z0-9\-._~!$&\'()*+,;=:') . '@' . $auth;
173             }
174 1442 100       3091 $url .= "//$auth" if defined $auth;
175              
176             # Path and query
177 1442         2862 my $path = $self->path_query;
178 1442 100 100     7089 $url .= !$auth || !length $path || $path =~ m!^[/?]! ? $path : "/$path";
179              
180             # Fragment
181 1442 100       3918 return $url unless defined(my $fragment = $self->fragment);
182 62         167 return $url . '#' . _encode($fragment, '^A-Za-z0-9\-._~!$&\'()*+,;=:@/?');
183             }
184              
185             1;
186              
187             =encoding utf8
188              
189             =head1 NAME
190              
191             Mojo::URL - Uniform Resource Locator
192              
193             =head1 SYNOPSIS
194              
195             use Mojo::URL;
196              
197             # Parse
198             my $url = Mojo::URL->new('http://sri:foo@example.com:3000/foo?foo=bar#23');
199             say $url->scheme;
200             say $url->userinfo;
201             say $url->host;
202             say $url->port;
203             say $url->path;
204             say $url->query;
205             say $url->fragment;
206              
207             # Build
208             my $url = Mojo::URL->new;
209             $url->scheme('http');
210             $url->host('example.com');
211             $url->port(3000);
212             $url->path('/foo/bar');
213             $url->query(foo => 'bar');
214             $url->fragment(23);
215             say "$url";
216              
217             =head1 DESCRIPTION
218              
219             L implements a subset of L, L
220             3987|https://tools.ietf.org/html/rfc3987> and the L for Uniform
221             Resource Locators with support for IDNA and IRIs.
222              
223             =head1 ATTRIBUTES
224              
225             L implements the following attributes.
226              
227             =head2 base
228              
229             my $base = $url->base;
230             $url = $url->base(Mojo::URL->new);
231              
232             Base of this URL, defaults to a L object.
233              
234             "http://example.com/a/b?c"
235             Mojo::URL->new("/a/b?c")->base(Mojo::URL->new("http://example.com"))->to_abs;
236              
237             =head2 fragment
238              
239             my $fragment = $url->fragment;
240             $url = $url->fragment('♥mojolicious♥');
241              
242             Fragment part of this URL.
243              
244             # "yada"
245             Mojo::URL->new('http://example.com/foo?bar=baz#yada')->fragment;
246              
247             =head2 host
248              
249             my $host = $url->host;
250             $url = $url->host('127.0.0.1');
251              
252             Host part of this URL.
253              
254             # "example.com"
255             Mojo::URL->new('http://sri:t3st@example.com:8080/foo')->host;
256              
257             =head2 port
258              
259             my $port = $url->port;
260             $url = $url->port(8080);
261              
262             Port part of this URL.
263              
264             # "8080"
265             Mojo::URL->new('http://sri:t3st@example.com:8080/foo')->port;
266              
267             =head2 scheme
268              
269             my $scheme = $url->scheme;
270             $url = $url->scheme('http');
271              
272             Scheme part of this URL.
273              
274             # "http"
275             Mojo::URL->new('http://example.com/foo')->scheme;
276              
277             =head2 userinfo
278              
279             my $info = $url->userinfo;
280             $url = $url->userinfo('root:♥');
281              
282             Userinfo part of this URL.
283              
284             # "sri:t3st"
285             Mojo::URL->new('https://sri:t3st@example.com/foo')->userinfo;
286              
287             =head1 METHODS
288              
289             L inherits all methods from L and implements the following new ones.
290              
291             =head2 clone
292              
293             my $url2 = $url->clone;
294              
295             Return a new L object cloned from this URL.
296              
297             =head2 host_port
298              
299             my $host_port = $url->host_port;
300             $url = $url->host_port('example.com:8080');
301              
302             Normalized version of L and L.
303              
304             # "xn--n3h.net:8080"
305             Mojo::URL->new('http://☃.net:8080/test')->host_port;
306              
307             # "example.com"
308             Mojo::URL->new('http://example.com/test')->host_port;
309              
310             =head2 ihost
311              
312             my $ihost = $url->ihost;
313             $url = $url->ihost('xn--bcher-kva.ch');
314              
315             Host part of this URL in punycode format.
316              
317             # "xn--n3h.net"
318             Mojo::URL->new('http://☃.net')->ihost;
319              
320             # "example.com"
321             Mojo::URL->new('http://example.com')->ihost;
322              
323             =head2 is_abs
324              
325             my $bool = $url->is_abs;
326              
327             Check if URL is absolute.
328              
329             # True
330             Mojo::URL->new('http://example.com')->is_abs;
331             Mojo::URL->new('http://example.com/test/index.html')->is_abs;
332              
333             # False
334             Mojo::URL->new('test/index.html')->is_abs;
335             Mojo::URL->new('/test/index.html')->is_abs;
336             Mojo::URL->new('//example.com/test/index.html')->is_abs;
337              
338             =head2 new
339              
340             my $url = Mojo::URL->new;
341             my $url = Mojo::URL->new('http://127.0.0.1:3000/foo?f=b&baz=2#foo');
342              
343             Construct a new L object and L URL if necessary.
344              
345             =head2 parse
346              
347             $url = $url->parse('http://127.0.0.1:3000/foo/bar?fo=o&baz=23#foo');
348              
349             Parse relative or absolute URL.
350              
351             # "/test/123"
352             $url->parse('/test/123?foo=bar')->path;
353              
354             # "example.com"
355             $url->parse('http://example.com/test/123?foo=bar')->host;
356              
357             # "sri@example.com"
358             $url->parse('mailto:sri@example.com')->path;
359              
360             =head2 password
361              
362             my $password = $url->password;
363              
364             Password part of L.
365              
366             # "s3cret"
367             Mojo::URL->new('http://isabel:s3cret@mojolicious.org')->password;
368              
369             # "s:3:c:r:e:t"
370             Mojo::URL->new('http://isabel:s:3:c:r:e:t@mojolicious.org')->password;
371              
372             =head2 path
373              
374             my $path = $url->path;
375             $url = $url->path('foo/bar');
376             $url = $url->path('/foo/bar');
377             $url = $url->path(Mojo::Path->new);
378              
379             Path part of this URL, relative paths will be merged with L, defaults to a L object.
380              
381             # "test"
382             Mojo::URL->new('http://example.com/test/Mojo')->path->parts->[0];
383              
384             # "/test/DOM/HTML"
385             Mojo::URL->new('http://example.com/test/Mojo')->path->merge('DOM/HTML');
386              
387             # "http://example.com/DOM/HTML"
388             Mojo::URL->new('http://example.com/test/Mojo')->path('/DOM/HTML');
389              
390             # "http://example.com/test/DOM/HTML"
391             Mojo::URL->new('http://example.com/test/Mojo')->path('DOM/HTML');
392              
393             # "http://example.com/test/Mojo/DOM/HTML"
394             Mojo::URL->new('http://example.com/test/Mojo/')->path('DOM/HTML');
395              
396             =head2 path_query
397              
398             my $path_query = $url->path_query;
399             $url = $url->path_query('/foo/bar?a=1&b=2');
400              
401             Normalized version of L and L.
402              
403             # "/test?a=1&b=2"
404             Mojo::URL->new('http://example.com/test?a=1&b=2')->path_query;
405              
406             # "/"
407             Mojo::URL->new('http://example.com/')->path_query;
408              
409             =head2 protocol
410              
411             my $proto = $url->protocol;
412              
413             Normalized version of L.
414              
415             # "http"
416             Mojo::URL->new('HtTp://example.com')->protocol;
417              
418             =head2 query
419              
420             my $query = $url->query;
421             $url = $url->query({merge => 'to'});
422             $url = $url->query([append => 'with']);
423             $url = $url->query(replace => 'with');
424             $url = $url->query('a=1&b=2');
425             $url = $url->query(Mojo::Parameters->new);
426              
427             Query part of this URL, key/value pairs in an array reference will be appended with L, and
428             key/value pairs in a hash reference merged with L, defaults to a L object.
429              
430             # "2"
431             Mojo::URL->new('http://example.com?a=1&b=2')->query->param('b');
432              
433             # "a=2&b=2&c=3"
434             Mojo::URL->new('http://example.com?a=1&b=2')->query->merge(a => 2, c => 3);
435              
436             # "http://example.com?a=2&c=3"
437             Mojo::URL->new('http://example.com?a=1&b=2')->query(a => 2, c => 3);
438              
439             # "http://example.com?a=2&a=3"
440             Mojo::URL->new('http://example.com?a=1&b=2')->query(a => [2, 3]);
441              
442             # "http://example.com?a=2&b=2&c=3"
443             Mojo::URL->new('http://example.com?a=1&b=2')->query({a => 2, c => 3});
444              
445             # "http://example.com?b=2"
446             Mojo::URL->new('http://example.com?a=1&b=2')->query({a => undef});
447              
448             # "http://example.com?a=1&b=2&a=2&c=3"
449             Mojo::URL->new('http://example.com?a=1&b=2')->query([a => 2, c => 3]);
450              
451             =head2 to_abs
452              
453             my $abs = $url->to_abs;
454             my $abs = $url->to_abs(Mojo::URL->new('http://example.com/foo'));
455              
456             Return a new L object cloned from this relative URL and turn it into an absolute one using L or
457             provided base URL.
458              
459             # "http://example.com/foo/baz.xml?test=123"
460             Mojo::URL->new('baz.xml?test=123')
461             ->to_abs(Mojo::URL->new('http://example.com/foo/bar.html'));
462              
463             # "http://example.com/baz.xml?test=123"
464             Mojo::URL->new('/baz.xml?test=123')
465             ->to_abs(Mojo::URL->new('http://example.com/foo/bar.html'));
466              
467             # "http://example.com/foo/baz.xml?test=123"
468             Mojo::URL->new('//example.com/foo/baz.xml?test=123')
469             ->to_abs(Mojo::URL->new('http://example.com/foo/bar.html'));
470              
471             =head2 to_string
472              
473             my $str = $url->to_string;
474              
475             Turn URL into a string. Note that L will not be included for security reasons.
476              
477             # "http://mojolicious.org"
478             Mojo::URL->new->scheme('http')->host('mojolicious.org')->to_string;
479              
480             # "http://mojolicious.org"
481             Mojo::URL->new('http://daniel:s3cret@mojolicious.org')->to_string;
482              
483             =head2 to_unsafe_string
484              
485             my $str = $url->to_unsafe_string;
486              
487             Same as L, but includes L.
488              
489             # "http://daniel:s3cret@mojolicious.org"
490             Mojo::URL->new('http://daniel:s3cret@mojolicious.org')->to_unsafe_string;
491              
492             =head2 username
493              
494             my $username = $url->username;
495              
496             Username part of L.
497              
498             # "isabel"
499             Mojo::URL->new('http://isabel:s3cret@mojolicious.org')->username;
500              
501             =head1 OPERATORS
502              
503             L overloads the following operators.
504              
505             =head2 bool
506              
507             my $bool = !!$url;
508              
509             Always true.
510              
511             =head2 stringify
512              
513             my $str = "$url";
514              
515             Alias for L.
516              
517             =head1 SEE ALSO
518              
519             L, L, L.
520              
521             =cut