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   67094 use Mojo::Base -base;
  61         141  
  61         462  
3 61     61   624 use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
  61     1666   144  
  61     2332   748  
  2743         10999  
  1358         58510  
4              
5 61     61   26015 use Mojo::Parameters;
  61         192  
  61         403  
6 61     61   11722 use Mojo::Path;
  61         190  
  61         468  
7 61     61   496 use Mojo::Util qw(decode encode punycode_decode punycode_encode url_escape url_unescape);
  61         165  
  61         144330  
8              
9             has base => sub { Mojo::URL->new };
10             has [qw(fragment host port scheme userinfo)];
11              
12             sub clone {
13 1133     1133 1 1850 my $self = shift;
14 1133         2186 my $clone = $self->new;
15 1133         5999 @$clone{keys %$self} = values %$self;
16 1133   66     5642 $clone->{$_} && ($clone->{$_} = $clone->{$_}->clone) for qw(base path query);
17 1133         2934 return $clone;
18             }
19              
20             sub host_port {
21 4770     4770 1 9552 my ($self, $host_port) = @_;
22              
23 4770 100       10017 if (defined $host_port) {
24 2383 100       17312 $self->port($1) if $host_port =~ s/:(\d+)$//;
25 2383         8016 my $host = url_unescape $host_port;
26 2383 100       10666 return $host =~ /[^\x00-\x7f]/ ? $self->ihost($host) : $self->host($host);
27             }
28              
29 2387 100       4932 return undef unless defined(my $host = $self->ihost);
30 1355 100       3827 return $host unless defined(my $port = $self->port);
31 1023         4728 return "$host:$port";
32             }
33              
34             sub ihost {
35 4877     4877 1 7563 my $self = shift;
36              
37             # Decode
38 4877 100       14138 return $self->host(join '.', map { /^xn--(.+)$/ ? punycode_decode $1 : $_ } split(/\./, shift, -1)) if @_;
  45 100       190  
39              
40             # Check if host needs to be encoded
41 4859 100       10856 return undef unless defined(my $host = $self->host);
42 3827 100       19046 return $host unless $host =~ /[^\x00-\x7f]/;
43              
44             # Encode
45 32 100       166 return join '.', map { /[^\x00-\x7f]/ ? ('xn--' . punycode_encode $_) : $_ } split(/\./, $host, -1);
  82         445  
46             }
47              
48 1351     1351 1 3844 sub is_abs { !!shift->scheme }
49              
50 6191 100   6191 1 123259 sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
51              
52             sub parse {
53 2712     2712 1 6113 my ($self, $url) = @_;
54              
55             # Official regex from RFC 3986
56 2712         15604 $url =~ m!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!;
57 2712 100       11137 $self->scheme($2) if defined $2;
58 2712 50       11034 $self->path($5) if defined $5;
59 2712 100       7407 $self->query($7) if defined $7;
60 2712 100       6548 $self->fragment(_decode(url_unescape $9)) if defined $9;
61 2712 100       8101 if (defined(my $auth = $4)) {
62 1403 100       5130 $self->userinfo(_decode(url_unescape $1)) if $auth =~ s/^([^\@]+)\@//;
63 1403         3587 $self->host_port($auth);
64             }
65              
66 2712         7866 return $self;
67             }
68              
69 6 100 100 6 1 24 sub password { (shift->userinfo // '') =~ /:(.*)$/ ? $1 : undef }
70              
71             sub path {
72 10426     10426 1 16856 my $self = shift;
73              
74             # Old path
75 10426   66     43793 $self->{path} ||= Mojo::Path->new;
76 10426 100       34760 return $self->{path} unless @_;
77              
78             # New path
79 3814 100       15649 $self->{path} = ref $_[0] ? $_[0] : $self->{path}->merge($_[0]);
80              
81 3814         8919 return $self;
82             }
83              
84             sub path_query {
85 3427     3427 1 7112 my ($self, $pq) = @_;
86              
87 3427 100       7493 if (defined $pq) {
88 1013 50       5160 return $self unless $pq =~ /^([^?#]*)(?:\?([^#]*))?/;
89 1013 100       4039 return defined $2 ? $self->path($1)->query($2) : $self->path($1);
90             }
91              
92 2414         5089 my $query = $self->query->to_string;
93 2414 100       5770 return $self->path->to_string . (length $query ? "?$query" : '');
94             }
95              
96 4851   100 4851 1 11975 sub protocol { lc(shift->scheme // '') }
97              
98             sub query {
99 4239     4239 1 6868 my $self = shift;
100              
101             # Old parameters
102 4239   66     20303 my $q = $self->{query} ||= Mojo::Parameters->new;
103 4239 100       17678 return $q unless @_;
104              
105             # Replace with list
106 308 100       1741 if (@_ > 1) { $q->pairs([])->parse(@_) }
  3 100       14  
    100          
107              
108             # Merge with hash
109 11         25 elsif (ref $_[0] eq 'HASH') { $q->merge(%{$_[0]}) }
  11         78  
110              
111             # Append array
112 2         7 elsif (ref $_[0] eq 'ARRAY') { $q->append(@{$_[0]}) }
  2         10  
113              
114             # New parameters
115 292 100       1185 else { $self->{query} = ref $_[0] ? $_[0] : $q->parse($_[0]) }
116              
117 308         829 return $self;
118             }
119              
120             sub to_abs {
121 325     325 1 749 my $self = shift;
122              
123 325         895 my $abs = $self->clone;
124 325 100       881 return $abs if $abs->is_abs;
125              
126             # Scheme
127 261   66     1052 my $base = shift || $abs->base;
128 261         678 $abs->base($base)->scheme($base->scheme);
129              
130             # Authority
131 261 100       690 return $abs if $abs->host;
132 255         743 $abs->userinfo($base->userinfo)->host($base->host)->port($base->port);
133              
134             # Absolute path
135 255         674 my $path = $abs->path;
136 255 100       789 return $abs if $path->leading_slash;
137              
138             # Inherit path
139 52 100       91 if (!@{$path->parts}) {
  52         161  
140 10         32 $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         148 else { $abs->path($base->path->clone->merge($path)->canonicalize) }
148              
149 52         384 return $abs;
150             }
151              
152 1439     1439 1 3625 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 23 sub username { (shift->userinfo // '') =~ /^([^:]+)/ ? $1 : undef }
156              
157 56   66 56   184 sub _decode { decode('UTF-8', $_[0]) // $_[0] }
158              
159 475     475   1454 sub _encode { url_escape encode('UTF-8', $_[0]), $_[1] }
160              
161             sub _string {
162 1442     1442   2841 my ($self, $unsafe) = @_;
163              
164             # Scheme
165 1442         2256 my $url = '';
166 1442 100       3015 if (my $proto = $self->protocol) { $url .= "$proto:" }
  416         1140  
167              
168             # Authority
169 1442         3705 my $auth = $self->host_port;
170 1442 100       3806 $auth = _encode($auth, '^A-Za-z0-9\-._~!$&\'()*+,;=:\[\]') if defined $auth;
171 1442 100 66     3728 if ($unsafe && defined(my $info = $self->userinfo)) {
172 3         10 $auth = _encode($info, '^A-Za-z0-9\-._~!$&\'()*+,;=:') . '@' . $auth;
173             }
174 1442 100       3299 $url .= "//$auth" if defined $auth;
175              
176             # Path and query
177 1442         2913 my $path = $self->path_query;
178 1442 100 100     7375 $url .= !$auth || !length $path || $path =~ m!^[/?]! ? $path : "/$path";
179              
180             # Fragment
181 1442 100       4188 return $url unless defined(my $fragment = $self->fragment);
182 62         175 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