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 62     62   67234 use Mojo::Base -base;
  62         141  
  62         445  
3 62     62   565 use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
  62     2115   161  
  62     1924   734  
  2814         11016  
  1488         61651  
4              
5 62     62   27370 use Mojo::Parameters;
  62         212  
  62         531  
6 62     62   11082 use Mojo::Path;
  62         188  
  62         404  
7 62     62   412 use Mojo::Util qw(decode encode punycode_decode punycode_encode url_escape url_unescape);
  62         169  
  62         150479  
8              
9             has base => sub { Mojo::URL->new };
10             has [qw(fragment host port scheme userinfo)];
11              
12             sub clone {
13 1230     1230 1 2025 my $self = shift;
14 1230         2374 my $clone = $self->new;
15 1230         6560 @$clone{keys %$self} = values %$self;
16 1230   66     6120 $clone->{$_} && ($clone->{$_} = $clone->{$_}->clone) for qw(base path query);
17 1230         3137 return $clone;
18             }
19              
20             sub host_port {
21 4918     4918 1 9955 my ($self, $host_port) = @_;
22              
23 4918 100       10550 if (defined $host_port) {
24 2381 100       17517 $self->port($1) if $host_port =~ s/:(\d+)$//;
25 2381         7417 my $host = url_unescape $host_port;
26 2381 100       10761 return $host =~ /[^\x00-\x7f]/ ? $self->ihost($host) : $self->host($host);
27             }
28              
29 2537 100       5409 return undef unless defined(my $host = $self->ihost);
30 1315 100       3421 return $host unless defined(my $port = $self->port);
31 1039         4633 return "$host:$port";
32             }
33              
34             sub ihost {
35 5052     5052 1 7946 my $self = shift;
36              
37             # Decode
38 5052 100       10900 return $self->host(join '.', map { /^xn--(.+)$/ ? punycode_decode $1 : $_ } split(/\./, shift, -1)) if @_;
  45 100       222  
39              
40             # Check if host needs to be encoded
41 5034 100       11189 return undef unless defined(my $host = $self->host);
42 3812 100       18419 return $host unless $host =~ /[^\x00-\x7f]/;
43              
44             # Encode
45 32 100       147 return join '.', map { /[^\x00-\x7f]/ ? ('xn--' . punycode_encode $_) : $_ } split(/\./, $host, -1);
  82         448  
46             }
47              
48 1363     1363 1 3735 sub is_abs { !!shift->scheme }
49              
50 6411 100   6411 1 120419 sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
51              
52             sub parse {
53 2807     2807 1 6136 my ($self, $url) = @_;
54              
55             # Official regex from RFC 3986
56 2807         15964 $url =~ m!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!;
57 2807 100       10901 $self->scheme($2) if defined $2;
58 2807 50       11369 $self->path($5) if defined $5;
59 2807 100       7499 $self->query($7) if defined $7;
60 2807 100       6235 $self->fragment(_decode(url_unescape $9)) if defined $9;
61 2807 100       8057 if (defined(my $auth = $4)) {
62 1389 100       5098 $self->userinfo(_decode(url_unescape $1)) if $auth =~ s/^([^\@]+)\@//;
63 1389         3666 $self->host_port($auth);
64             }
65              
66 2807         8107 return $self;
67             }
68              
69 6 100 100 6 1 20 sub password { (shift->userinfo // '') =~ /:(.*)$/ ? $1 : undef }
70              
71             sub path {
72 10908     10908 1 17026 my $self = shift;
73              
74             # Old path
75 10908   66     45498 $self->{path} ||= Mojo::Path->new;
76 10908 100       35982 return $self->{path} unless @_;
77              
78             # New path
79 3921 100       16053 $self->{path} = ref $_[0] ? $_[0] : $self->{path}->merge($_[0]);
80              
81 3921         8876 return $self;
82             }
83              
84             sub path_query {
85 3589     3589 1 7190 my ($self, $pq) = @_;
86              
87 3589 100       7817 if (defined $pq) {
88 1025 50       5129 return $self unless $pq =~ /^([^?#]*)(?:\?([^#]*))?/;
89 1025 100       3971 return defined $2 ? $self->path($1)->query($2) : $self->path($1);
90             }
91              
92 2564         5413 my $query = $self->query->to_string;
93 2564 100       6163 return $self->path->to_string . (length $query ? "?$query" : '');
94             }
95              
96 5028   100 5028 1 12922 sub protocol { lc(shift->scheme // '') }
97              
98             sub query {
99 4402     4402 1 7458 my $self = shift;
100              
101             # Old parameters
102 4402   66     21052 my $q = $self->{query} ||= Mojo::Parameters->new;
103 4402 100       18090 return $q unless @_;
104              
105             # Replace with list
106 308 100       1785 if (@_ > 1) { $q->pairs([])->parse(@_) }
  3 100       22  
    100          
107              
108             # Merge with hash
109 11         34 elsif (ref $_[0] eq 'HASH') { $q->merge(%{$_[0]}) }
  11         80  
110              
111             # Append array
112 2         4 elsif (ref $_[0] eq 'ARRAY') { $q->append(@{$_[0]}) }
  2         9  
113              
114             # New parameters
115 292 100       1296 else { $self->{query} = ref $_[0] ? $_[0] : $q->parse($_[0]) }
116              
117 308         1264 return $self;
118             }
119              
120             sub to_abs {
121 325     325 1 802 my $self = shift;
122              
123 325         887 my $abs = $self->clone;
124 325 100       883 return $abs if $abs->is_abs;
125              
126             # Scheme
127 261   66     1155 my $base = shift || $abs->base;
128 261         726 $abs->base($base)->scheme($base->scheme);
129              
130             # Authority
131 261 100       1010 return $abs if $abs->host;
132 255         806 $abs->userinfo($base->userinfo)->host($base->host)->port($base->port);
133              
134             # Absolute path
135 255         711 my $path = $abs->path;
136 255 100       754 return $abs if $path->leading_slash;
137              
138             # Inherit path
139 52 100       111 if (!@{$path->parts}) {
  52         121  
140 10         42 $abs->path($base->path->clone->canonicalize);
141              
142             # Query
143 10 100       41 $abs->query($base->query->clone) unless length $abs->query->to_string;
144             }
145              
146             # Merge paths
147 42         123 else { $abs->path($base->path->clone->merge($path)->canonicalize) }
148              
149 52         439 return $abs;
150             }
151              
152 1577     1577 1 3916 sub to_string { shift->_string(0) }
153 3     3 1 10 sub to_unsafe_string { shift->_string(1) }
154              
155 6 100 100 6 1 19 sub username { (shift->userinfo // '') =~ /^([^:]+)/ ? $1 : undef }
156              
157 56   66 56   170 sub _decode { decode('UTF-8', $_[0]) // $_[0] }
158              
159 423     423   1273 sub _encode { url_escape encode('UTF-8', $_[0]), $_[1] }
160              
161             sub _string {
162 1580     1580   3058 my ($self, $unsafe) = @_;
163              
164             # Scheme
165 1580         2384 my $url = '';
166 1580 100       3703 if (my $proto = $self->protocol) { $url .= "$proto:" }
  364         990  
167              
168             # Authority
169 1580         3675 my $auth = $self->host_port;
170 1580 100       4103 $auth = _encode($auth, '^A-Za-z0-9\-._~!$&\'()*+,;=:\[\]') if defined $auth;
171 1580 100 66     3872 if ($unsafe && defined(my $info = $self->userinfo)) {
172 3         9 $auth = _encode($info, '^A-Za-z0-9\-._~!$&\'()*+,;=:') . '@' . $auth;
173             }
174 1580 100       3351 $url .= "//$auth" if defined $auth;
175              
176             # Path and query
177 1580         3293 my $path = $self->path_query;
178 1580 100 100     7653 $url .= !$auth || !length $path || $path =~ m!^[/?]! ? $path : "/$path";
179              
180             # Fragment
181 1580 100       4524 return $url unless defined(my $fragment = $self->fragment);
182 62         158 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