File Coverage

blib/lib/URI/Builder.pm
Criterion Covered Total %
statement 171 178 96.0
branch 78 88 88.6
condition 21 24 87.5
subroutine 30 31 96.7
pod 17 17 100.0
total 317 338 93.7


line stmt bran cond sub pod time code
1             package URI::Builder;
2              
3 4     4   140202 use strict;
  4         11  
  4         161  
4 4     4   22 use warnings;
  4         19  
  4         193  
5              
6             =head1 NAME
7              
8             URI::Builder - URI objects optimised for manipulation
9              
10             =head1 SYNOPSIS
11              
12             my $uri = URI::Builder->new(
13             scheme => 'http',
14             host => 'www.cpan.org',
15             );
16              
17             $uri->path_segments(qw( misc cpan-faq.html ));
18              
19             say $uri->as_string; # http://www.cpan.org/misc/cpan-faq.html
20              
21             =head1 VERSION
22              
23             0.04
24              
25             =cut
26              
27             our $VERSION = '0.04';
28              
29             =head1 DESCRIPTION
30              
31             This class is a close relative of L, but while that class is optimised
32             for parsing, this is optimised for building up or modifying URIs. To that end
33             objects of this class represent their URIs in sections, each of which are
34             independently mutable, that then need to be serialised to form a string. In
35             contrast, C uses a fully-formed string internally which must be parsed
36             afresh each time a mutation is performed on it.
37              
38             At the moment only http and https URIs are known to work correctly, support
39             for other schemes may follow later.
40              
41             =cut
42              
43 4     4   3742 use URI;
  4         32130  
  4         147  
44 4     4   39 use Scalar::Util qw( blessed );
  4         8  
  4         602  
45 4     4   30 use Carp qw( confess );
  4         7  
  4         1208  
46              
47             # Utility functions
48             sub _flatten {
49 141 100       680 return map {
    100          
50 52     52   150 ref $_ eq 'ARRAY' ? _flatten(@$_)
51             : ref $_ eq 'HASH' ? _flatten_hash($_)
52             : $_
53             } @_ = @_;
54             }
55              
56             sub _flatten_hash {
57 5     5   5 my $hash = shift;
58              
59 7         14 return map {
60 5         14 my ($k, $v) = ($_, $hash->{$_});
61 7 100       19 $v = '' unless defined $v;
62 7         13 map { $k => $_ } _flatten $v
  16         44  
63             } keys %$hash;
64             }
65              
66 4     4   34 use overload ('""' => \&as_string, fallback => 1);
  4         10  
  4         28  
