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