File Coverage

blib/lib/Net/Proxy/Type.pm
Criterion Covered Total %
statement 194 269 72.1
branch 81 174 46.5
condition 32 110 29.0
subroutine 28 31 90.3
pod 14 14 100.0
total 349 598 58.3


line stmt bran cond sub pod time code
1             package Net::Proxy::Type;
2              
3 3     3   153430 use strict;
  3         9  
  3         205  
4 3     3   19 use Exporter;
  3         6  
  3         143  
5 3     3   2132 use Errno qw(EWOULDBLOCK EAGAIN);
  3         3372  
  3         478  
6 3     3   20 use Carp;
  3         6  
  3         318  
7 3     3   2430 use IO::Socket::INET qw(:DEFAULT :crlf);
  3         58487  
  3         27  
8 3     3   6937 use IO::Select;
  3         6589  
  3         261  
9              
10             use constant {
11 3         2021 UNKNOWN_PROXY => 4294967296,
12             DEAD_PROXY => 0,
13             HTTP_PROXY => 1,
14             SOCKS4_PROXY => 2,
15             SOCKS5_PROXY => 4,
16             HTTPS_PROXY => 8,
17             CONNECT_PROXY => 16,
18 3     3   24 };
  3         7  
19              
20             our $VERSION = '0.08';
21             our @ISA = qw(Exporter);
22             our @EXPORT_OK = qw(HTTP_PROXY HTTPS_PROXY CONNECT_PROXY SOCKS4_PROXY SOCKS5_PROXY UNKNOWN_PROXY DEAD_PROXY);
23             our %EXPORT_TAGS = (types => [qw(HTTP_PROXY HTTPS_PROXY CONNECT_PROXY SOCKS4_PROXY SOCKS5_PROXY UNKNOWN_PROXY DEAD_PROXY)]);
24              
25             our $CONNECT_TIMEOUT = 5;
26             our $WRITE_TIMEOUT = 5;
27             our $READ_TIMEOUT = 5;
28             our $URL = 'http://www.google.com/';
29             our $HTTPS_URL = 'https://www.google.com/';
30             our $KEYWORD = 'google';
31             our $HTTPS_KEYWORD = 'google';
32             our $HTTP_VER = '1.1';
33             our %NAME = (
34             UNKNOWN_PROXY, 'UNKNOWN_PROXY',
35             DEAD_PROXY, 'DEAD_PROXY',
36             HTTP_PROXY, 'HTTP_PROXY',
37             HTTPS_PROXY, 'HTTPS_PROXY',
38             CONNECT_PROXY, 'CONNECT_PROXY',
39             SOCKS4_PROXY, 'SOCKS4_PROXY',
40             SOCKS5_PROXY, 'SOCKS5_PROXY',
41             );
42              
43             sub new
44             {
45 1     1 1 13 my ($class, %opts) = @_;
46 1         4 my $self = bless {}, $class;
47            
48 1   33     19 $self->{connect_timeout} = $opts{connect_timeout} || $opts{timeout} || $CONNECT_TIMEOUT;
49 1   33     9 $self->{write_timeout} = $opts{write_timeout} || $opts{timeout} || $WRITE_TIMEOUT;
50 1   33     11 $self->{read_timeout} = $opts{read_timeout} || $opts{timeout} || $READ_TIMEOUT;
51 1   33     7 $self->{http_strict} = $opts{http_strict} || $opts{strict};
52 1   33     7 $self->{https_strict} = $opts{https_strict} || $opts{strict};
53 1   33     5 $self->{connect_strict} = $opts{connect_strict} || $opts{strict};
54 1   33     5 $self->{socks4_strict} = $opts{socks4_strict} || $opts{strict};
55 1   33     5 $self->{socks5_strict} = $opts{socks5_strict} || $opts{strict};
56 1   33     4 $self->{http_ver} = $opts{http_ver} || $HTTP_VER;
57 1   33     5 $self->{keyword} = $opts{keyword} || $KEYWORD;
58 1   33     4 $self->{https_keyword} = $opts{https_keyword} || $HTTPS_KEYWORD;
59 1         3 $self->{noauth} = $opts{noauth};
60 1   33     7 $self->url($opts{url} || $URL);
61 1   33     7 $self->https_url($opts{https_url} || $HTTPS_URL);
62            
63 1         4 $self;
64             }
65              
66             foreach my $key (qw(
67             connect_timeout write_timeout read_timeout http_strict https_strict
68             connect_strict socks4_strict socks5_strict keyword https_keyword noauth http_ver
69             ))
70             { # generate sub's for get/set object properties using closure
71 3     3   18 no strict 'refs';
  3         7  
  3         12001  
72             *$key = sub
73             {
74 1     1   1309 my $self = shift;
75            
76 1 50       46 return $self->{$key} = $_[0] if defined $_[0];
77 0         0 return $self->{$key};
78             }
79             }
80              
81             sub timeout
82             { # set timeout for all operations
83 1     1 1 32 my ($self, $timeout) = @_;
84            
85 1         16 $self->{connect_timeout} = $timeout;
86 1         9 $self->{write_timeout} = $timeout;
87 1         10 $self->{read_timeout} = $timeout;
88             }
89              
90             sub strict
91             { # set strict mode for all proxy types
92 4     4 1 2646 my ($self, $strict) = @_;
93            
94 4         49 $self->{http_strict} = $strict;
95 4         13 $self->{https_strict} = $strict;
96 4         8 $self->{connect_strict} = $strict;
97 4         9 $self->{socks4_strict} = $strict;
98 4         13 $self->{socks5_strict} = $strict;
99             }
100              
101             sub url
102             { # set or get url
103 1     1 1 2 my $self = shift;
104            
105 1 50       21 if(defined($_[0])) {
106 1 50       14 ($self->{host}) = $_[0] =~ m!^http://([^:/]+)!
107             or croak('Incorrect url specified. Should be http://[^:/]+');
108 1         3 return $self->{url} = $_[0];
109             }
110            
111 0         0 return $self->{url};
112             }
113              
114             sub https_url
115             { # set or get https url
116 1     1 1 1 my $self = shift;
117            
118 1 50       4 if(defined($_[0])) {
119 1 50       10 ($self->{https_host}, $self->{https_pathquery}) = $_[0] =~ m!^https://([^:/]+)(/.*)?!
120             or croak('Incorrect url specified. Should be https://[^:/]+(/.*)?');
121 1         4 return $self->{https_url} = $_[0];
122             }
123            
124 0         0 return $self->{https_url};
125             }
126              
127             my @checkers = (
128             CONNECT_PROXY, \&is_connect,
129             HTTPS_PROXY, \&is_https,
130             HTTP_PROXY, \&is_http,
131             SOCKS4_PROXY, \&is_socks4,
132             SOCKS5_PROXY, \&is_socks5
133             );
134              
135             sub _get
136             { # base get method
137 6     6   11 my $self = shift;
138 6         9 my $max = pop;
139 6         25 my ($proxyaddr, $proxyport, $checkmask);
140 0         0 my @found;
141            
142 6 50       92 if (@_ == 3) {
    50          
    50          
143 0         0 ($proxyaddr, $proxyport, $checkmask) = @_;
144             }
145             elsif (($proxyaddr, $proxyport) = _parse_proxyaddr($_[0])) {
146 0         0 $checkmask = $_[1];
147             }
148             elsif (@_ == 2) {
149 6         36 ($proxyaddr, $proxyport) = @_;
150             }
151             else {
152 0         0 push @found, [DEAD_PROXY, 0];
153 0         0 return \@found;
154             }
155            
156 6         7 my ($ok, $con_time);
157 6         20 for(my $i=0; $i<@checkers; $i+=2) {
158 13 50       39 if(defined($checkmask)) {
159 0 0       0 unless($checkers[$i] & $checkmask) {
160 0         0 next;
161             }
162             }
163            
164 13         68 ($ok, $con_time) = $checkers[$i+1]->($self, $proxyaddr, $proxyport);
165            
166 13 100       99 if($ok) {
    100          
167 3         10 push @found, [$checkers[$i], $con_time];
168 3 50       23 last if @found == $max;
169             }
170             elsif(!defined($ok)) {
171 2         7 push @found, [DEAD_PROXY, 0];
172 2         4 last;
173             }
174             }
175            
176 6 100       19 unless (@found) {
177 1         10 push @found, [UNKNOWN_PROXY, $con_time];
178             }
179            
180 6         40 return \@found;
181             }
182              
183             sub get
184             { # get proxy type
185 6     6 1 1082 my $self = shift;
186            
187 6         84 my $found = $self->_get(@_, 1);
188 6 100       102 return wantarray ? @{$found->[0]} : $found->[0][0];
  1         5  
189             }
190              
191             sub get_as_string
192             { # same as get(), but return string
193 0     0 1 0 my ($self, $proxyaddr, $proxyport, $checkmask) = @_;
194            
195 0         0 my $type = $self->get($proxyaddr, $proxyport, $checkmask);
196 0         0 return $NAME{$type};
197             }
198              
199             sub get_all
200             { # get all proxy types
201 0     0 1 0 my $self = shift;
202            
203 0         0 my $found = $self->_get(@_, 0);
204            
205 0         0 my $types = 0;
206 0         0 my $con_time = 0;
207            
208 0         0 for my $t (@$found) {
209 0         0 $types |= $t->[0];
210 0 0       0 if ($t->[1] > $con_time) {
211 0         0 $con_time = $t->[1];
212             }
213             }
214            
215 0 0       0 return wantarray ? ($types, $con_time) : $types;
216             }
217              
218             sub get_all_as_string
219             { # same as get_all(), but return string array
220 0     0 1 0 my $self = shift;
221            
222 0         0 my @names = map { $NAME{$_->[0]} } @{$self->_get(@_, 0)};
  0         0  
  0         0  
223 0         0 return @names;
224             }
225              
226             sub is_http
227             { # check is this http proxy
228 3     3 1 10209 my ($self, $proxyaddr, $proxyport) = @_;
229            
230 3 50       39 my ($socket, $con_time) = $self->_create_socket($proxyaddr, $proxyport)
231             or return;
232            
233             # simply do http request
234 3 50       34 unless($self->_http_request($socket)) {
235 0         0 goto IS_HTTP_ERROR;
236             }
237            
238 3         13 my ($buf, $rc);
239 3 100       39 unless($self->{http_strict}) {
240             # simple check. does response begins from `HTTP'?
241 1         5 $rc = $self->_read_from_socket($socket, $buf, 12);
242 1         18 my ($code) = $buf =~ /(\d+$)/;
243 1 50 33     5 if ($code == 407 && $self->{noauth}) {
244             # proxy auth required
245 0         0 goto IS_HTTP_ERROR;
246             }
247            
248 1 50 33     14 if(!$rc || substr($buf, 0, 4) ne 'HTTP') {
249 0         0 goto IS_HTTP_ERROR;
250             }
251             }
252             else {
253             # strict check. does response header contains keyword?
254 2 100       11 unless($self->_is_strict_response($socket, $self->{keyword})) {
255 1         10 goto IS_HTTP_ERROR;
256             }
257             }
258            
259 2         9 $socket->close();
260 2 100       135 return wantarray ? (1, $con_time) : 1;
261            
262 1         5 IS_HTTP_ERROR:
263             $socket->close();
264 1 50       88 return wantarray ? (0, $con_time) : 0;
265             }
266              
267             sub is_connect
268             { # check is this conenct proxy
269 7     7 1 16 my ($self, $proxyaddr, $proxyport) = @_;
270            
271 7 100       25 my ($socket, $con_time) = $self->_create_socket($proxyaddr, $proxyport)
272             or return;
273            
274 5 50       76 $self->_write_to_socket(
275             $socket,
276             'CONNECT '.$self->{host}.':80 HTTP/1.1'.CRLF.'Host: '.$self->{host}.':80'.CRLF.CRLF
277             ) or goto IS_CONNECT_ERROR;
278            
279 5 100       15155 $self->_read_from_socket($socket, my $headers, CRLF.CRLF, 2000)
280             or goto IS_CONNECT_ERROR;
281 4 50       85 my ($code) = $headers =~ m!^HTTP/\d.\d (\d{3})!
282             or goto IS_CONNECT_ERROR;
283 4 0 0     25 if ($code == 407 && ($self->{noauth} || $self->{connect_strict})) {
      33        
284 0         0 goto IS_CONNECT_ERROR;
285             }
286 4 100 66     74 if (($code < 200 || $code >= 300) && $code != 407) {
      66        
287 3         17 goto IS_CONNECT_ERROR;
288             }
289 1 50       33 if ($self->{connect_strict}) {
290 0 0       0 unless($self->_http_request($socket)) {
291 0         0 goto IS_CONNECT_ERROR;
292             }
293            
294 0 0       0 unless($self->_is_strict_response($socket, $self->{keyword})) {
295 0         0 goto IS_CONNECT_ERROR;
296             }
297             }
298            
299 1         15 $socket->close();
300 1 50       154 return wantarray ? (1, $con_time) : 1;
301            
302 4         18 IS_CONNECT_ERROR:
303             $socket->close();
304 4 100       350 return wantarray() ? (0, $con_time) : 0;
305             }
306              
307             sub is_https
308             { # check is this https proxy
309 4     4 1 9 my ($self, $proxyaddr, $proxyport) = @_;
310            
311 4 50       11 my ($socket, $con_time) = $self->_create_socket($proxyaddr, $proxyport)
312             or return;
313            
314 4 50       30 $self->_write_to_socket(
315             $socket, 'CONNECT '.$self->{https_host}.':443 HTTP/1.1'.CRLF.'Host: '.$self->{https_host}.':443'.CRLF.CRLF
316             ) or goto IS_HTTPS_ERROR;
317            
318 4 100       17 $self->_read_from_socket($socket, my $headers, CRLF.CRLF, 2000)
319             or goto IS_HTTPS_ERROR;
320 2 50       19 my ($code) = $headers =~ m!^HTTP/\d.\d (\d{3})!
321             or goto IS_HTTPS_ERROR;
322 2 0 0     16 if ($code == 407 && ($self->{noauth} || $self->{https_strict})) {
      33        
323 0         0 goto IS_HTTPS_ERROR;
324             }
325 2 50 33     25 if (($code < 200 || $code >= 300) && $code != 407) {
      33        
326 0         0 goto IS_HTTPS_ERROR;
327             }
328            
329 2 100       8 if ($self->{https_strict}) {
330 1         1683 require IO::Socket::SSL;
331 1         126492 $socket->blocking(1);
332            
333 1 50       38 unless (IO::Socket::SSL->start_SSL($socket, Timeout => $self->{read_timeout})) {
334 1         3853 goto IS_HTTPS_ERROR;
335             }
336            
337 0         0 $socket->blocking(0);
338 0 0 0     0 $self->_write_to_socket(
339             $socket,
340             'GET ' . ($self->{https_pathquery}||'/') . ' HTTP/' . $self->{http_ver} . CRLF . 'Host: ' . $self->{https_host} .
341             CRLF . CRLF
342             ) or goto IS_HTTPS_ERROR;
343            
344 0 0       0 unless ($self->_is_strict_response($socket, $self->{https_keyword})) {
345 0         0 goto IS_HTTPS_ERROR;
346             }
347             }
348            
349 1         16 $socket->close();
350 1 50       52 return wantarray ? (1, $con_time) : 1;
351            
352 3         19 IS_HTTPS_ERROR:
353             $socket->close();
354 3 100       174 return wantarray ? (0, $con_time) : 0;
355             }
356              
357             sub is_socks4
358             { # check is this socks4 proxy
359             # http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol
360 1     1 1 54 my ($self, $proxyaddr, $proxyport) = @_;
361            
362 1 50       5 my ($socket, $con_time) = $self->_create_socket($proxyaddr, $proxyport)
363             or return;
364            
365 1 50       1577 unless($self->_write_to_socket($socket, "\x04\x01" . pack('n', 80) . inet_aton($self->{host}) . "\x00")) {
366 0         0 goto IS_SOCKS4_ERROR;
367             }
368            
369 1         3 my ($buf, $rc);
370 1         4 $rc = $self->_read_from_socket($socket, $buf, 8);
371 1 0 33     24 if(!$rc || substr($buf, 0, 1) ne "\x00" || substr($buf, 1, 1) ne "\x5a") {
      33        
372 1         15 goto IS_SOCKS4_ERROR;
373             }
374            
375 0 0       0 if($self->{socks4_strict}) {
376 0 0       0 unless($self->_http_request($socket)) {
377 0         0 goto IS_SOCKS4_ERROR;
378             }
379            
380 0 0       0 unless($self->_is_strict_response($socket, $self->{keyword})) {
381 0         0 goto IS_SOCKS4_ERROR;
382             }
383             }
384            
385 0         0 $socket->close();
386 0 0       0 return wantarray ? (1, $con_time) : 1;
387            
388 1         17 IS_SOCKS4_ERROR:
389             $socket->close();
390 1 50       607 return wantarray ? (0, $con_time) : 0;
391             }
392              
393             sub is_socks5
394             { # check is this socks5 proxy
395             # http://tools.ietf.org/search/rfc1928
396 1     1 1 6 my ($self, $proxyaddr, $proxyport) = @_;
397            
398 1 50       10 my ($socket, $con_time) = $self->_create_socket($proxyaddr, $proxyport)
399             or return;
400            
401 1 50       7 unless($self->_write_to_socket($socket, "\x05\x01\x00")) {
402 0         0 goto IS_SOCKS5_ERROR;
403             }
404            
405 1         4 my ($buf, $rc);
406 1         8 $rc = $self->_read_from_socket($socket, $buf, 2);
407 1 50       15 unless($rc) {
408 1         10 goto IS_SOCKS5_ERROR;
409             }
410            
411 0         0 my $c = substr($buf, 1, 1);
412 0 0 0     0 if($c eq "\x01" || $c eq "\x02" || $c eq "\xff") {
      0        
413             # this is socks5 proxy with authentification
414 0 0 0     0 if($self->{noauth} || $self->{socks5_strict}) {
415 0         0 goto IS_SOCKS5_ERROR;
416             }
417             }
418             else {
419 0 0       0 if($c ne "\x00") {
420 0         0 goto IS_SOCKS5_ERROR;
421             }
422            
423 0 0       0 unless($self->_write_to_socket($socket, "\x05\x01\x00\x01" . inet_aton($self->{host}) . pack('n', 80))) {
424 0         0 goto IS_SOCKS5_ERROR;
425             }
426            
427             # minimum length of response is 10
428             # it is not necessarily to read whole response
429 0         0 $rc = $self->_read_from_socket($socket, $buf, 10);
430 0 0 0     0 if(!$rc || substr($buf, 1, 1) ne "\x00") {
431 0         0 goto IS_SOCKS5_ERROR;
432             }
433            
434 0 0       0 if($self->{socks5_strict}) {
435 0 0       0 unless($self->_http_request($socket)) {
436 0         0 goto IS_SOCKS5_ERROR;
437             }
438            
439 0 0       0 unless($self->_is_strict_response($socket, $self->{keyword})) {
440 0         0 goto IS_SOCKS5_ERROR;
441             }
442             }
443             }
444            
445 0         0 $socket->close();
446 0 0       0 return wantarray ? (1, $con_time) : 1;
447            
448 1         17 IS_SOCKS5_ERROR:
449             $socket->close();
450 1 50       598 return wantarray ? (0, $con_time) : 0;
451             }
452              
453             sub _http_request
454             { # do http request for some host
455 3     3   11 my ($self, $socket) = @_;
456 3         96 $self->_write_to_socket(
457             $socket, 'GET ' . $self->{url} . ' HTTP/' . $self->{http_ver} . CRLF . 'Host: ' . $self->{host} . CRLF . CRLF
458             );
459             }
460              
461             sub _is_strict_response
462             { # to make sure about proxy type we will read response header and try to find keyword
463             # without this check most of http servers may be recognized as http proxy, because its response after _http_request() begins from `HTTP'
464 2     2   28 my ($self, $socket, $keyword) = @_;
465            
466 2 100       11 $self->_read_from_socket($socket, my $headers, CRLF.CRLF, 4096)
467             or return 0;
468 1 50       7 my ($code) = $headers =~ m!HTTP/\d\.\d (\d{3})!
469             or return 0;
470 1 50 33     24 if ((caller(1))[3] eq __PACKAGE__.'::is_http' && $code == 407 && $self->{noauth}) {
      33        
471 0         0 return 0;
472             }
473            
474 1         5 return index($headers, $keyword) != -1;
475             }
476              
477             sub _write_to_socket
478             { # write data to non-blocking socket; return 1 on success, 0 on failure (timeout or other error)
479 14     14   55 my ($self, $socket, $msg) = @_;
480            
481 14         283 local $SIG{PIPE} = 'IGNORE';
482            
483 14         57 my $selector = IO::Select->new($socket);
484 14         539 my $start = time();
485 14         58 while(time() - $start < $self->{write_timeout}) {
486 14 50       50 unless($selector->can_write(1)) {
487             # socket couldn't accept data for now, check if timeout expired and try again
488 0         0 next;
489             }
490            
491 14         521 my $rc = $socket->syswrite($msg);
492 14 50 0     1201 if($rc > 0) {
    0          
493             # reduce our message
494 14         41 substr($msg, 0, $rc) = '';
495 14 50       51 if(length($msg) == 0) {
496             # all data successfully writed
497 14         231 return 1;
498             }
499             }
500             elsif($! != EWOULDBLOCK && $! != EAGAIN) {
501             # some error in the socket; will return false
502 0         0 last;
503             }
504             }
505            
506 0         0 return 0;
507             }
508              
509             sub _read_from_socket
510             { # read $limit bytes from non-blocking socket; return 0 if EOF, undef if error, bytes readed on success ($limit)
511 14     14   45 my ($self, $socket) = (shift, shift);
512 14         25 my $num_limit;
513             my $str_limit;
514 14 100       49 if (@_ == 2) {
515 3         6 $num_limit = pop;
516             }
517             else {
518 11         43 ($str_limit, $num_limit) = @_[1,2];
519             }
520            
521 14         34 my $limit_idx;
522 14         73 my $selector = IO::Select->new($socket);
523 14         654 my $start = time();
524 14         47 $_[0] = ''; # clean buffer variable like sysread() do
525            
526 14         55 while(time() - $start < $self->{read_timeout}) {
527 18 100       116 unless($selector->can_read(1)) {
528             # no data in socket for now, check is timeout expired and try again
529 6         6006357 next;
530             }
531            
532 12         2271 my $rc = $socket->sysread($_[0], $num_limit, length $_[0]);
533 12 50 0     219 if(defined($rc)) {
    0          
534             # no errors
535 12 100       24 if($rc > 0) {
536 8         11 $num_limit -= $rc;
537            
538 8 50 33     126 if ($num_limit == 0 || (defined $str_limit && ($limit_idx = index($_[0], $str_limit)) != -1)) {
      66        
539 8 100 66     47 if (defined $limit_idx && $limit_idx >= 0) {
540             # cut off all after $str_limit
541 7         15 substr($_[0], $limit_idx+length($str_limit)) = '';
542             }
543 8         39 return length($_[0]);
544             }
545             }
546             else {
547             # EOF in the socket
548 4         32 return 0;
549             }
550             }
551             elsif($! != EWOULDBLOCK && $! != EAGAIN) {
552 0         0 last;
553             }
554             }
555            
556 2         35 return undef;
557             }
558              
559             sub _create_socket
560             { # trying to create non-blocking socket by proxy address; return valid socket on success, 0 or undef on failure
561 16     16   34 my ($self, $proxyaddr, $proxyport) = @_;
562            
563 16 50       58 unless(defined($proxyport)) {
564 0 0       0 ($proxyaddr, $proxyport) = _parse_proxyaddr($proxyaddr)
565             or return 0;
566             }
567            
568 16         30 my $conn_start = time();
569 16 100       44 my $socket = $self->_open_socket($proxyaddr, $proxyport)
570             or return;
571            
572 14         107 return ($socket, time() - $conn_start);
573             }
574              
575             sub _open_socket
576             { # blocking open for non-blocking socket
577 16     16   22 my ($self, $host, $port) = @_;
578 16         212 my $socket = IO::Socket::INET->new(PeerHost => $host, PeerPort => $port, Timeout => $self->{connect_timeout}, Blocking => 0);
579            
580 16         10351 return $socket;
581             }
582              
583             sub _parse_proxyaddr
584             { # parse proxy address like this one: localhost:8080 -> host=localhost, port=8080
585 6     6   10 my ($proxyaddr) = @_;
586 6 50       66 my ($host, $port) = $proxyaddr =~ /^([^:]+):(\d+)$/
587             or return;
588            
589 0           return ($host, $port);
590             }
591              
592             1;
593              
594             __END__