File Coverage

blib/lib/Robots/Validate.pm
Criterion Covered Total %
statement 49 52 94.2
branch 14 18 77.7
condition 4 7 57.1
subroutine 11 13 84.6
pod 1 1 100.0
total 79 91 86.8


line stmt bran cond sub pod time code
1              
2             # ABSTRACT: Validate that IP addresses are associated with known robots
3              
4             use v5.10;
5 1     1   815  
  1         4  
6             use Moo 1;
7 1     1   499  
  1         6567  
  1         5  
8             use MooX::Const v0.4.0;
9 1     1   1853 use List::Util 1.33 qw/ first none /;
  1         154298  
  1         5  
10 1     1   197 use Net::DNS::Resolver;
  1         26  
  1         71  
11 1     1   6 use Ref::Util qw/ is_plain_hashref /;
  1         2  
  1         33  
12 1     1   519 use Types::Standard -types;
  1         491  
  1         61  
13 1     1   7  
  1         1  
  1         11  
14             # RECOMMEND PREREQ: Type::Tiny::XS
15             # RECOMMEND PREREQ: Ref::Util::XS
16              
17             use namespace::autoclean;
18 1     1   4282  
  1         2  
  1         12  
19             our $VERSION = 'v0.2.7';
20              
21              
22             has resolver => (
23             is => 'lazy',
24             isa => InstanceOf ['Net::DNS::Resolver'],
25             builder => 1,
26             );
27              
28             return Net::DNS::Resolver->new;
29             }
30 0     0   0  
31              
32             has robots => (
33             is => 'const',
34             isa => ArrayRef [
35             Dict [
36             name => Str,
37             agent => Optional [RegexpRef],
38             domain => RegexpRef,
39             ]
40             ],
41             lazy => 1,
42             strict => 0,
43             builder => 1,
44             );
45              
46             return [
47              
48             {
49             name => 'Amazonbot',
50             agent => qr/\bAmazonbot\b/,
51             domain => qr/\.amazonaws\.com$/,
52 0     0   0 },
53              
54             {
55             name => 'Applebot',
56             agent => qr/\bApplebot\b/,
57             domain => qr/\.applebot\.apple\.com$/,
58             },
59              
60             {
61             name => 'Arquivo.pt',
62             agent => qr/arquivo-web-crawler/,
63             domain => qr/\.arquivo\.pt$/,
64             },
65              
66             {
67             name => 'Baidu',
68             agent => qr/\bBaiduspider\b/,
69             domain => qr/\.crawl\.baidu\.com$/,
70              
71             },
72              
73             {
74             name => 'Bing',
75             agent => qr/\b(?:Bingbot|MSNBot|AdIdxBot|BingPreview)\b/i,
76             domain => qr/\.search\.msn\.com$/,
77              
78             },
79              
80             {
81             name => 'CocCoc',
82             agent => qr/\bcoccocbot-web\b/,
83             domain => qr/\.coccoc\.com$/,
84             },
85              
86             {
87             name => 'Embedly',
88             agent => qr/\bEmbedly\b/,
89             domain => qr/\.embed\.ly$/,
90             },
91              
92             {
93             name => 'Exabot',
94             agent => qr/\bExabot\b/i,
95             domain => qr/\.exabot\.com$/,
96             },
97              
98             {
99             name => 'Google',
100             agent => qr/\bGoogle(?:bot?)\b/i,
101             domain => qr/\.google(?:bot)?\.com$/,
102             },
103              
104             {
105             name => 'InfoTiger',
106             agent => qr/\bInfoTigerBot\b/,
107             domain => qr/\.infotiger\.com$/,
108             },
109              
110             {
111             name => 'IONOS',
112             agent => qr/\bIonCrawl\b/,
113             domain => qr/\.1and1\.org$/,
114             },
115              
116             {
117             name => 'LinkedIn',
118             agent => qr/\bLinkedInBot\b/,
119             domain => qr/\.linkedin\.com$/,
120             },
121              
122             {
123             name => 'Mojeek',
124             agent => qr/\bMojeekBot\b/,
125             domain => qr/\.mojeek\.com$/,
126             },
127              
128             {
129             name => 'Neevabot',
130             agent => qr/\bNeevabot\b/,
131             domain => qr/\.neevabot\.com$/,
132             },
133              
134             {
135             name => 'PetalBot',
136             agent => qr/\bPetalBot\b/,
137             domain => qr/\.petalsearch\.com$/,
138             },
139              
140             {
141             name => 'Pinterest',
142             agent => qr/\bPinterest\b/,
143             domain => qr/\.pinterest\.com$/,
144             },
145              
146             {
147             name => 'Qwant',
148             agent => qr/\bQwantify\b/,
149             domain => qr/\.qwant\.com$/,
150             },
151              
152             {
153             name => 'SeznamBot',
154             agent => qr/\bSeznam\b/,
155             domain => qr/\.seznam\.cz$/,
156             },
157              
158             {
159             name => 'Sogou',
160             agent => qr/\bSogou\b/,
161             domain => qr/\.sogou\.com$/,
162             },
163              
164             {
165             name => 'Yahoo',
166             agent => qr/yahoo/i,
167             domain => qr/\.crawl\.yahoo\.net$/,
168              
169             },
170              
171             {
172             name => "Yandex",
173             agent => qr/Yandex/,
174             domain => qr/\.yandex\.(?:com|ru|net)$/,
175             },
176              
177             {
178             name => 'Yeti',
179             agent => qr/\bnaver\.me\b/,
180             domain => qr/\.naver\.com$/,
181             },
182              
183             ];
184             }
185              
186              
187             has die_on_error => (
188             is => 'lazy',
189             isa => Bool,
190             default => 0,
191             );
192              
193              
194             my ( $self, $ip, $args ) = @_;
195              
196             if (is_plain_hashref($ip) && !$args) {
197             $args = { agent => $ip->{HTTP_USER_AGENT} };
198 5     5 1 9636 $ip = $ip->{REMOTE_ADDR};
199             }
200 5 100 66     20  
201 1         3 my $res = $self->resolver;
202 1         2  
203             # Reverse DNS
204              
205 5         104 my $hostname;
206              
207             if ( my $reply = $res->query($ip, 'PTR') ) {
208             ($hostname) = map { $_->ptrdname } $reply->answer;
209 5         33 }
210             else {
211 5 100       29 die $res->errorstring if $self->die_on_error;
212 4         1025 }
  4         40  
