File Coverage

blib/lib/Net/BitTorrent/Torrent/Tracker/HTTP.pm
Criterion Covered Total %
statement 119 169 70.4
branch 38 56 67.8
condition 6 10 60.0
subroutine 18 26 69.2
pod 3 3 100.0
total 184 264 69.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             package Net::BitTorrent::Torrent::Tracker::HTTP;
3             {
4 11     11   180792 use strict;
  11         29  
  11         399  
5 11     11   56 use warnings;
  11         21  
  11         301  
6 11     11   1273 use Carp qw[carp];
  11         21  
  11         558  
7 11     11   82 use Scalar::Util qw[blessed weaken refaddr];
  11         21  
  11         706  
8 11     11   63 use List::Util qw[sum];
  11         22  
  11         1012  
9 11     11   1902 use Socket qw[PF_INET SOMAXCONN SOCK_STREAM inet_aton pack_sockaddr_in];
  11         8774  
  11         1174  
10 11     11   68 use Fcntl qw[F_SETFL O_NONBLOCK];
  11         22  
  11         694  
11 11     11   57 use lib q[../../../../../lib];
  11         19  
  11         69  
12 11     11   3056 use Net::BitTorrent::Util qw[:bencode :compact];
  11         25  
  11         1888  
13 11     11   65 use version qw[qv];
  11         28  
  11         94  
14             our $VERSION_BASE = 50; our $UNSTABLE_RELEASE = 0; our $VERSION = sprintf(($UNSTABLE_RELEASE ? q[%.3f_%03d] : q[%.3f]), (version->new(($VERSION_BASE))->numify / 1000), $UNSTABLE_RELEASE);
15             my (@CONTENTS)
16             = \my (%_url, %_tier, %resolve,
17             %_event, %_socket, %_data_out,
18             %_peers, %_complete, %_incomplete
19             );
20             my %REGISTRY;
21              
22             sub new {
23 27     27 1 128 my ($class, $args) = @_;
24 27 100       96 if (!$args) {
25 1         337 carp q[Net::[...]Tracker::HTTP->new({}) requires params];
26 1         13 return;
27             }
28 26 100 66     213 if ((!$args->{q[URL]}) || ($args->{q[URL]} !~ m[^http://]i)) {
29 3         451 carp q[Net::[...]Tracker::HTTP->new({}) requires a valid URL];
30 3         93 return;
31             }
32 23 100 66     374 if ( (!$args->{q[Tier]})
33             || (!$args->{q[Tier]}->isa(q[Net::BitTorrent::Torrent::Tracker])))
34 3         419 { carp
35             q[Net::[...]Tracker::HTTP->new({}) requires a parent Tracker];
36 3         115 return;
37             }
38 20         98 my $self = bless \$args->{q[URL]}, $class;
39 20         141 $_url{refaddr $self} = $args->{q[URL]};
40 20         85 $_tier{refaddr $self} = $args->{q[Tier]};
41 20         109 $_peers{refaddr $self} = q[];
42 20         73 $_complete{refaddr $self} = 0;
43 20         108 $_incomplete{refaddr $self} = 0;
44 20         107 weaken $_tier{refaddr $self};
45 20         139 weaken($REGISTRY{refaddr $self} = $self);
46 20         126 return $self;
47             }
48              
49             # Accessors | Public
50 4     4 1 1116 sub url { my ($self) = @_; return $_url{refaddr $self}; }
  4         33  
51              
52             # Accesors | Private
53 48     48   173 sub _socket { return $_socket{refaddr +shift}; }
54 0     0   0 sub _tier { return $_tier{refaddr +shift}; }
55 43     43   446 sub _peers { return $_peers{refaddr +shift}; }
56 0     0   0 sub _client { return $_tier{refaddr +shift}->_client }
57              
58             # Methods | Private
59             sub _announce {
60 16     16   58 my ($self, $event) = @_;
61              
62             #warn sprintf q[_announce(%s)], $event? qq['$event']:q[];
63 16 100       57 if (defined $event) {
64 15 100       125 if ($event !~ m[^(?:st(?:art|opp)|complet)ed$]) {
65 1         168 carp sprintf q[Invalid event for announce: %s], $event;
66 1         55 return;
67             }
68 14         65 $_event{refaddr $self} = $event;
69             }
70 15         326 my ($host, $port, $path)
71             = $_url{refaddr $self} =~ m{^http://([^/:]*)(?::(\d+))?(/.*)$};
72 15 100       47 $port = $port ? $port : 80;
73 15         33 my $packed_host = undef;
74 15 100       123 if ($host
75             !~ m[^(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.]?){4})$])
76 5 50       6231 { my ($name, $aliases, $addrtype, $length, @addrs)
77             = gethostbyname($host)
78             or return;
79 5         23 $packed_host = $addrs[0];
80             }
81 10         65 else { $packed_host = inet_aton($host) }
82 15 50       2985 socket($_socket{refaddr $self},
83             PF_INET, SOCK_STREAM, getprotobyname(q[tcp]))
84             or return;
85 15 50       242 if (not($^O eq q[MSWin32]
    50          
86             ? ioctl($_socket{refaddr $self}, 0x8004667e, pack(q[I], 1))
87             : fcntl($_socket{refaddr $self}, F_SETFL, O_NONBLOCK)
88             )
89             )
90 0         0 { $_tier{refaddr $self}->_torrent->_event(
91             q[tracker_failure],
92             {Tracker => $self,
93             Reason => sprintf(
94             q[There was a problem making an outgoing socket non-blocking: [%d] %s],
95             $^E, $^E
96             )
97             }
98             );
99 0         0 return;
100             }
101 15         5569 my $_inet_aton = inet_aton($host);
102 15 50       61 if (!$_inet_aton) {
103 0         0 $_tier{refaddr $self}->_torrent->_event(
104             q[tracker_failure],
105             {Tracker => $self,
106             Reason => sprintf(q[Cannot resolve host: %s], $host)
107             }
108             );
109 0         0 return;
110             }
111 15         80 my $pack_sockaddr_in = pack_sockaddr_in($port, $_inet_aton);
112 15 50       46 if (!$pack_sockaddr_in) {
113 0         0 $_tier{refaddr $self}->_torrent->_event(
114             q[tracker_failure],
115             {Tracker => $self,
116             Reason => sprintf(q[Cannot resolve host: %s], $host)
117             }
118             );
119 0         0 return;
120             }
121 15         1449 connect($_socket{refaddr $self}, $pack_sockaddr_in);
122 15 100       153 $_tier{refaddr $self}->_torrent->_event(q[tracker_connect],
123             {Tracker => $self,
124             (defined $event
125             ? (Event => $event)
126             : ()
127             )
128             }
129             );
130 15         113 my $infohash = $_tier{refaddr $self}->_torrent->infohash;
131 15         418 $infohash =~ s|(..)|\%$1|g;
132 15 100 50     120 my %query_hash = (
      50        
133             q[info_hash] => $infohash,
134             q[peer_id] => $_tier{refaddr $self}->_client->peerid(),
135             q[port] => ($_tier{refaddr $self}->_client->_tcp_port() || 0),
136             q[uploaded] => $_tier{refaddr $self}->_torrent->uploaded(),
137             q[downloaded] => $_tier{refaddr $self}->_torrent->downloaded(),
138             q[left] => (
139             $_tier{refaddr $self}->_torrent->raw_data(1)
140             ->{q[info]}{q[piece length]} * sum(
141             split(
142             q[],
143             unpack(
144             q[b*],
145             ($_tier{refaddr $self}->_torrent->_wanted() || q[]
146             )
147             )
148             )
149             )
150             ),
151             q[key] => $^T,
152             q[numwant] => 200,
153             q[compact] => 1,
154             q[no_peer_id] => 1,
155             ($_event{refaddr $self}
156             ? (q[event] => $_event{refaddr $self})
157             : ()
158             )
159             );
160 164         639 my $url
161             = $path
162             . ($path =~ m[\?] ? q[&] : q[?])
163             . (join q[&],
164 15 50       492 map { sprintf q[%s=%s], $_, $query_hash{$_} }
165             sort keys %query_hash
166             );
167 15         169 $_data_out{refaddr $self} =
168             join(qq[\015\012],
169             qq[GET $url HTTP/1.0],
170             q[Connection: close],
171             qq[Host: $host:$port],
172             q[Accept: text/plain],
173             q[Accept-Encoding:],
174             qq[User-Agent: Net::BitTorrent/] . $Net::BitTorrent::VERSION,
175             q[],
176             q[]);
177 15         92 $_tier{refaddr $self}->_client->_remove_connection($self);
178 15         101 return $_tier{refaddr $self}->_client->_add_connection($self, q[wo]);
179             }
180              
181             sub _rw {
182 12     12   31 my ($self, $read, $write, $error) = @_;
183              
184             #Carp::cluck sprintf q[%s->_rw(%d, %d, %d)], __PACKAGE__, $read, $write, $error;
185 12         29 my ($actual_read, $actual_write) = (0, 0);
186 12 50       81 return if not defined $_tier{refaddr $self}->_client;
187 12 50       51 if ($error) {
    100          
    50          
188 0         0 $_tier{refaddr $self}->_client->_remove_connection($self);
189 0         0 shutdown($_socket{refaddr $self}, 2);
190 0         0 close $_socket{refaddr $self};
191             $_tier{refaddr $self}->_client->_schedule(
192             { Time => time + 30,
193             Code => sub {
194 0     0   0 my ($s) = @_;
195 0         0 $_tier{refaddr $s}->_shuffle;
196 0         0 return $_tier{refaddr $s}->_announce();
197             },
198 0         0 Object => $self
199             }
200             );
201 0         0 return;
202             }
203             elsif ($write) {
204 6         317 $actual_write = syswrite($_socket{refaddr $self},
205             $_data_out{refaddr $self}, $write);
206 6 50       20 if (!$actual_write) {
207 0         0 $_tier{refaddr $self}->_torrent->_event(
208             q[tracker_failure],
209             {Tracker => $self,
210             Reason => sprintf(q[Cannot write to tracker: %s], $^E)
211             }
212             );
213 0         0 $_tier{refaddr $self}->_client->_remove_connection($self);
214 0         0 shutdown($_socket{refaddr $self}, 2);
215 0         0 close $_socket{refaddr $self};
216             $_tier{refaddr $self}->_client->_schedule(
217             { Time => time + 300,
218             Code => sub {
219 0     0   0 my ($s) = @_;
220 0         0 $s->_tier->_shuffle;
221 0         0 return $s->_tier->_announce();
222             },
223 0         0 Object => $self
224             }
225             );
226 0         0 return;
227             }
228 6         39 $_tier{refaddr $self}->_torrent->_event(q[tracker_write],
229             {Tracker => $self, Length => $actual_write});
230 6         43 substr($_data_out{refaddr $self}, 0, $actual_write, q[]);
231 6 50       26 if (!length $_data_out{refaddr $self}) {
232 6         123 shutdown($_socket{refaddr $self}, 1);
233 6         31 $_tier{refaddr $self}->_client->_remove_connection($self);
234 6         30 $_tier{refaddr $self}->_client->_add_connection($self, q[ro]);
235             }
236             }
237             elsif ($read) {
238 6         189 $actual_read
239             = sysread($_socket{refaddr $self}, my ($data), $read, 0);
240 6 50       22 if (not $actual_read) {
241 0         0 $_tier{refaddr $self}->_torrent->_event(
242             q[tracker_failure],
243             {Tracker => $self,
244             Reason => sprintf(q[Cannot read from tracker: %s], $^E)
245             }
246             );
247 0         0 $_tier{refaddr $self}->_client->_remove_connection($self);
248 0         0 shutdown($_socket{refaddr $self}, 2);
249 0         0 close $_socket{refaddr $self};
250             $_tier{refaddr $self}->_client->_schedule(
251             { Time => time + 300,
252             Code => sub {
253 0     0   0 my ($s) = @_;
254 0         0 $_tier{refaddr $s}->_shuffle;
255 0         0 return $_tier{refaddr $s}->_announce();
256             },
257 0         0 Object => $self
258             }
259             );
260 0         0 return;
261             }
262             else {
263 6         42 $_tier{refaddr $self}->_torrent->_event(q[tracker_read],
264             {Tracker => $self, Length => $actual_read});
265 6         107 $data =~ s[^.+(?:\015?\012){2}][]s;
266 6         40 $data = bdecode($data);
267 6 50       23 if ($data) {
268 6 50       20 if (defined $data->{q[failure reason]}) {
269 0         0 $_tier{refaddr $self}->_torrent->_event(
270             q[tracker_failure],
271             {Tracker => $self,
272             Reason => $data->{q[failure reason]}
273             }
274             );
275             }
276             else {
277 6         50 $_peers{refaddr $self} = $data->{q[peers]};
278 6         23 $_complete{refaddr $self} = $data->{q[complete]};
279 6         19 $_incomplete{refaddr $self} = $data->{q[incomplete]};
280 6         34 $_tier{refaddr $self}
281             ->_torrent->_event(q[tracker_success],
282             {Tracker => $self, Payload => $data});
283 6         4146 delete $_event{refaddr $self};
284             }
285             }
286             $_tier{refaddr $self}->_client->_schedule(
287             { Time => (time + (defined $data->{q[interval]}
288             ? $data->{q[interval]}
289             : 1800
290             )
291             ),
292             Code =>
293 0     0   0 sub { return $_tier{refaddr +shift}->_announce() }
294             ,
295 6 50       37 Object => $self
296             }
297             );
298             }
299 6         43 $_tier{refaddr $self}->_client->_remove_connection($self);
300 6         58 shutdown($_socket{refaddr $self}, 2);
301 6         193 close $_socket{refaddr $self};
302 6         37 $_tier{refaddr $self}
303             ->_torrent->_event(q[tracker_disconnect], {Tracker => $self});
304             }
305             else {
306 0         0 $_tier{refaddr $self}->_torrent->_event(
307             q[tracker_failure],
308             {Tracker => $self,
309             Reason => q[Failed to read from tracker]
310             }
311             );
312             $_tier{refaddr $self}->_client->_schedule(
313             { Time => time + 300,
314             Code =>
315 0     0   0 sub { return $_tier{refaddr +shift}->_announce(); },
316 0         0 Object => $self
317             }
318             );
319 0         0 return;
320             }
321 12         47 return ($actual_read, $actual_write);
322             }
323              
324             sub as_string {
325 1     1 1 3 my ($self, $advanced) = @_;
326 1 50       82 my $dump = !$advanced ? $$self : sprintf <<'END',
327             Net::BitTorrent::Torrent::Tracker::HTTP
328              
329             URL: %s
330             END
331             $_url{refaddr $self};
332 1 50       7 return defined wantarray ? $dump : print STDERR qq[$dump\n];
333             }
334              
335             sub CLONE {
336 0     0   0 for my $_oID (keys %REGISTRY) {
337 0         0 my $_obj = $REGISTRY{$_oID};
338 0         0 my $_nID = refaddr $_obj;
339 0         0 for (@CONTENTS) {
340 0         0 $_->{$_nID} = $_->{$_oID};
341 0         0 delete $_->{$_oID};
342             }
343 0         0 weaken $_tier{$_nID};
344 0         0 weaken($REGISTRY{$_nID} = $_obj);
345 0         0 delete $REGISTRY{$_oID};
346             }
347 0         0 return 1;
348             }
349             DESTROY {
350 11     11   1347 my ($self) = @_;
351 11         31 for (@CONTENTS) { delete $_->{refaddr $self}; }
  99         261  
352 11         92 return delete $REGISTRY{refaddr $self};
353             }
354             1;
355             }
356              
357             =pod
358              
359             =head1 NAME
360              
361             Net::BitTorrent::Torrent::Tracker::HTTP - Single HTTP BitTorrent Tracker
362              
363             =head1 Constructor
364              
365             =over 4
366              
367             =item C
368              
369             Creates a C object. This
370             constructor should not be used directly.
371              
372             =back
373              
374             =head1 Methods
375              
376             =over
377              
378             =item C
379              
380             Returns the related HTTP URL according to the original metadata.
381              
382             =item C
383              
384             Returns a 'ready to print' dump of the object's data structure. If
385             called in void context, the structure is printed to C.
386             C is a boolean value.
387              
388             =back
389              
390             =head1 BUGS/TODO
391              
392             =over 4
393              
394             =item *
395              
396             Does not support HTTPS trackers.
397              
398             =back
399              
400             =head1 Author
401              
402             Sanko Robinson - http://sankorobinson.com/
403              
404             CPAN ID: SANKO
405              
406             =head1 License and Legal
407              
408             Copyright (C) 2008-2009 by Sanko Robinson Esanko@cpan.orgE
409              
410             This program is free software; you can redistribute it and/or modify
411             it under the terms of The Artistic License 2.0. See the F
412             file included with this distribution or
413             http://www.perlfoundation.org/artistic_license_2_0. For
414             clarification, see http://www.perlfoundation.org/artistic_2_0_notes.
415              
416             When separated from the distribution, all POD documentation is covered
417             by the Creative Commons Attribution-Share Alike 3.0 License. See
418             http://creativecommons.org/licenses/by-sa/3.0/us/legalcode. For
419             clarification, see http://creativecommons.org/licenses/by-sa/3.0/us/.
420              
421             Neither this module nor the L is affiliated with
422             BitTorrent, Inc.
423              
424             =for svn $Id: HTTP.pm d3c97de 2009-09-12 04:31:46Z sanko@cpan.org $
425              
426             =cut