File Coverage

blib/lib/Net/Yadis/Discovery.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Net::Yadis::Discovery;
2              
3 6     6   222240 use strict;
  6         14  
  6         259  
4 6     6   35 use warnings;
  6         13  
  6         204  
5 6     6   33 use vars qw($VERSION @EXPORT);
  6         15  
  6         449  
6             $VERSION = "0.05";
7              
8 6     6   34 use base qw(Exporter);
  6         7  
  6         880  
9 6     6   34 use Carp ();
  6         9  
  6         489  
10 6     6   5366 use URI::Fetch 0.02;
  6         861239  
  6         204  
11 6     6   3300 use XML::Simple;
  0            
  0            
12             use Module::Pluggable::Fast
13             search => [ 'Net::Yadis::Discovery::Protocol' ],
14             callback => sub { };
15             use Net::Yadis::Object;
16              
17             @EXPORT = qw(YR_HEAD YR_GET YR_XRDS);
18              
19             use constant {
20             YR_HEAD => 0,
21             YR_GET => 1,
22             YR_XRDS => 2,
23             };
24              
25             use fields (
26             'cache', # the Cache object sent to URI::Fetch
27             '_ua', # Custom LWP::UserAgent instance to use
28             'last_errcode', # last error code we got
29             'last_errtext', # last error code we got
30             'debug', # debug flag or codeblock
31             'identity_url', # URL to be identified
32             'xrd_url', # URL of XRD file
33             'xrd_objects', # Yadis XRD decoded objects
34             );
35              
36             sub new {
37             my $self = shift;
38             $self = fields::new( $self ) unless ref $self;
39             my %opts = @_;
40              
41             $self->ua ( delete $opts{ua} );
42             $self->cache ( delete $opts{cache} );
43              
44             $self->{debug} = delete $opts{debug};
45              
46             Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts;
47              
48             $self->plugins;
49             return $self;
50             }
51              
52             sub cache { &_getset; }
53             sub identity_url { &_getset; }
54             sub xrd_url { &_getset; }
55             sub xrd_objects { _pack_array(&_getset); }
56             sub _ua { &_getset; }
57             sub _getset {
58             my $self = shift;
59             my $param = (caller(1))[3];
60             $param =~ s/.+:://;
61              
62             if (@_) {
63             my $val = shift;
64             Carp::croak("Too many parameters") if @_;
65             $self->{$param} = $val;
66             }
67             return $self->{$param};
68             }
69              
70             sub _debug {
71             my $self = shift;
72             return unless $self->{debug};
73              
74             if (ref $self->{debug} eq "CODE") {
75             $self->{debug}->($_[0]);
76             } else {
77             print STDERR "[DEBUG Net::Yadis::Discovery] $_[0]\n";
78             }
79             }
80              
81             sub _fail {
82             my $self = shift;
83             my ($code, $text) = @_;
84              
85             $text ||= {
86             'xrd_parse_error' => "Error occured since parsing yadis document.",
87             'xrd_format_error' => "This is not yadis document (not xrds format).",
88             'too_many_hops' => 'Too many hops by X-XRDS-Location.',
89             'empty_url' => 'Empty URL',
90             'no_yadis_document' => 'Cannot find yadis Document',
91             'url_gone' => 'URL is no longer available',
92             }->{$code};
93              
94             $self->{last_errcode} = $code;
95             $self->{last_errtext} = $text;
96              
97             $self->_debug("fail($code) $text");
98             wantarray ? () : undef;
99             }
100             sub err {
101             my $self = shift;
102             $self->{last_errcode} . ": " . $self->{last_errtext};
103             }
104             sub errcode {
105             my $self = shift;
106             $self->{last_errcode};
107             }
108             sub errtext {
109             my $self = shift;
110             $self->{last_errtext};
111             }
112             sub _clear_err {
113             my $self = shift;
114             $self->{last_errtext} = '';
115             $self->{last_errcode} = '';
116             }
117              
118             sub ua {
119             my $self = shift;
120             my $ua = shift if @_;
121             Carp::croak("Too many parameters") if @_;
122              
123             if (($ua) || (!$self->{_ua})) {
124             $self->{_ua} = Net::Yadis::Discovery::UA->new($ua);
125             }
126              
127             $self->{_ua}->{'ua'};
128             }
129              
130             sub _get_contents {
131             my $self = shift;
132              
133             my ($url, $final_url_ref, $content_ref, $headers_ref) = @_;
134             $final_url_ref ||= do { my $dummy; \$dummy; };
135              
136             my $ures = URI::Fetch->fetch($url,
137             UserAgent => $self->_ua,
138             Cache => $self->_ua->force_head ? undef : $self->cache,
139             ContentAlterHook => sub {my $htmlref = shift;$$htmlref =~ s/
140             )
141             or return $self->_fail("url_fetch_error", "Error fetching URL: " . URI::Fetch->errstr);
142              
143             if ($ures->status == URI::Fetch::URI_GONE()) {
144             return $self->_fail("url_gone");
145             }
146            
147             my $res = $ures->http_response;
148              
149             $$final_url_ref = $res->request->uri->as_string;
150             $res->headers->scan(sub{$headers_ref->{lc($_[0])} ||= $_[1];});
151             $$content_ref = $ures->content;
152              
153             return 1;
154             }
155              
156             sub discover {
157             my $self = shift;
158             my $url = shift or return $self->_fail("empty_url");
159             my $count = shift || YR_HEAD; # $count = YR_HEAD:HEAD request YR_GET:GET request YR_XRDS:XRDS request
160             Carp::croak("Too many parameters") if @_;
161              
162             # trim whitespace
163             $url =~ s/^\s+//;
164             $url =~ s/\s+$//;
165             return $self->_fail("empty_url") unless $url;
166              
167             my $final_url;
168             my %headers;
169              
170             $self->_ua->force_head(1) if ($count == YR_HEAD);
171              
172             my $xrd;
173             $self->_get_contents($url, \$final_url, \$xrd, \%headers) or return;
174              
175             $self->identity_url($final_url) if ($count < YR_XRDS);
176              
177             my $doc_url;
178             if (($doc_url = $headers{'x-yadis-location'} || $headers{'x-xrds-location'}) && ($count < YR_XRDS)) {
179             return $self->discover($doc_url,YR_XRDS);
180             } elsif ($headers{'content-type'} eq 'application/xrds+xml') {
181             return $self->discover($final_url,YR_XRDS) if ((!$xrd) && ($count == YR_HEAD));
182             $self->xrd_url($final_url);
183             return $self->parse_xrd($xrd);
184             }
185              
186             return $count == YR_HEAD ? $self->discover($final_url,YR_GET) : $self->_fail($count == YR_GET ? "no_yadis_document" :"too_many_hops");
187             }
188              
189             sub parse_xrd {
190             my $self = shift;
191             my $xrd = shift;
192             Carp::croak("Too many parameters") if @_;
193              
194             my $xs_hash = XMLin($xrd) or return $self->_fail("xrd_parse_error");
195             ($xs_hash->{'xmlns'} and $xs_hash->{'xmlns'} eq 'xri://$xrd*($v*2.0)') or $self->_fail("xrd_format_error");
196             my %xmlns;
197             foreach (map { /^(xmlns:(.+))$/ and [$1,$2] } keys %$xs_hash) {
198             next unless ($_);
199             $xmlns{$_->[1]} = $xs_hash->{$_->[0]};
200             }
201             my @priority;
202             my @nopriority;
203             foreach my $service (_pack_array($xs_hash->{'XRD'}{'Service'})) {
204             bless $service, "Net::Yadis::Object";
205             $service->{'Type'} or next;
206             $service->{'URI'} ||= $self->identity_url;
207              
208             foreach my $sname (keys %$service) {
209             foreach my $ns (keys %xmlns) {
210             $service->{"{$xmlns{$ns}}$1"} = delete $service->{$sname} if ($sname =~ /^${ns}:(.+)$/);
211             }
212             }
213             defined($service->{'priority'}) ? push(@priority,$service) : push(@nopriority,$service);
214             # Services without priority fields are lowest priority
215             }
216             my @service = sort {$a->{'priority'} <=> $b->{'priority'}} @priority;
217             push (@service,@nopriority);
218             foreach (grep {/^_protocol/} keys %$self) { delete $self->{$_} }
219              
220             $self->xrd_objects(\@service);
221             }
222              
223             sub _pack_array { wantarray ? ref($_[0]) eq 'ARRAY' ? @{$_[0]} : ($_[0]) : $_[0] }
224              
225             sub servers {
226             my $self = shift;
227             my %protocols;
228             my $code_ref;
229             my $protocol = undef;
230            
231             Carp::croak("Not calling discover method yet") unless $self->xrd_objects;
232              
233             foreach my $option (@_) {
234             Carp::croak("No option allow after code reference option") if $code_ref;
235             my $ref = ref($option);
236             if ($ref eq 'CODE') {
237             $code_ref = $option;
238             } elsif ($ref eq 'ARRAY') {
239             Carp::croak("Version array option needs protocol name or URL") unless $protocol;
240             $protocols{$protocol}->{versionarray} = $option;
241             $protocol = undef;
242             } else {
243             my $default = {versionarray => []};
244             unless ($option =~ /^http/) {
245             my $method = "${option}_regex";
246             Carp::croak("Unknown protocol: $option") unless $self->can($method);
247             $default->{urlregex} = $self->$method;
248             $method = "${option}_objectclass";
249             $default->{objectclass} = $self->$method if $self->can($method);
250             }
251              
252             $protocols{$option} = $default;
253             $protocol = $option;
254             }
255             }
256              
257             my @servers;
258             @servers = $self->xrd_objects if (keys %protocols == 0);
259             foreach my $key (keys %protocols) {
260             my $regex = $protocols{$key}->{urlregex} || $key;
261             my @ver = @{$protocols{$key}->{versionarray}};
262             my $ver_regex = @ver ? '('.join('|',map { $_ =~ s/\./\\./g; $_ } @ver).')' : '.+' ;
263             $regex =~ s/\\ver/$ver_regex/;
264              
265             push (@servers,map { $protocols{$key}->{objectclass} ? bless($_ , $protocols{$key}->{objectclass}) : $_ } grep {join(",",$_->Type) =~ /$regex/} $self->xrd_objects);
266             }
267              
268             @servers = $code_ref->(@servers) if ($code_ref);
269              
270             wantarray ? @servers : \@servers;
271             }
272              
273             package Net::Yadis::Discovery::UA;
274              
275             # This module is decolation module to LWP::UserAgent.
276             # This add application/xrds+xml HTTP header and GET method to request object used in URI::Fetch.
277              
278             use strict;
279             use warnings;
280             use LWP::UserAgent;
281             use vars qw($AUTOLOAD $lwpclass);
282              
283             BEGIN {
284             eval "use LWPx::ParanoidAgent;";
285             $lwpclass = $@ ? "LWP::UserAgent" : "LWPx::ParanoidAgent";
286             }
287              
288             sub new {
289             my $class = shift;
290             my $ua = shift;
291             unless ($ua) {
292             $ua = $lwpclass->new;
293             $ua->timeout(10);
294             }
295             bless {ua => $ua,force_head => 0},$class;
296             }
297              
298             sub request {
299             my $self = shift;
300             my $req = shift;
301             $req->header('Accept' => 'application/xrds+xml');
302             $req->method($self->force_head ? "HEAD" : "GET");
303             $self->force_head(0);
304             $self->{'ua'}->request($req);
305             }
306              
307             sub force_head {
308             $_[0]->{'force_head'} = $_[1] if defined($_[1]);
309             $_[0]->{'force_head'};
310             }
311              
312             sub AUTOLOAD {
313             my $self = shift;
314             return if $AUTOLOAD =~ /::DESTROY$/;
315             $AUTOLOAD =~ s/.*:://;
316             $self->{'ua'}->$AUTOLOAD(@_);
317             }
318              
319             1;
320             __END__