213              
214             return unless $hostname;
215 1 50       242  
216             $args //= {};
217              
218 5 100       83 my $agent = $args->{agent};
219             my @matches =
220 4   100     17 grep { !$agent || $agent =~ $_->{agent} } @{ $self->robots };
221              
222 4         6 my $reply = $res->search( $hostname, "A" )
223             or $self->die_on_error && die $res->errorstring;
224 4 100       4  
  4         52  
  4         78  
225             return unless $reply;
226 4 50 0     21  
227             if (
228             none { $_ eq $ip } (
229 4 50       605 map { $_->address }
230             grep { $_->can('address') } $reply->answer
231 4 50       19 )
232 4     4   49 )
233 4         11 {
234 4         33 return;
235             }
236              
237             if ( my $match = first { $hostname =~ $_->{domain} } @matches ) {
238 0         0  
239             return {
240             %$match,
241 4 100   3   23 hostname => $hostname,
  3         19  
242             ip_address => $ip,
243             };
244 3         46  
245             }
246              
247             return;
248             }
249              
250              
251 1         9 1;
252              
253              
254             =pod
255              
256             =encoding UTF-8
257              
258             =head1 NAME
259              
260             Robots::Validate - Validate that IP addresses are associated with known robots
261              
262             =head1 VERSION
263              
264             version v0.2.7
265              
266             =head1 SYNOPSIS
267              
268             use Robots::Validate;
269              
270             my $rv = Robots::Validate->new;
271              
272             ...
273              
274             if ( $rs->validate( $ip, \%opts ) ) { ... }
275              
276             =head1 DESCRIPTION
277              
278             =head1 ATTRIBUTES
279              
280             =head2 C<resolver>
281              
282             This is the L<Net::DNS::Resolver> used for DNS lookups.
283              
284             =head2 C<robots>
285              
286             This is an array reference of rules with information about
287             robots. Each item is a hash reference with the following keys:
288              
289             =over
290              
291             =item C<name>
292              
293             The name of the robot.
294              
295             =item C<agent>
296              
297             A regular expression for matching against user agent names.
298              
299             =item C<domain>
300              
301             A regular expression for matching against the hostname.
302              
303             =back
304              
305             =head2 C<die_on_error>
306              
307             When true, L</validate> will die on a L</resolver> failure.
308              
309             By default it is false.
310              
311             =head1 METHODS
312              
313             =head2 C<validate>
314              
315             my $result = $rv->validate( $ip, \%opts );
316              
317             This method attempts to validate that an IP address belongs to a known
318             robot by first looking up the hostname that corresponds to the IP address,
319             and then validating that the hostname resolves to that IP address.
320              
321             If this succeeds, it then checks if the hostname is associated with a
322             known web robot.
323              
324             If that succeeds, it returns a copy of the matched rule from L</robots>.
325              
326             You can specify the following C<%opts>:
327              
328             =over
329              
330             =item C<agent>
331              
332             This is the user-agent string. If it does not match, then the DNS lookkups
333             will not be performed.
334              
335             It is optional.
336              
337             =back
338              
339             Alternatively, you can pass in a Plack environment:
340              
341             my $result = $rv->validate($env);
342              
343             =head1 KNOWN ISSUES
344              
345             =head2 Undocumented Rules
346              
347             Many of these rules are not documented, but have been guessed from web
348             traffic.
349              
350             =head2 Limitations
351              
352             The current module can only be used for systems that consistently
353             support reverse DNS lookups. This means that it cannot be used to
354             validate some robots from
355             L<Facebook|https://developers.facebook.com/docs/sharing/webmasters/crawler>
356             or Twitter.
357              
358             =head1 SEE ALSO
359              
360             =over
361              
362             =item L<Verifying Bingbot|https://www.bing.com/webmaster/help/how-to-verify-bingbot-3905dc26>
363              
364             =item L<Verifying Googlebot|https://support.google.com/webmasters/answer/80553>
365              
366             =item L<How to check that a robot belongs to Yandex|https://yandex.com/support/webmaster/robot-workings/check-yandex-robots.html>
367              
368             =back
369              
370             =head1 SOURCE
371              
372             The development version is on github at L<https://github.com/robrwo/Robots-Validate>
373             and may be cloned from L<git://github.com/robrwo/Robots-Validate.git>
374              
375             =head1 BUGS
376              
377             Please report any bugs or feature requests on the bugtracker website
378             L<https://github.com/robrwo/Robots-Validate/issues>
379              
380             When submitting a bug or request, please include a test-file or a
381             patch to an existing test-file that illustrates the bug or desired
382             feature.
383              
384             =head1 AUTHOR
385              
386             Robert Rothenberg <rrwo@cpan.org>
387              
388             =head1 COPYRIGHT AND LICENSE
389              
390             This software is Copyright (c) 2018-2022 by Robert Rothenberg.
391              
392             This is free software, licensed under:
393              
394             The Artistic License 2.0 (GPL Compatible)
395              
396             =cut