File Coverage

lib/URI/Title.pm
Criterion Covered Total %
statement 82 126 65.0
branch 16 54 29.6
condition 9 25 36.0
subroutine 15 16 93.7
pod 1 1 100.0
total 123 222 55.4


line stmt bran cond sub pod time code
1             package URI::Title;
2              
3 4     4   39505 use 5.006;
  4         9  
4 4     4   12 use warnings;
  4         4  
  4         86  
5 4     4   11 use strict;
  4         4  
  4         76  
6              
7 4     4   10 use base qw(Exporter);
  4         3  
  4         379  
8             our @EXPORT_OK = qw( title );
9              
10             our $VERSION;
11              
12 4     4   1500 use Module::Pluggable (search_path => ['URI::Title'], require => 1 );
  4         30091  
  4         20  
13 4     4   1984 use File::Type;
  4         47595  
  4         167  
14              
15 4     4   2067 use LWP::UserAgent;
  4         117157  
  4         100  
16 4     4   21 use HTTP::Request;
  4         4  
  4         77  
17 4     4   11 use HTTP::Response;
  4         6  
  4         2115  
18              
19              
20             sub _ua {
21 4     4   37 my $ua = LWP::UserAgent->new;
22 4         6952 $ua->agent("URI::Title/$VERSION");
23 4         166 $ua->timeout(20);
24 4         52 $ua->default_header('Accept-Encoding' => 'gzip');
25 4         105 return $ua;
26             }
27              
28             sub _get_limited {
29 3     3   4 my $url = shift;
30 3   50     26 my $size = shift || 32*1024;
31 3         10 my $ua = _ua();
32 3         12 $ua->max_size($size);
33 3         37 my $req = HTTP::Request->new(GET => $url);
34 3         15624 $req->header( Range => "bytes=0-$size" );
35 3         206 $req->header( "Accept-Encoding" => "" ); # vox sends invalid gzipped data?
36 3         121 my $res = eval { $ua->request($req) };
  3         15  
37 3 50       361874 return unless $res; # useragent explodes for non-valid uris
38              
39             # some servers don't like the Range header. If we
40             # get an odd 4xx response that isn't 404, just try getting
41             # the full thing. This may be a little impolite.
42 3 50 66     9 return _get_all($url) if $res->code >= 400 and $res->code < 500 and $res->code != 404;
      66        
43 2 50       21 return unless $res->is_success;
44 2 50       20 if (!wantarray) {
45 0   0     0 return $res->decoded_content || $res->content;
46             }
47 2         5 my $cset = "iso-8859-1"; # default;
48 2         6 my $ct = $res->header("Content-type");
49 2 100       55 if ($ct =~ /charset\s*=\>?\s*\"?([\w-]+)/i) {
50 1         3 $cset = lc($1);
51             #warn "Got charset $cset from URI headers\n";
52             }
53 2   33     12 return ($res->decoded_content || $res->content, $cset);
54             }
55              
56             sub _get_end {
57 0     0   0 my $url = shift;
58 0   0     0 my $size = shift || 16*1024;
59              
60 0         0 my $ua = _ua();
61              
62 0         0 my $request = HTTP::Request->new(HEAD => $url);
63 0         0 my $response = $ua->request($request);
64 0 0       0 return unless $response; # useragent explodes for non-valid uris
65 0         0 my $length = $response->header('Content-Length');
66              
67 0 0       0 return unless $length; # We can't get the length, and we're _not_
68             # going to get the whole thing.
69              
70 0         0 my $start = $length - $size;
71              
72 0         0 $ua->max_size($size);
73              
74 0         0 my $req = HTTP::Request->new(GET => $url);
75 0         0 $req->header( Range => "bytes=$start-$length" );
76 0         0 my $res = $ua->request($req);
77 0 0       0 return unless $res; # useragent explodes for non-valid uris
78              
79 0 0       0 return unless $res->is_success;
80 0 0       0 return $res->decoded_content unless wantarray;
81 0         0 my $cset = "iso-8859-1"; # default;
82 0         0 my $ct = $res->header("Content-type");
83 0 0       0 if ($ct =~ /charset=\"?(.*)\"?$/) {
84 0         0 $cset = $1;
85             }
86 0         0 return ($res->decoded_content, $cset);
87             }
88              
89             sub _get_all {
90 1     1   23 my $url = shift;
91 1         2 my $ua = _ua();
92 1         4 my $req = HTTP::Request->new(GET => $url);
93 1         50 my $res = $ua->request($req);
94 1 50       202 return unless $res->is_success;
95 0 0       0 return $res->decoded_content unless wantarray;
96 0         0 my $cset = "iso-8859-1"; # default;
97 0         0 my $ct = $res->header("Content-type");
98 0 0       0 if ($ct =~ /charset=\"?(.*)\"?$/) {
99 0         0 $cset = $1;
100             }
101 0         0 return ($res->decoded_content, $cset);
102             }
103              
104             # cache
105             our $HANDLERS;
106             sub _handlers {
107 2     2   12 my @plugins = plugins();
108 2 50       6457 return $HANDLERS if $HANDLERS;
109 2         7 for my $plugin (@plugins) {
110 8         106 for my $type ($plugin->types) {
111 18         30 $HANDLERS->{$type} = $plugin;
112             }
113             }
114 2         4 return $HANDLERS;
115             }
116              
117             sub title {
118 3     3 1 210729 my $param = shift;
119 3         6 my $data;
120             my $url;
121 0         0 my $type;
122 3         6 my $cset = "iso-8859-1"; # default
123              
124             # we can be passed a hashref. Keys are url, or data.
125 3 50       10 if (ref($param)) {
126 0 0       0 if ($param->{data}) {
    0          
127 0         0 $data = $param->{data};
128 0 0       0 $data = $$data if ref($data); # we can be passed a ref to the data
129             } elsif ($param->{url}) {
130 0         0 $url = $param->{url};
131             } else {
132 4     4   39 use Carp qw(croak);
  4         4  
  4         1081  
133 0         0 croak("Expected a single parameter, or an 'url' or 'data' key");
134             }
135              
136             # otherwise, assume we're passed an url
137             } else {
138 3         6 $url = $param;
139             }
140              
141 3 0 33     11 if (!$url and !$data) {
142 0         0 warn "Need at least an url or data";
143 0         0 return;
144             }
145              
146             # If we don't have data, we will have an url, so try to get data.
147 3 50       10 if (!$data) {
148             # url might be a filename
149 3 50       89 if (-e $url) {
150 0         0 local $/ = undef;
151 0 0       0 unless (open DATA, $url) {
152 0         0 warn "$url looks like a file and isn't";
153 0         0 return;
154             }
155 0         0 $data = ;
156 0         0 close DATA;
157            
158             # If not, assume it's an url
159             } else {
160             # special case for itms
161 3 50       15 if ($url =~ s/^itms:/http:/) {
162 0         0 $type = "itms";
163 0         0 $data = 1; # we don't need it, fake it.
164              
165             } else {
166             # special case for spotify
167 3         13 $url =~ s{^(?:http://open.spotify.com/|spotify:)(\w+)[:/]}{http://spotify.url.fi/$1/};
168            
169 3         8 $url =~ s{#!}{?_escaped_fragment_=};
170              
171 3         11 ($data, $cset) = _get_limited($url);
172             }
173             }
174             }
175 3 100       8990 if (!$data) {
176             #warn "Can't get content for $url";
177 1         5 return;
178             }
179              
180 2 50       6 return undef unless $data;
181              
182 2   33     23 $type ||= File::Type->new->checktype_contents($data);
183              
184 2         1655 my $handlers = _handlers();
185             my $handler = $handlers->{$type} || $handlers->{default}
186 2 50 33     14 or return;
187              
188 2         7 return $handler->title($url, $data, $type, $cset);
189             }
190              
191             1;
192              
193             __END__