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   93793 use Mojo::Base -base;
  65         146  
  65         495  
3 65     65   1919 use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
  65     1984   3888  
  65     1632   783  
  2332         12199  
  1518         101171  
4              
5 65     65   33707 use Mojo::Parameters;
  65         207  
  65         493  
6 65     65   12730 use Mojo::Path;
  65         206  
  65         461  
7 65     65   425 use Mojo::Util qw(decode encode punycode_decode punycode_encode url_escape url_unescape);
  65         136  
  65         204234  
8              
9             has base => sub { Mojo::URL->new };
10             has [qw(fragment host port scheme userinfo)];
11              
12             sub clone {
13 1281     1281 1 2314 my $self = shift;
14 1281         2991 my $clone = $self->new;
15 1281         8673 @$clone{keys %$self} = values %$self;
16 1281   66     7270 $clone->{$_} && ($clone->{$_} = $clone->{$_}->clone) for qw(base path query);
17 1281         3819 return $clone;
18             }
19              
20             sub host_port {
21 5174     5174 1 15593 my ($self, $host_port) = @_;
22              
23 5174 100       12439 if (defined $host_port) {
24 2544 100       23205 $self->port($1) if $host_port =~ s/:(\d+)$//;
25 2544         11457 my $host = url_unescape $host_port;
26 2544 100       14351 return $host =~ /[^\x00-\x7f]/ ? $self->ihost($host) : $self->host($host);
27             }
28              
29 2630 100       7576 return undef unless defined(my $host = $self->ihost);
30 1382 100       4405 return $host unless defined(my $port = $self->port);
31 1098         6976 return "$host:$port";
32             }
33              
34             sub ihost {
35 5245     5245 1 9709 my $self = shift;
36              
37             # Decode
38 5245 100       12535 return $self->host(join '.', map { /^xn--(.+)$/ ? punycode_decode $1 : $_ } split(/\./, shift, -1)) if @_;
  45 100       293  
39              
40             # Check if host needs to be encoded
41 5227 100       14764 return undef unless defined(my $host = $self->host);
42 3979 100       23777 return $host unless $host =~ /[^\x00-\x7f]/;
43              
44             # Encode
45 32 100       199 return join '.', map { /[^\x00-\x7f]/ ? ('xn--' . punycode_encode $_) : $_ } split(/\./, $host, -1);
  82         546  
46             }
47              
48 1442     1442 1 6635 sub is_abs { !!shift->scheme }
49              
50 6766 100   6766 1 498591 sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
51              
52             sub parse {
53 2977     2977 1 7609 my ($self, $url) = @_;
54              
55             # Official regex from RFC 3986
56 2977         21903 $url =~ m!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!;
57 2977 100       14887 $self->scheme($2) if defined $2;
58 2977 50       17268 $self->path($5) if defined $5;
59 2977 100       9878 $self->query($7) if defined $7;
60 2977 100       9157 $self->fragment(_decode(url_unescape $9)) if defined $9;
61 2977 100       11965 if (defined(my $auth = $4)) {
62 1486 100       7881 $self->userinfo(_decode(url_unescape $1)) if $auth =~ s/^([^\@]+)\@//;
63 1486         6338 $self->host_port($auth);
64             }
65              
66 2977         11993 return $self;
67             }
68              
69 6 100 100 6 1 30 sub password { (shift->userinfo // '') =~ /:(.*)$/ ? $1 : undef }
70              
71             sub path {
72 11394     11394 1 21961 my $self = shift;
73              
74             # Old path
75 11394   66     63350 $self->{path} ||= Mojo::Path->new;
76 11394 100       47233 return $self->{path} unless @_;
77              
78             # New path
79 4158 100       21438 $self->{path} = ref $_[0] ? $_[0] : $self->{path}->merge($_[0]);
80              
81 4158         12001 return $self;
82             }
83              
84             sub path_query {
85 3756     3756 1 9542 my ($self, $pq) = @_;
86              
87 3756 100       10076 if (defined $pq) {
88 1091 50       8675 return $self unless $pq =~ /^([^?#]*)(?:\?([^#]*))?/;
89 1091 100       5181 return defined $2 ? $self->path($1)->query($2) : $self->path($1);
90             }
91              
92 2665         8483 my $query = $self->query->to_string;
93 2665 100       8131 return $self->path->to_string . (length $query ? "?$query" : '');
94             }
95              
96 5318   100 5318 1 20221 sub protocol { lc(shift->scheme // '') }
97              
98             sub query {
99 4597     4597 1 8505 my $self = shift;
100              
101             # Old parameters
102 4597   66     32482 my $q = $self->{query} ||= Mojo::Parameters->new;
103 4597 100       29789 return $q unless @_;
104              
105             # Replace with list
106 304 100       1851 if (@_ > 1) { $q->pairs([])->parse(@_) }
  3 100       18  
    100          
107              
108             # Merge with hash
109 11         24 elsif (ref $_[0] eq 'HASH') { $q->merge(%{$_[0]}) }
  11         67  
110              
111             # Append array
112 2         5 elsif (ref $_[0] eq 'ARRAY') { $q->append(@{$_[0]}) }
  2         13  
113              
114             # New parameters
115 288 100       1477 else { $self->{query} = ref $_[0] ? $_[0] : $q->parse($_[0]) }
116              
117 304         1150 return $self;
118             }
119              
120             sub to_abs {
121 334     334 1 688 my $self = shift;
122              
123 334         1153 my $abs = $self->clone;
124 334 100       1101 return $abs if $abs->is_abs;
125              
126             # Scheme
127 266   66     1238 my $base = shift || $abs->base;
128 266         716 $abs->base($base)->scheme($base->scheme);
129              
130             # Authority
131 266 100       739 return $abs if $abs->host;
132 260         855 $abs->userinfo($base->userinfo)->host($base->host)->port($base->port);
133              
134             # Absolute path
135 260         713 my $path = $abs->path;
136 260 100       822 return $abs if $path->leading_slash;
137              
138             # Inherit path
139 53 100       90 if (!@{$path->parts}) {
  53         159  
140 10         28 $abs->path($base->path->clone->canonicalize);
141              
142             # Query
143 10 100       28 $abs->query($base->query->clone) unless length $abs->query->to_string;
144             }
145              
146             # Merge paths
147 43         127 else { $abs->path($base->path->clone->merge($path)->canonicalize) }
148              
149 53         368 return $abs;
150             }
151              
152 1612     1612 1 4870 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 33 sub username { (shift->userinfo // '') =~ /^([^:]+)/ ? $1 : undef }
156              
157 56   66 56   219 sub _decode { decode('UTF-8', $_[0]) // $_[0] }
158              
159 433     433   1714 sub _encode { url_escape encode('UTF-8', $_[0]), $_[1] }
160              
161             sub _string {
162 1615     1615   3564 my ($self, $unsafe) = @_;
163              
164             # Scheme
165 1615         2863 my $url = '';
166 1615 100       4286 if (my $proto = $self->protocol) { $url .= "$proto:" }
  374         936  
167              
168             # Authority
169 1615         4700 my $auth = $self->host_port;
170 1615 100       4448 $auth = _encode($auth, '^A-Za-z0-9\-._~!$&\'()*+,;=:\[\]') if defined $auth;
171 1615 100 66     7691 if ($unsafe && defined(my $info = $self->userinfo)) {
172 3         9 $auth = _encode($info, '^A-Za-z0-9\-._~!$&\'()*+,;=:') . '@' . $auth;
173             }
174 1615 100       3864 $url .= "//$auth" if defined $auth;
175              
176             # Path and query
177 1615         4072 my $path = $self->path_query;
178 1615 100 100     9865 $url .= !$auth || !length $path || $path =~ m!^[/?]! ? $path : "/$path";
179              
180             # Fragment
181 1615 100       5356 return $url unless defined(my $fragment = $self->fragment);
182 62         161 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