File Coverage

blib/lib/Protocol/Yadis.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition 1 2 50.0
subroutine 4 4 100.0
pod n/a
total 15 18 83.3


line stmt bran cond sub pod time code
1             package Protocol::Yadis;
2              
3 3     3   157732 use strict;
  3         7  
  3         305  
4 3     3   15 use warnings;
  3         6  
  3         128  
5              
6             require Carp;
7              
8 3   50 3   14 use constant DEBUG => $ENV{PROTOCOL_YADIS_DEBUG} || 0;
  3         5  
  3         282  
9              
10 3     3   1631 use Protocol::Yadis::Document;
  0            
  0            
11              
12             our $VERSION = '1.00';
13              
14             sub new {
15             my $class = shift;
16             my %param = @_;
17              
18             my $self = {@_};
19             bless $self, $class;
20              
21             Carp::croak('http_req_cb is required') unless $self->{http_req_cb};
22              
23             $self->{_headers} = {'Accept' => 'application/xrds+xml'};
24              
25             return $self;
26             }
27              
28             sub http_req_cb { shift->{http_req_cb} }
29             sub head_first { shift->{head_first} }
30              
31             sub discover {
32             my $self = shift;
33             my ($url, $cb) = @_;
34              
35             my $method = $self->head_first ? 'HEAD' : 'GET';
36              
37             if ($method eq 'GET') {
38             return $self->_initial_req($url, sub { $cb->(@_) });
39             }
40             else {
41             $self->_initial_head_req(
42             $url => sub {
43             my ($self, $location, $error) = @_;
44              
45             return $cb->($self, undef, $error) if $error;
46              
47             return $self->_initial_req($url, sub { $cb->(@_) })
48             unless $location;
49              
50             return $self->_second_req($location => sub { $cb->(@_); });
51             }
52             );
53             }
54             }
55              
56             sub _parse_document {
57             my $self = shift;
58             my ($headers, $body) = @_;
59              
60             my $content_type = $headers->{'Content-Type'};
61              
62             if ( $content_type
63             && $content_type =~ m/^(?:application\/xrds\+xml|text\/xml);?/)
64             {
65             my $document = Protocol::Yadis::Document->parse($body);
66              
67             return $document if $document;
68             }
69              
70             return;
71             }
72              
73             sub _initial_req {
74             my $self = shift;
75             my ($url, $cb) = @_;
76              
77             $self->_initial_get_req(
78             $url => sub {
79             my ($self, $document, $location, $error) = @_;
80              
81             # Error
82             return $cb->($self, undef, $error) if $error;
83              
84             # Yadis document
85             return $cb->($self, $document) if $document;
86              
87             # No new location
88             return $cb->($self) unless $location;
89              
90             # New location
91             return $self->_second_req($location => $cb);
92             }
93             );
94             }
95              
96             sub _initial_head_req {
97             my $self = shift;
98             my ($url, $cb) = @_;
99              
100             warn 'HEAD request' if DEBUG;
101              
102             $self->http_req_cb->(
103             $url, 'HEAD',
104             $self->{_headers},
105             undef => sub {
106             my ($url, $status, $headers, $body, $error) = @_;
107              
108             # Error
109             return $cb->($self, undef, $error) if $error;
110              
111             # Wrong response status
112             return $cb->($self, undef, 'Wrong response status')
113             unless $status && $status == 200;
114              
115             # New location
116             if (my $location = $headers->{'X-XRDS-Location'}) {
117             warn 'Found X-XRDS-Location' if DEBUG;
118              
119             return $cb->($self, $location);
120             }
121              
122             # Nothing found
123             $cb->($self);
124             }
125             );
126             }
127              
128             sub _initial_get_req {
129             my $self = shift;
130             my ($url, $cb) = @_;
131              
132             warn 'GET request' if DEBUG;
133              
134             $self->http_req_cb->(
135             $url, 'GET',
136             $self->{_headers},
137             undef => sub {
138             my ($url, $status, $headers, $body, $error) = @_;
139              
140             # Pass the error
141             return $cb->($self, undef, undef, $error) if $error;
142              
143             warn 'after user callback' if DEBUG;
144              
145             # Wrong response status
146             return $cb->($self, undef, undef, 'Wrong response status')
147             unless $status && $status == 200;
148              
149             warn 'status is ok' if DEBUG;
150              
151             # New XRDS location found
152             if (my $location = $headers->{'X-XRDS-Location'}) {
153             warn 'Found X-XRDS-Location' if DEBUG;
154              
155             # Response body
156             if ($body) {
157             warn 'Found body' if DEBUG;
158              
159             my $document = $self->_parse_document($headers, $body);
160              
161             # Yadis document discovered
162             return $cb->($self, $document) if $document;
163             }
164              
165             warn 'no yadis was found' if DEBUG;
166              
167             # Not a Yadis document, thus try new location
168             return $cb->($self, undef, $location);
169             }
170              
171             warn 'No X-XRDS-Location header was found' if DEBUG;
172              
173             # Response body
174             if ($body) {
175             my $document = $self->_parse_document($headers, $body);
176              
177             # Yadis document discovered
178             return $cb->($self, $document) if $document;
179              
180             warn 'Found HTML' if DEBUG;
181             my ($head) = ($body =~ m/<\s*head\s*>(.*?)<\/\s*head\s*>/is);
182              
183             # Invalid HTML
184             return $cb->($self, undef, undef, 'No was found')
185             unless $head;
186              
187             my $location;
188             my $tags = _html_tag(\$head);
189             foreach my $tag (@$tags) {
190             next unless $tag->{name} eq 'meta';
191              
192             my $attrs = $tag->{attrs};
193             next
194             unless %$attrs
195             && $attrs->{'http-equiv'}
196             && $attrs->{'http-equiv'} =~ m/^X-XRDS-Location$/i;
197              
198             last if ($location = $attrs->{content});
199             }
200              
201             # Try new location
202             return $cb->($self, undef, $location) if $location;
203              
204             # No HTML information was found
205             return $cb->($self, undef, undef, 'No was found');
206             }
207              
208             warn 'No body was found' if DEBUG;
209             return $cb->($self, undef, undef, 'No document was found');
210             }
211             );
212             }
213              
214             sub _second_req {
215             my $self = shift;
216             my ($url, $cb) = @_;
217              
218             warn 'Second GET request' if DEBUG;
219              
220             $self->http_req_cb->(
221             $url, 'GET',
222             $self->{_headers},
223             undef => sub {
224             my ($url, $status, $headers, $body, $error) = @_;
225              
226             # Error
227             return $cb->($self, undef, $error) if $error;
228              
229             # Wrong response status
230             return $cb->($self, undef, 'Wrong response status')
231             unless $status && $status == 200;
232              
233             # No document
234             return $cb->($self, undef, 'No body was found') unless $body;
235              
236             # Found Yadis document
237             if (my $document = $self->_parse_document($headers, $body)) {
238             warn 'XRDS Document was found' if DEBUG;
239             return $cb->($self, $document);
240             }
241              
242             # Nothing found
243             return $cb->($self);
244             }
245             );
246             }
247              
248             # based on HTML::TagParser
249             sub _html_tag {
250             my $txtref = shift; # reference
251             my $flat = [];
252              
253             while (
254             $$txtref =~ s{
255             ^(?:[^<]*) < (?:
256             ( / )? ( [^/!<>\s"'=]+ )
257             ( (?:"[^"]*"|'[^']*'|[^"'<>])+ )?
258             |
259             (!-- .*? -- | ![^\-] .*? )
260             ) \/?> ([^<]*)
261             }{}sxg
262             )
263             {
264             my $attrs;
265             if ($3) {
266             my $attr = $3;
267             my $name;
268             my $value;
269             while ($attr =~ s/^([^=]+)=//s) {
270             $name = lc $1;
271             $name =~ s/^\s*//s;
272             $name =~ s/\s*$//s;
273             $attr =~ s/^\s*//s;
274             if ($attr =~ m/^('|")/s) {
275             my $quote = $1;
276             $attr =~ s/^$quote(.*?)$quote//s;
277             $value = $1;
278             }
279             else {
280             $attr =~ s/^(.*?)\s*//s;
281             $value = $1;
282             }
283             $attrs->{$name} = $value;
284             }
285             }
286              
287             next if defined $4;
288             my $hash = {
289             name => lc $2,
290             content => $5,
291             attrs => $attrs
292             };
293             push(@$flat, $hash);
294             }
295              
296             return $flat;
297             }
298              
299             1;
300             __END__