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 65     65   90481 use Mojo::Base -base;
  65         123  
  65         520  
3 65     65   2012 use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
  65     1863   4376  
  65     1511   698  
  2332         10840  
  1518         110654  
4              
5 65     65   31186 use Mojo::Parameters;
  65         169  
  65         434  
6 65     65   12608 use Mojo::Path;
  65         148  
  65         512  
7 65     65   389 use Mojo::Util qw(decode encode punycode_decode punycode_encode url_escape url_unescape);
  65         127  
  65         199788  
8              
9             has base => sub { Mojo::URL->new };
10             has [qw(fragment host port scheme userinfo)];
11              
12             sub clone {
13 1281     1281 1 2115 my $self = shift;
14 1281         2963 my $clone = $self->new;
15 1281         8842 @$clone{keys %$self} = values %$self;
16 1281   66     7717 $clone->{$_} && ($clone->{$_} = $clone->{$_}->clone) for qw(base path query);
17 1281         4105 return $clone;
18             }
19              
20             sub host_port {
21 5174     5174 1 12737 my ($self, $host_port) = @_;
22              
23 5174 100       12193 if (defined $host_port) {
24 2544 100       21359 $self->port($1) if $host_port =~ s/:(\d+)$//;
25 2544         10431 my $host = url_unescape $host_port;
26 2544 100       12900 return $host =~ /[^\x00-\x7f]/ ? $self->ihost($host) : $self->host($host);
27             }
28              
29 2630 100       6950 return undef unless defined(my $host = $self->ihost);
30 1382 100       4516 return $host unless defined(my $port = $self->port);
31 1098         6288 return "$host:$port";
32             }
33              
34             sub ihost {
35 5245     5245 1 10136 my $self = shift;
36              
37             # Decode
38 5245 100       11432 return $self->host(join '.', map { /^xn--(.+)$/ ? punycode_decode $1 : $_ } split(/\./, shift, -1)) if @_;
  45 100       199  
39              
40             # Check if host needs to be encoded
41 5227 100       13604 return undef unless defined(my $host = $self->host);
42 3979 100       22051 return $host unless $host =~ /[^\x00-\x7f]/;
43              
44             # Encode
45 32 100       174 return join '.', map { /[^\x00-\x7f]/ ? ('xn--' . punycode_encode $_) : $_ } split(/\./, $host, -1);
  82         625  
46             }
47              
48 1442     1442 1 4819 sub is_abs { !!shift->scheme }
49              
50 6766 100   6766 1 463318 sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
51              
52             sub parse {
53 2977     2977 1 7284 my ($self, $url) = @_;
54              
55             # Official regex from RFC 3986
56 2977         21734 $url =~ m!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!;
57 2977 100       14419 $self->scheme($2) if defined $2;
58 2977 50       14949 $self->path($5) if defined $5;
59 2977 100       8828 $self->query($7) if defined $7;
60 2977 100       7876 $self->fragment(_decode(url_unescape $9)) if defined $9;
61 2977 100       11134 if (defined(my $auth = $4)) {
62 1486 100       5609 $self->userinfo(_decode(url_unescape $1)) if $auth =~ s/^([^\@]+)\@//;
63 1486         4614 $self->host_port($auth);
64             }
65              
66 2977         10234 return $self;
67             }
68              
69 6 100 100 6 1 33 sub password { (shift->userinfo // '') =~ /:(.*)$/ ? $1 : undef }
70              
71             sub path {
72 11394     11394 1 19999 my $self = shift;
73              
74             # Old path
75 11394   66     58817 $self->{path} ||= Mojo::Path->new;
76 11394 100       42229 return $self->{path} unless @_;
77              
78             # New path
79 4158 100       19504 $self->{path} = ref $_[0] ? $_[0] : $self->{path}->merge($_[0]);
80              
81 4158         11156 return $self;
82             }
83              
84             sub path_query {
85 3756     3756 1 8714 my ($self, $pq) = @_;
86              
87 3756 100       9006 if (defined $pq) {
88 1091 50       6497 return $self unless $pq =~ /^([^?#]*)(?:\?([^#]*))?/;
89 1091 100       6453 return defined $2 ? $self->path($1)->query($2) : $self->path($1);
90             }
91              
92 2665         7359 my $query = $self->query->to_string;
93 2665 100       8015 return $self->path->to_string . (length $query ? "?$query" : '');
94             }
95              
96 5318   100 5318 1 15280 sub protocol { lc(shift->scheme // '') }
97              
98             sub query {
99 4597     4597 1 18071 my $self = shift;
100              
101             # Old parameters
102 4597   66     29811 my $q = $self->{query} ||= Mojo::Parameters->new;
103 4597 100       21128 return $q unless @_;
104              
105             # Replace with list
106 304 100       2029 if (@_ > 1) { $q->pairs([])->parse(@_) }
  3 100       14  
    100          
107              
108             # Merge with hash
109 11         22 elsif (ref $_[0] eq 'HASH') { $q->merge(%{$_[0]}) }
  11         56  
110              
111             # Append array
112 2         6 elsif (ref $_[0] eq 'ARRAY') { $q->append(@{$_[0]}) }
  2         13  
113              
114             # New parameters
115 288 100       1544 else { $self->{query} = ref $_[0] ? $_[0] : $q->parse($_[0]) }
116              
117 304         1040 return $self;
118             }
119              
120             sub to_abs {
121 334     334 1 769 my $self = shift;
122              
123 334         1226 my $abs = $self->clone;
124 334 100       998 return $abs if $abs->is_abs;
125              
126             # Scheme
127 266   66     1158 my $base = shift || $abs->base;
128 266         756 $abs->base($base)->scheme($base->scheme);
129              
130             # Authority
131 266 100       757 return $abs if $abs->host;
132 260         784 $abs->userinfo($base->userinfo)->host($base->host)->port($base->port);
133              
134             # Absolute path
135 260         696 my $path = $abs->path;
136 260 100       877 return $abs if $path->leading_slash;
137              
138             # Inherit path
139 53 100       92 if (!@{$path->parts}) {
  53         151  
140 10         30 $abs->path($base->path->clone->canonicalize);
141              
142             # Query
143 10 100       177 $abs->query($base->query->clone) unless length $abs->query->to_string;
144             }
145              
146             # Merge paths
147 43         108 else { $abs->path($base->path->clone->merge($path)->canonicalize) }
148              
149 53         336 return $abs;
150             }
151              
152 1612     1612 1 4825 sub to_string { shift->_string(0) }
153 3     3 1 16 sub to_unsafe_string { shift->_string(1) }
154              
155 6 100 100 6 1 30 sub username { (shift->userinfo // '') =~ /^([^:]+)/ ? $1 : undef }
156              
157 56   66 56   190 sub _decode { decode('UTF-8', $_[0]) // $_[0] }
158              
159 433     433   1803 sub _encode { url_escape encode('UTF-8', $_[0]), $_[1] }
160              
161             sub _string {
162 1615     1615   3704 my ($self, $unsafe) = @_;
163              
164             # Scheme
165 1615         3008 my $url = '';
166 1615 100       4325 if (my $proto = $self->protocol) { $url .= "$proto:" }
  374         946  
167              
168             # Authority
169 1615         4594 my $auth = $self->host_port;
170 1615 100       4609 $auth = _encode($auth, '^A-Za-z0-9\-._~!$&\'()*+,;=:\[\]') if defined $auth;
171 1615 100 66     4612 if ($unsafe && defined(my $info = $self->userinfo)) {
172 3         8 $auth = _encode($info, '^A-Za-z0-9\-._~!$&\'()*+,;=:') . '@' . $auth;
173             }
174 1615 100       3716 $url .= "//$auth" if defined $auth;
175              
176             # Path and query
177 1615         3983 my $path = $self->path_query;
178 1615 100 100     9162 $url .= !$auth || !length $path || $path =~ m!^[/?]! ? $path : "/$path";
179              
180             # Fragment
181 1615 100       5161 return $url unless defined(my $fragment = $self->fragment);
182 62         151 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