67              
68             =head1 ATTRIBUTES
69              
70             The following attributes relate closely with the URI methods of the same
71             names.
72              
73             =over
74              
75             =item * scheme
76              
77             =item * userinfo
78              
79             =item * host
80              
81             =item * port
82              
83             =item * path_segments
84              
85             =item * query_form
86              
87             =item * query_keywords
88              
89             =item * fragment
90              
91             =back
92              
93             In addition the C attribute defines how C fields
94             are joined. It defaults to C<';'> but can be usefully set to '&'.
95              
96             The accessors for these attributes have a similar interface to the L
97             methods, that is to say that they return old values when new ones are set.
98             Those attributes that take a list of values: C, C
99             and C all return plain lists but can be passed nested array
100             references.
101              
102             =cut
103              
104             my (@uri_fields, %listish, @fields);
105              
106             BEGIN {
107             # Fields that correspond to methods in URI
108 4     4   16 @uri_fields = qw(
109             scheme
110             userinfo
111             host
112             port
113             path_segments
114             query_form
115             query_keywords
116             fragment
117             );
118              
119             # Fields that contain lists of values
120 4         10 %listish = map { $_ => 1 } qw(
  12         50  
121             path_segments
122             query_form
123             query_keywords
124             );
125              
126             # All fields
127 4         21 @fields = ( @uri_fields, qw( query_separator ));
128              
129             # Generate accessors for all fields:
130 4         19 for my $field (@fields) {
131 4     4   765 my $glob = do { no strict 'refs'; \*$field };
  4         7  
  4         620  
  36         47  
  36         183  
132              
133             *$glob = $listish{$field} ? sub {
134 221     221   320 my $self = shift;
135 221 100       235 my @old = @{ $self->{$field} || []};
  221         1052  
136 221 100       481 $self->{$field} = [ _flatten @_ ] if @_;
137 221         627 return @old;
138             }
139             : sub {
140 248     248   646 my $self = shift;
141 248         502 my $old = $self->{$field};
142 248 50       488 $self->{$field} = shift if @_;
143 248         721 return $old;
144 36 100       8803 };
145             }
146             }
147              
148             =head1 METHODS
149              
150             =head2 new
151              
152             The constructor.
153              
154             In addition to the attributes listed above, a C argument can be passed as
155             a string or a L object, which will be parsed to popoulate any missing
156             fields.
157              
158             # a cpan URL without its path
159             my $uri = URI::Builder->new(
160             uri => 'http://www.cpan.org/SITES.html',
161             path_segments => [],
162             );
163              
164             Non-attribute arguments that match other methods in the class will cause those
165             methods to be called on the object. This means that what we internally regard
166             as composite attributes can be specified directly in the constructor.
167              
168             # Implicitly populate path_segments:
169             my $uri = URI::Builder->new( path => 'relative/path' );
170              
171             Unrecognised arguments cause an exception.
172              
173             =cut
174              
175             sub new {
176 34     34 1 15730 my $class = shift;
177 34 50 33     215 my %opts = @_ == 1 && ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
  0         0  
178              
179 34   100     179 $opts{query_separator} ||= ';';
180              
181 34 100       110 if (my $uri = $opts{uri}) {
182 9         63 $uri = $class->_inflate_uri($uri);
183              
184 9         31770 for my $field (@uri_fields) {
185 72 100 66     3137 if (!defined $opts{$field} && (my $code = $uri->can($field))) {
186 69 100       307 $opts{$field} =
187             $listish{$field} ? [ $code->($uri) ] : $code->($uri);
188             }
189             }
190             }
191              
192             $_ = [ _flatten $_ ]
193 34   100     454 for grep defined && ref ne 'ARRAY', @opts{ keys %listish };
194              
195             # Still no scheme? Default to http
196             # $opts{scheme} ||= 'http';
197              
198 34         125 my $self = bless { map { $_ => $opts{$_} } @fields }, $class;
  306         770  
199              
200 34         163 delete @opts{@fields};
201              
202 34         117 for my $field (sort keys %opts) {
203 14 50       86 if (my $method = $self->can($field)) {
204 14         49 $method->($self, _flatten delete $opts{$field});
205             }
206             }
207              
208 34 50       787 if (my @invalid = sort keys %opts) {
209 0         0 confess "Unrecognised fields in constructor: ", join ', ', @invalid;
210             }
211              
212 34         140 return $self;
213             }
214              
215             # Turn various things into URI objects
216             sub _inflate_uri {
217 11     11   126 my ($self, $thing) = @_;
218              
219 11 100       53 if (blessed $thing) {
220 4 50       35 if ($thing->isa('URI')) {
    0          
221 4         11 return $thing;
222             }
223             elsif ($thing->isa(__PACKAGE__)) {
224 0         0 return $thing->uri;
225             }
226             else {
227 0         0 return URI->new("$thing");
228             }
229             }
230             else {
231 7         55 return URI->new($thing);
232             }
233             }
234              
235             =head2 abs
236              
237             $absolute_uri = $relative_uri->abs($base_uri)
238              
239             Returns a new L object as an absolute URL based on the given
240             base URI.
241              
242             Implemented as a wrapper of L.
243              
244             =cut
245              
246             sub abs {
247 1     1 1 398 my ($self, $base, @args) = @_;
248 1         3 my $class = ref $self;
249              
250 1         4 return $class->new(uri => $self->uri->abs($self->_inflate_uri($base), @args));
251             }
252              
253              
254             =head2 rel
255              
256             $relative_uri = $absolute_uri->rel($base_uri)
257              
258             Returns a new L object denoting the relative URI compared with
259             the base URI.
260              
261             Implemented as a wrapper of L.
262              
263             =cut
264              
265             sub rel {
266 1     1 1 7 my ($self, $base) = @_;
267 1         3 my $class = ref $self;
268              
269 1         4 return $class->new(uri => $self->uri->rel($self->_inflate_uri($base)));
270             }
271              
272             =head2 clone
273              
274             Returns a new object with all attributes copied.
275              
276             =cut
277              
278             sub clone {
279 2     2 1 20 my $self = shift;
280              
281 2         24 my %clone = %$self;
282 2         7 for my $list ( keys %listish ) {
283 6 50 100     18 $clone{$list} &&= [ @{ $clone{$list} || [] } ];
  3         20  
284             }
285              
286 2         13 return ref($self)->new(%clone);
287             }
288              
289             =head2 as_string
290              
291             Returns the URI described by the object as a string. This is built up from the
292             individual components each time it's called.
293              
294             This is also used as the stringification overload.
295              
296             =cut
297              
298             sub as_string {
299 44     44 1 397 my $self = shift;
300              
301 44         53 my @parts;
302              
303 44 100       98 if (my $authority = $self->authority) {
304 28 100       63 if (my $scheme = $self->scheme) {
305 22         45 push @parts, "$scheme:";
306             }
307              
308 28         40 $authority =~ s/:@{[ $self->default_port ]}\z//;
  28         83  
309              
310 28         3188 push @parts, "//$authority";
311             }
312              
313 44 100       107 if (my $path = $self->path) {
314 20 100       64 $path =~ s{^(?!/)}{/} if @parts;
315 20         35 push @parts, $path;
316             }
317              
318 44 100       107 if (my $query = $self->query) {
319 15         37 push @parts, "?$query";
320             }
321              
322 44 100       105 if (my $fragment = $self->fragment) {
323 3         9 push @parts, "#$fragment";
324             }
325              
326 44         254 return join('', @parts);
327             }
328              
329             =head2 uri
330              
331             Returns a version of this object as a L object.
332              
333             =cut
334              
335             sub uri {
336 11     11 1 23 my $self = shift;
337              
338 11         51 return URI->new($self->as_string);
339             }
340              
341             =head2 default_port
342              
343             Returns the default port for the current object's scheme. This is obtained
344             from the appropriate L subclass. See L.
345              
346             =head2 secure
347              
348             Returns true if the current scheme is a secure one, false otherwise. See
349             L.
350              
351             =cut
352              
353             sub _implementor {
354 28     28   32 my $self = shift;
355              
356 28   100     49 return URI::implementor($self->scheme || 'http');
357             }
358              
359 28     28 1 58 sub default_port { shift->_implementor->default_port }
360 0     0 1 0 sub secure { shift->_implementor->secure }
361              
362             =head2 authority
363              
364             Returns the 'authority' section of the URI. In our case this is obtained by
365             combining C, C and C together as appropriate.
366              
367             Note that this is a read-only operation.
368              
369             =cut
370              
371             sub authority {
372 44     44 1 58 my $self = shift;
373 44         154 my ($user, $host) = ($self->userinfo, $self->host_port);
374              
375 44 100       201 return $host ? $user ? "$user\@$host" : $host : '';
    100          
376             }
377              
378             =head2 host_port
379              
380             Returns the host and port in a single string.
381              
382             =cut
383              
384             sub host_port {
385 44     44 1 59 my $self = shift;
386 44         88 my ($host, $port) = ($self->host, $self->port);
387              
388 44 100       185 return $host ? $port ? "$host:$port" : $host : '';
    100          
389             }
390              
391             =head2 path
392              
393             Returns the path portion of the URI as a string.
394              
395             Can be assigned to to populate C.
396              
397             Leading, trailing and doubled slashes are represented faithfully using empty
398             path segments.
399              
400             =cut
401              
402             sub path {
403 54     54 1 72 my $self = shift;
404              
405 54         247 my $old = join '/', $self->path_segments;
406              
407 54 100       147 if (@_) {
408 6         24 my @segments = split '/', shift, -1;
409 6         17 $self->path_segments(@segments);
410             }
411              
412 54         162 return $old;
413             }
414              
415             =head2 query
416              
417             Returns a string representation of the query. This is obtained from either
418             C or C, in that order.
419              
420             If an argument is passed, it is parsed to populate C.
421              
422             =cut
423              
424             sub query {
425 49     49 1 73 my ($self, $query) = @_;
426              
427 49         52 my @new;
428 49 100       94 if ($query) {
429             # Parse the new query string using a URI object
430 1         4 @new = URI->new("?$query", $self->scheme)->query_form;
431             }
432              
433 49 50       170 unless (defined wantarray) {
434             # void context, don't bother building the query string
435 0         0 $self->query_form(@new);
436 0         0 return;
437             }
438              
439 49         51 my $old;
440 49 100       111 if (my @form = $self->query_form) {
441 15 100       49 push @form, '' if @form % 2;
442 15         62 my $uri = URI->new;
443 15         978 $uri->query_form(\@form, $self->query_separator);
444 15         1598 $old = $uri->query();
445             }
446             else {
447 34         76 $old = join '+', $self->query_keywords;
448             }
449              
450 49         255 $self->query_form(@new);
451              
452 49         175 return $old;
453             }
454              
455             =head2 path_query
456              
457             Returns a string representation of the path plus the query string. See
458             L.
459              
460             =cut
461              
462             sub path_query {
463 2     2 1 3 my $self = shift;
464 2         5 my ($path, $query) = ($self->path, $self->query);
465              
466 2 50       8 my $old = $path . ($query ? "?$query" : '');
467              
468 2 100       6 if (@_) {
469 1         12 my $uri = URI->new($_[0]);
470 1         33 $self->$_([ $uri->$_ ]) for qw( path_segments query_form );
471             }
472              
473 2         22 return $old
474             }
475              
476             =head2 query_param
477              
478             @keys = $uri->query_param
479             @values = $uri->query_param($key)
480             @old_values = $uri->query_param($key, @new_values);
481              
482             This works exactly like the method of the same name implemented in
483             L.
484              
485             With no arguments, all unique query field names are returned
486              
487             With one argument, all values for the given field name are returned
488              
489             With more than one argument, values for the given key (first argument) are set
490             to the given values (remaining arguments). Care is taken in this case to
491             preserve the ordering of the fields.
492              
493             =cut
494              
495             sub query_param {
496 18     18 1 74 my ($self, $key, @values) = @_;
497 18         41 my @form = $self->query_form;
498              
499 18 100       38 if ($key) {
500 12   100     174 my @indices = grep $_ % 2 == 0 && $form[$_] eq $key, 0 .. $#form;
501 12         42 my @old_values = @form[ map $_ + 1, @indices ];
502              
503 12 100       34 if (@values) {
504 2         5 @values = _flatten @values;
505 2         11 splice @form, pop @indices, 2 while @indices > @values;
506              
507 2 100       6 my $last_index = @indices ? $indices[-1] + 2 : @form;
508              
509 2   100     11 while (@values && @indices) {
510 3         15 splice @form, shift @indices, 2, $key, shift @values;
511             }
512              
513 2 100       4 if (@values) {
514 1         3 splice @form, $last_index, 0, map { $key => $_ } @values;
  1         3  
515             }
516              
517 2         6 $self->query_form(@form);
518             }
519              
520 12 100       78 return wantarray ? @old_values : $old_values[0];
521             }
522             else {
523 6         8 my %seen;
524 6         68 return grep !$seen{$_}++, map $form[$_], grep $_ % 2 == 0, 0 .. $#form;
525             }
526             }
527              
528             =head2 query_param_append
529              
530             $uri->query_param_append($key, @values)
531              
532             Appends fields to the end of the C. Returns nothing.
533              
534             =cut
535              
536             sub query_param_append {
537 1     1 1 4 my ($self, $key, @values) = @_;
538              
539 1         3 $self->query_form($self->query_form, map { $key => $_ } _flatten @values);
  4         7  
540              
541 1         4 return;
542             }
543              
544             =head2 query_param_delete
545              
546             @old_values = $uri->query_param_delete($key)
547              
548             Removes all fields with the given key from the C.
549              
550             =cut
551              
552             sub query_param_delete {
553 1     1 1 2 my ($self, $key) = @_;
554              
555 1         4 return $self->query_param($key, []);
556             }
557              
558             =head2 query_form_hash
559              
560             $hashref = $uri->query_form_hash
561             $old_hashref = $uri->query_form_hash(\%new_hashref)
562              
563             A hash representation of the C, with multiple values represented
564             as arrayrefs.
565              
566             =cut
567              
568             sub query_form_hash {
569 8     8 1 13 my $self = shift;
570 8         9 my @new;
571              
572 8 100 100     53 if (my %form = @_ == 1 && ref $_[0] eq 'HASH' ? %{ $_[0] } : @_) {
  2 100       10  
573 4         18 @new = _flatten_hash(\%form);
574             }
575              
576 8 100       21 unless (defined wantarray) {
577             # void context, don't bother building the hash
578 3         8 $self->query_form(@new);
579 3         12 return;
580             }
581              
582 7         18 my %form = map {
583 5         14 my @values = $self->query_param($_);
584 7 100       36 ( $_ => @values == 1 ? $values[0] : \@values );
585             } $self->query_param;
586              
587 5 100       15 $self->query_form(@new) if @new;
588              
589 5         43 return \%form;
590             }
591              
592             =head1 TODO
593              
594             The following URI methods are currently not implemented:
595              
596             =over
597              
598             =item * as_iri
599              
600             =item * ihost
601              
602             =back
603              
604             =head1 LICENSE
605              
606             L
607              
608             =cut
609              
610             1;
611              
612             __END__