File Coverage

blib/lib/LWP/RobotUA.pm
Criterion Covered Total %
statement 80 99 80.8
branch 21 36 58.3
condition 4 11 36.3
subroutine 11 14 78.5
pod 9 9 100.0
total 125 169 73.9


line stmt bran cond sub pod time code
1             package LWP::RobotUA;
2              
3 2     2   276921 use base qw(LWP::UserAgent);
  2         8  
  2         1063  
4              
5             our $VERSION = '6.34';
6              
7             require WWW::RobotRules;
8             require HTTP::Request;
9             require HTTP::Response;
10              
11 2     2   11 use Carp ();
  2         4  
  2         23  
12 2     2   8 use HTTP::Status ();
  2         3  
  2         28  
13 2     2   19 use HTTP::Date qw(time2str);
  2         2  
  2         71  
14 2     2   12 use strict;
  2         2  
  2         1661  
15              
16              
17             #
18             # Additional attributes in addition to those found in LWP::UserAgent:
19             #
20             # $self->{'delay'} Required delay between request to the same
21             # server in minutes.
22             #
23             # $self->{'rules'} A WWW::RobotRules object
24             #
25              
26             sub new
27             {
28 2     2 1 607516 my $class = shift;
29 2         20 my %cnf;
30 2 50       36 if (@_ < 4) {
31             # legacy args
32 2         58 @cnf{qw(agent from rules)} = @_;
33             }
34             else {
35 0         0 %cnf = @_;
36             }
37              
38 2 50       30 Carp::croak('LWP::RobotUA agent required') unless $cnf{agent};
39             Carp::croak('LWP::RobotUA from address required')
40 2 50 33     71 unless $cnf{from} && $cnf{from} =~ m/\@/;
41              
42 2   50     25 my $delay = delete $cnf{delay} || 1;
43 2         14 my $use_sleep = delete $cnf{use_sleep};
44 2 50       25 $use_sleep = 1 unless defined($use_sleep);
45 2         12 my $rules = delete $cnf{rules};
46              
47 2         67 my $self = LWP::UserAgent->new(%cnf);
48 2         16 $self = bless $self, $class;
49              
50 2         25 $self->{'delay'} = $delay; # minutes
51 2         5 $self->{'use_sleep'} = $use_sleep;
52              
53 2 50       8 if ($rules) {
54 0         0 $rules->agent($cnf{agent});
55 0         0 $self->{'rules'} = $rules;
56             }
57             else {
58 2         42 $self->{'rules'} = WWW::RobotRules->new($cnf{agent});
59             }
60              
61 2         134 $self;
62             }
63              
64              
65 8     8 1 9198 sub delay { shift->_elem('delay', @_); }
66 2     2 1 23 sub use_sleep { shift->_elem('use_sleep', @_); }
67              
68              
69             sub agent
70             {
71 0     0 1 0 my $self = shift;
72 0         0 my $old = $self->SUPER::agent(@_);
73 0 0       0 if (@_) {
74             # Changing our name means to start fresh
75 0         0 $self->{'rules'}->agent($self->{'agent'});
76             }
77 0         0 $old;
78             }
79              
80              
81             sub rules {
82 0     0 1 0 my $self = shift;
83 0         0 my $old = $self->_elem('rules', @_);
84 0 0       0 $self->{'rules'}->agent($self->{'agent'}) if @_;
85 0         0 $old;
86             }
87              
88              
89             sub no_visits
90             {
91 2     2 1 53 my($self, $netloc) = @_;
92 2 50       10 $self->{'rules'}->no_visits($netloc) || 0;
93             }
94              
95             *host_count = \&no_visits; # backwards compatibility with LWP-5.02
96              
97              
98             sub host_wait
99             {
100 13     13 1 85 my($self, $netloc) = @_;
101 13 100       43 return undef unless defined $netloc;
102 12         54 my $last = $self->{'rules'}->last_visit($netloc);
103 12 100       89 if ($last) {
104 10         49 my $wait = int($self->{'delay'} * 60 - (time - $last));
105 10 50       43 $wait = 0 if $wait < 0;
106 10         35 return $wait;
107             }
108 2         15 return 0;
109             }
110              
111              
112             sub simple_request
113             {
114 13     13 1 59 my($self, $request, $arg, $size) = @_;
115              
116             # Do we try to access a new server?
117 13         55 my $allowed = $self->{'rules'}->allowed($request->uri);
118              
119 13 100       3030 if ($allowed < 0) {
120             # Host is not visited before, or robots.txt expired; fetch "robots.txt"
121 2         9 my $robot_url = $request->uri->clone;
122 2         27 $robot_url->path("robots.txt");
123 2         110 $robot_url->query(undef);
124              
125             # make access to robot.txt legal since this will be a recursive call
126 2         40 $self->{'rules'}->parse($robot_url, "");
127              
128 2         307 my $robot_req = HTTP::Request->new('GET', $robot_url);
129 2         119 my $parse_head = $self->parse_head(0);
130 2         39 my $robot_res = $self->request($robot_req);
131 2         18 $self->parse_head($parse_head);
132 2         20 my $fresh_until = $robot_res->fresh_until;
133 2         1640 my $content = "";
134 2 50 33     19 if ($robot_res->is_success && $robot_res->content_is_text) {
135 2         245 $content = $robot_res->decoded_content;
136 2 50 33     4364 $content = "" unless $content && $content =~ /^\s*Disallow\s*:/mi;
137             }
138 2         19 $self->{'rules'}->parse($robot_url, $content, $fresh_until);
139              
140             # recalculate allowed...
141 2         1680 $allowed = $self->{'rules'}->allowed($request->uri);
142             }
143              
144             # Check rules
145 13 100       466 unless ($allowed) {
146 2         13 my $res = HTTP::Response->new(
147             HTTP::Status::RC_FORBIDDEN, 'Forbidden by robots.txt');
148 2         127 $res->request( $request ); # bind it to that request
149 2         26 return $res;
150             }
151              
152 11         22 my $netloc = eval { local $SIG{__DIE__}; $request->uri->host_port; };
  11         61  
  11         73  
153 11         407 my $wait = $self->host_wait($netloc);
154              
155 11 100       33 if ($wait) {
156 6 100       25 if ($self->{'use_sleep'}) {
157 4         12000964 sleep($wait)
158             }
159             else {
160 2         17 my $res = HTTP::Response->new(
161             HTTP::Status::RC_SERVICE_UNAVAILABLE, 'Please, slow down');
162 2         150 $res->header('Retry-After', time2str(time + $wait));
163 2         221 $res->request( $request ); # bind it to that request
164 2         29 return $res;
165             }
166             }
167              
168             # Perform the request
169 9         207 my $res = $self->SUPER::simple_request($request, $arg, $size);
170              
171 9         80 $self->{'rules'}->visit($netloc);
172              
173 9         245 $res;
174             }
175              
176              
177             sub as_string
178             {
179 0     0 1   my $self = shift;
180 0           my @s;
181 0           push(@s, "Robot: $self->{'agent'} operated by $self->{'from'} [$self]");
182 0           push(@s, " Minimum delay: " . int($self->{'delay'}*60) . "s");
183 0 0         push(@s, " Will sleep if too early") if $self->{'use_sleep'};
184 0           push(@s, " Rules = $self->{'rules'}");
185 0           join("\n", @s, '');
186             }
187              
188             1;
189              
190              
191             __END__