File Coverage

blib/lib/LWP/UserAgent/Tor.pm
Criterion Covered Total %
statement 38 92 41.3
branch 0 26 0.0
condition 0 12 0.0
subroutine 13 17 76.4
pod 2 2 100.0
total 53 149 35.5


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