File Coverage

blib/lib/LWP/UserAgent/Tor.pm
Criterion Covered Total %
statement 35 89 39.3
branch 0 26 0.0
condition 0 14 0.0
subroutine 12 16 75.0
pod 2 2 100.0
total 49 147 33.3


line stmt bran cond sub pod time code
1             package LWP::UserAgent::Tor;
2              
3 2     2   29104 use 5.010;
  2         5  
4 2     2   8 use strict;
  2         0  
  2         29  
5 2     2   5 use warnings;
  2         4  
  2         51  
6 2     2   7 use Carp;
  2         3  
  2         153  
7 2     2   1159 use LWP::UserAgent;
  2         62904  
  2         60  
8 2     2   1213 use File::MMagic;
  2         23801  
  2         54  
9 2     2   779 use File::Which qw(which);
  2         1230  
  2         87  
10 2     2   882 use IO::Socket::INET;
  2         21197  
  2         8  
11 2     2   2294 use LWP::Protocol::socks;
  2         129202  
  2         57  
12 2     2   773 use Net::EmptyPort qw(empty_port);
  2         5187  
  2         100  
13 2     2   849 use Proc::Background;
  2         4540  
  2         97  
14              
15 2     2   9 use base 'LWP::UserAgent';
  2         2  
  2         1107  
16              
17             our $VERSION = '0.05';
18              
19             sub new {
20 0     0 1   my ($class, %args) = @_;
21              
22 0   0       my $tor_control_port = delete( $args{tor_control_port} ) // empty_port();
23 0   0       my $tor_port = delete( $args{tor_port} ) // do {
24 0           my $port;
25 0           while (($port = empty_port()) == $tor_control_port){};
26 0           $port;
27             };
28 0   0       my $tor_ip = delete( $args{tor_ip} ) // 'localhost';
29 0           my $tor_cfg = delete( $args{tor_cfg} );
30              
31 0           my $self = $class->SUPER::new(%args);
32 0           $self->{_tor_proc} = _start_tor_proc($tor_ip, $tor_port, $tor_control_port, $tor_cfg);
33 0   0       $self->{_tor_socket} = IO::Socket::INET->new(
34             PeerAddr => $tor_ip,
35             PeerPort => $tor_control_port,
36             ) // croak 'could not connect to tor';
37              
38 0           $self->proxy( [ 'http', 'https' ], "socks://$tor_ip:$tor_port" );
39              
40 0           return bless $self, $class;
41             }
42              
43             sub DESTROY {
44 0     0     my ($self) = @_;
45              
46 0           my $tor_proc = $self->{_tor_proc};
47 0 0         $tor_proc->die if defined $tor_proc;
48 0 0         $self->SUPER::DESTROY if $self->can('SUPER::DESTROY');
49              
50 0           return;
51             }
52              
53             sub _start_tor_proc {
54 0     0     my ($ip, $port, $control_port, $cfg) = @_;
55              
56             # There must be a Tor binary in $PATH; it might be named "tor.real".
57 0           my $tor = which 'tor';
58 0 0         defined $tor or croak 'could not find tor binary in $PATH';
59 0           my $tor_real = which 'tor.real';
60              
61 0           my $mm = File::MMagic->new;
62 0           my $file_format = $mm->checktype_filename($tor);
63              
64 0           my $binary_format = 'application/octet-stream';
65              
66 0 0 0       if ($file_format eq $binary_format) {
    0          
    0          
67             # tor is a binary; do nothing.
68             }
69             elsif ($file_format =~ m/sh script text$/ && defined $tor_real) {
70             # tor is a shell script; it could be from a Tor Browser distribution.
71 0           $file_format = $mm->checktype_filename($tor_real);
72 0 0         if ($file_format eq $binary_format) {
    0          
73             # tor.real is the corresponding Tor binary.
74 0           $tor = $tor_real;
75             }
76             elsif ($file_format =~ m|^x-system/x-error; |) {
77 0           $file_format =~ s|^x-system/x-error; ||;
78 0           croak 'tor.real file format error detected: "' . $file_format . '"';
79             }
80             else {
81 0           croak 'could not find matching tor binary for tor shell script';
82             }
83             }
84             elsif ($file_format =~ m|^x-system/x-error; |) {
85 0           $file_format =~ s|^x-system/x-error; ||;
86 0           croak 'tor file format error detected: "' . $file_format . '"';
87             }
88             else {
89 0           croak 'could not work with tor file format "' . $file_format . '"';
90             }
91              
92 0           my $tor_cmd = "$tor --ControlListenaddress $ip:$control_port --ControlPort auto --SocksPort $port --quiet";
93 0 0         if (defined $cfg){
94 0 0         croak 'tor config file does not exist' unless -e $cfg;
95 0           $tor_cmd .= " -f $cfg";
96             }
97              
98 0           my $tor_proc = Proc::Background->new($tor_cmd);
99              
100             # starting tor...
101 0           sleep 1;
102              
103 0 0         if (!$tor_proc->alive) {
104 0           croak "error running tor. Run tor manually to get a hint.";
105             }
106              
107 0           return $tor_proc;
108             }
109              
110              
111             sub rotate_ip {
112 0     0 1   my ($self) = @_;
113              
114 0           my $socket = $self->{_tor_socket};
115 0           my $answer = q{};
116              
117 0           $socket->send("AUTHENTICATE\n");
118 0           $socket->recv($answer, 1024);
119 0 0         return 0 unless $answer eq "250 OK\r\n";
120              
121 0           $socket->send("SIGNAL NEWNYM\n");
122 0           $socket->recv($answer, 1024);
123 0 0         return 0 unless $answer eq "250 OK\r\n";
124              
125 0           return 1;
126             }
127              
128             1;
129              
130             __END__