File Coverage

blib/lib/AnyEvent/HTTP/Socks.pm
Criterion Covered Total %
statement 95 109 87.1
branch 32 46 69.5
condition 6 12 50.0
subroutine 20 24 83.3
pod 0 5 0.0
total 153 196 78.0


line stmt bran cond sub pod time code
1             package AnyEvent::HTTP::Socks;
2              
3 16     16   1054240 use strict;
  16         32  
  16         544  
4 16     16   64 use Socket;
  16         16  
  16         6656  
5 16     16   96 use IO::Socket::Socks;
  16         176  
  16         2112  
6 16     16   9376 use AnyEvent::Socket;
  16         328448  
  16         1824  
7 16     16   144 use Errno;
  16         16  
  16         448  
8 16     16   48 use Carp;
  16         16  
  16         736  
9 16     16   48 use base 'Exporter';
  16         16  
  16         1664  
10             require AnyEvent::HTTP;
11              
12             our $VERSION = '0.05';
13              
14             our @EXPORT = qw(
15             http_get
16             http_head
17             http_post
18             http_request
19             );
20              
21             use constant {
22 16         17376 READ_WATCHER => 1,
23             WRITE_WATCHER => 2,
24 16     16   64 };
  16         16  
25              
26             sub http_get($@) {
27 7     7 0 11775 unshift @_, 'GET';
28 7         21 &http_request;
29             }
30              
31             sub http_head($@) {
32 0     0 0 0 unshift @_, 'HEAD';
33 0         0 &http_request;
34             }
35              
36             sub http_post($$@) {
37 0     0 0 0 my $url = shift;
38 0         0 unshift @_, 'POST', $url, 'body';
39 0         0 &http_request;
40             }
41              
42             sub http_request($$@) {
43 7     7 0 19 my ($method, $url, $cb) = (shift, shift, pop);
44 7         24 my %opts = @_;
45            
46 7         18 my $socks = delete $opts{socks};
47 7 50       14 if ($socks) {
48 7         7 my @chain;
49 7         65 while ($socks =~ m!socks(4|4a|5)://(?:([^\s:]+):([^\s@]*)@)?(\[[0-9a-f:.]+\]|[^\s:]+):(\d+)!gi) {
50 10         117 push @chain, {ver => $1, login => $2, pass => $3, host => $4, port => $5};
51             }
52            
53 7 50       16 if (@chain) {
54             $opts{tcp_connect} = sub {
55 7     7   701 my ($cv, $watcher, $timer, $sock);
56 7         8 my @tmp_chain = @chain; # copy: on redirect @tmp_chain will be already empty
57 7         22 _socks_prepare_connection(\$cv, \$watcher, \$timer, $sock, \@tmp_chain, @_);
58 7         51 };
59             }
60             else {
61 0         0 croak 'unsupported socks address specified';
62             }
63             }
64            
65 7         40 AnyEvent::HTTP::http_request( $method, $url, %opts, $cb );
66             }
67              
68             sub inject {
69 0     0 0 0 my ($class, $where) = @_;
70 0         0 $class->export($where, @EXPORT);
71             }
72              
73             sub _socks_prepare_connection {
74 10     10   30 my ($cv, $watcher, $timer, $sock, $chain, $c_host, $c_port, $c_cb, $p_cb) = @_;
75            
76 10 100       27 unless ($sock) { # first connection in the chain
77             # XXX: need also support IPv6 when SOCKS host is a domain name, but this is not so easy
78 7 50       51 socket(
    50          
79             $sock,
80             $chain->[0]{host} =~ /^\[.+\]$/ ? PF_INET6 : PF_INET,
81             SOCK_STREAM,
82             getprotobyname('tcp')
83             )
84             or return $c_cb->();
85            
86 7         166 my $timeout = $p_cb->($sock);
87             $$timer = AnyEvent->timer(
88             after => $timeout,
89             cb => sub {
90 1     1   969 undef $$watcher;
91 1         8 undef $$cv;
92 1         4 $! = Errno::ETIMEDOUT;
93 1         3 $c_cb->();
94             }
95 7         57 );
96            
97 7   33     108 $_->{host} =~ s/^\[// and $_->{host} =~ s/\]$// for @$chain;
98             }
99            
100             $$cv = AE::cv {
101 10     10   132 _socks_connect($cv, $watcher, $timer, $sock, $chain, $c_host, $c_port, $c_cb);
102 10         313 };
103            
104 10         88 $$cv->begin;
105            
106 10         53 $$cv->begin;
107             inet_aton $chain->[0]{host}, sub {
108 10     10   362 $chain->[0]{host} = format_address shift;
109 10 50       192 $$cv->end if $$cv;
110 10         83 };
111            
112 10 100 66     158 if (($chain->[0]{ver} == 5 && $IO::Socket::Socks::SOCKS5_RESOLVE == 0) ||
      66        
      33        
113             ($chain->[0]{ver} eq '4' && $IO::Socket::Socks::SOCKS4_RESOLVE == 0)) { # 4a = 4
114             # resolving on the client side enabled
115 3 50       8 my $host = @$chain > 1 ? \$chain->[1]{host} : \$c_host;
116 3         10 $$cv->begin;
117            
118             inet_aton $$host, sub {
119 3     3   111 $$host = format_address shift;
120 3 50       33 $$cv->end if $$cv;
121             }
122 3         25 }
123            
124 10         37 $$cv->end;
125            
126 10         448 return $sock;
127             }
128              
129             sub _socks_connect {
130 10     10   23 my ($cv, $watcher, $timer, $sock, $chain, $c_host, $c_port, $c_cb) = @_;
131 10         18 my $link = shift @$chain;
132            
133 10         8 my @specopts;
134 10 50       22 if ($link->{ver} eq '4a') {
135 0         0 $link->{ver} = 4;
136 0         0 push @specopts, SocksResolve => 1;
137             }
138            
139 10 100       20 if (defined $link->{login}) {
140 3         7 push @specopts, Username => $link->{login};
141 3 50       7 if ($link->{ver} == 5) {
142 3         21 push @specopts, Password => $link->{pass}, AuthType => 'userpass';
143             }
144             }
145            
146 10 100       32 my ($host, $port) = @$chain ? ($chain->[0]{host}, $chain->[0]{port}) : ($c_host, $c_port);
147            
148 10 100       19 if (ref($sock) eq 'GLOB') {
149             # not connected socket
150 7 50       51 $sock = IO::Socket::Socks->new_from_socket(
151             $sock,
152             Blocking => 0,
153             ProxyAddr => $link->{host},
154             ProxyPort => $link->{port},
155             SocksVersion => $link->{ver},
156             ConnectAddr => $host,
157             ConnectPort => $port,
158             @specopts
159             ) or return $c_cb->();
160             }
161             else {
162 3 50       25 $sock->command(
163             SocksVersion => $link->{ver},
164             ConnectAddr => $host,
165             ConnectPort => $port,
166             @specopts
167             ) or return $c_cb->();
168             }
169            
170 10 100       4841 my ($poll, $w_type) = $SOCKS_ERROR == SOCKS_WANT_READ ?
171             ('r', READ_WATCHER) :
172             ('w', WRITE_WATCHER);
173            
174             $$watcher = AnyEvent->io(
175             fh => $sock,
176             poll => $poll,
177 13     13   5011388 cb => sub { _socks_handshake($cv, $watcher, $w_type, $timer, $sock, $chain, $c_host, $c_port, $c_cb) }
178 10         188 );
179             }
180              
181             sub _socks_handshake {
182 24     24   81 my ($cv, $watcher, $w_type, $timer, $sock, $chain, $c_host, $c_port, $c_cb) = @_;
183            
184 24 100       119 if ($sock->ready) {
185 8         2197 undef $$watcher;
186            
187 8 100       36 if (@$chain) {
188 3         12 return _socks_prepare_connection($cv, $watcher, $timer, $sock, $chain, $c_host, $c_port, $c_cb);
189             }
190            
191 5         12 undef $$timer;
192 5         88 return $c_cb->($sock);
193             }
194            
195 16 50       7419 if ($SOCKS_ERROR == SOCKS_WANT_WRITE) {
    100          
196 0 0       0 if ($w_type != WRITE_WATCHER) {
197 0         0 undef $$watcher;
198             $$watcher = AnyEvent->io(
199             fh => $sock,
200             poll => 'w',
201 0     0   0 cb => sub { _socks_handshake($cv, $watcher, WRITE_WATCHER, $timer, $sock, $chain, $c_host, $c_port, $c_cb) }
202 0         0 );
203             }
204             }
205             elsif ($SOCKS_ERROR == SOCKS_WANT_READ) {
206 15 100       404 if ($w_type != READ_WATCHER) {
207 7         9 undef $$watcher;
208             $$watcher = AnyEvent->io(
209             fh => $sock,
210             poll => 'r',
211 11     11   3977223 cb => sub { _socks_handshake($cv, $watcher, READ_WATCHER, $timer, $sock, $chain, $c_host, $c_port, $c_cb) }
212 7         45 );
213             }
214             }
215             else {
216             # unknown error
217 1         25 $@ = "IO::Socket::Socks: $SOCKS_ERROR";
218 1         13 undef $$watcher;
219 1         2 undef $$timer;
220 1         10 $c_cb->();
221             }
222             }
223              
224             1;
225             __END__