File Coverage

blib/lib/WebService/AbuseIPDB.pm
Criterion Covered Total %
statement 108 108 100.0
branch 42 42 100.0
condition 12 17 70.5
subroutine 14 14 100.0
pod 5 5 100.0
total 181 186 97.3


line stmt bran cond sub pod time code
1             package WebService::AbuseIPDB;
2              
3 10     10   723328 use 5.010;
  10         117  
4 10     10   55 use strict;
  10         23  
  10         246  
5 10     10   53 use warnings;
  10         18  
  10         294  
6              
7             # use other modules
8 10     10   8455 use IO::Socket::SSL;
  10         881282  
  10         86  
9 10     10   7302 use REST::Client;
  10         484428  
  10         405  
10 10     10   142 use Carp;
  10         26  
  10         588  
11 10     10   3002 use JSON::XS;
  10         21490  
  10         521  
12 10     10   80 use URI; # The GET requests need URI-escaping
  10         25  
  10         12787  
13              
14             our $VERSION = '0.03_02';
15              
16             sub new {
17 10     10 1 9744 my ($class, %opts) = @_;
18             croak "Only version 2 is supported."
19 10 100 100     210 if exists $opts{ver} && 2 != $opts{ver};
20 9 100       247 croak "No key provided." unless exists $opts{key};
21              
22             # This is weird. If you don't set the outer timeout it goes to 300.
23             # If you do set the outer timeout, it sets the inner one too so it's
24             # effectively doubled. Setting the inner timeout has no effect.
25             my $self = {
26             ua => REST::Client->new (
27             { host => 'https://api.abuseipdb.com',
28             timeout => $opts{timeout} // 20,
29             agent => "WebService::AbuseIPDB/$VERSION",
30              
31             # useragent => LWP::UserAgent->new (ssl_opts => {
32             # verify_hostname => 1,
33             # },
34             # #timeout => $opts{timeout} // 20,
35             # )
36             }
37             ),
38             retry => $opts{retry} // 0,
39             api_ver => $opts{ver} // 2,
40             key => $opts{key},
41 8   100     142 };
      100        
      100        
42 8         36547 bless ($self, $class);
43 8         62 return $self;
44             }
45              
46             sub _send_receive {
47 15     15   143 my ($self, $meth, $path, $data) = @_;
48              
49 15         65 $path = "/api/v$self->{api_ver}/$path";
50 15         53 my $ct = {'Content-type' => 'application/json'};
51             my $headers = {
52             Accept => 'application/json',
53             Key => $self->{key}
54 15         104 };
55 15 100       64 if ($meth eq 'GET') {
56 11         80 my $u = URI->new ($path);
57 11         27008 $u->query_form (%$data);
58 11         1393 $path = $u->as_string;
59             }
60 15         132 my $tries_left = $self->{retry} + 1;
61 15         53 while ($tries_left) {
62              
63 16 100       187 if ($meth eq 'GET') {
    100          
64 12         59 $self->{ua}->GET ($path, $headers);
65             } elsif ($meth eq 'POST') {
66 3         7 $headers->{'Content-type'} = 'application/json';
67 3         25 $self->{ua}->POST ($path, encode_json ($data), $headers);
68             } else {
69 1         151 croak "Unrecognised method '$meth'";
70             }
71              
72 15 100       12825376 if ($self->{ua}->responseCode !~ /400|50./) {
73             return decode_json $self->{ua}->responseContent
74 10 100       143 if $self->{ua}->responseHeader ('Content-type') eq
75             'application/json';
76 1         68 return undef;
77             }
78 5         104 $tries_left--;
79 5         22 warn "REST error " . $self->{ua}->responseCode;
80             }
81              
82 4 100       579 if ($self->{ua}->responseCode > 499) {
83             # Not our problem, so don't carp
84             return {
85             errors => [{
86             status => $self->{ua}->responseCode,
87             detail => $self->{ua}->responseContent //
88 3   50     59 'Server Problem'
89             }]
90             };
91             }
92              
93             carp "Problem with $meth $path\nData was " . encode_json ($data) .
94             "\nClient warning: " .
95             ( $self->{ua}->responseHeader ('Client-Warning') //
96             $self->{ua}->responseContent //
97 1   33     24 $self->{ua}->responseCode);
      33        
98 1         367 return undef;
99             }
100              
101             sub check {
102 8     8 1 3697 my ($self, %args) = @_;
103 8 100       48 unless (exists $args{ip}) {
104 1         173 carp "No IP in argument hash";
105 1         114 return;
106             }
107              
108             # Validate this here TODO
109 7         29 my $data = {ipAddress => $args{ip}};
110 7 100       34 $data->{maxAgeInDays} = $args{max_age} if exists $args{max_age};
111             # TODO $data->{verbose} = 1 if $args{verbose};
112 7         2088 require WebService::AbuseIPDB::CheckResponse;
113 7         38 return WebService::AbuseIPDB::CheckResponse->new (
114             $self->_send_receive ('GET', 'check', $data));
115             }
116              
117             sub check_block {
118 3     3 1 1862 my ($self, %args) = @_;
119 3 100       14 unless (exists $args{ip}) {
120 1         182 carp "No IP in argument hash";
121 1         136 return;
122             }
123              
124             # Validate this here TODO
125 2         7 my $data = {network => $args{ip}};
126 2 100       7 $data->{maxAgeInDays} = $args{max_age} if exists $args{max_age};
127 2         531 require WebService::AbuseIPDB::CheckBlockResponse;
128 2         8 return WebService::AbuseIPDB::CheckBlockResponse->new (
129             $self->_send_receive ('GET', 'check-block', $data));
130             }
131              
132             sub report {
133 5     5 1 3107 my ($self, %args) = @_;
134 5         13 for my $mand (qw/ip categories/) {
135 9 100       29 unless (exists $args{$mand}) {
136 2         256 carp "No '$mand' key in argument hash";
137 2         229 return;
138             }
139             }
140              
141             # More validation here
142 3         28 my $data = {ip => $args{ip}};
143              
144             # Form the category string
145             # More validation here too
146 3         533 require WebService::AbuseIPDB::Category;
147             my @categories =
148 3         6 map { WebService::AbuseIPDB::Category->new ($_) } @{$args{categories}};
  6         25  
  3         9  
149 3         11 $data->{categories} = join (',', map { $_->id } @categories);
  4         11  
150              
151             # Trim the comment
152             $data->{comment} = substr ($args{comment}, 0, 1024)
153 3 100       13 if defined $args{comment};
154              
155             # Run it
156 3         474 require WebService::AbuseIPDB::ReportResponse;
157 3         11 return WebService::AbuseIPDB::ReportResponse->new (
158             $self->_send_receive ('POST', 'report', $data));
159             }
160              
161             sub blacklist {
162 7     7 1 4554 my ($self, %args) = @_;
163 7         21 my $data = {
164             limit => 1000,
165             confidenceMinimum => 75
166             };
167              
168 7 100       23 if (exists $args{limit}) {
169 4 100       26 unless ($args{limit} =~ /^[0-9]+$/) {
170 1         178 carp "limit must be a whole number";
171 1         99 return;
172             }
173 3 100       12 if ($args{limit} < 1) {
174 1         77 carp "limit must be greater than zero";
175 1         129 return;
176             }
177 2         5 $data->{limit} = $args{limit};
178             }
179              
180 5 100       14 if (exists $args{min_abuse}) {
181 4 100       23 unless ($args{min_abuse} =~ /^[0-9]+$/) {
182 1         73 carp "min_abuse must be a whole number";
183 1         73 return;
184             }
185 3 100       10 if ($args{min_abuse} < 25) {
186 1         74 carp "min_abuse is $args{min_abuse} but must be greater than 24";
187 1         73 return;
188             }
189 2 100       6 if ($args{min_abuse} > 100) {
190 1         73 carp "min_abuse is $args{min_abuse} but must be less than 100";
191 1         72 return;
192             }
193 1         3 $data->{confidenceMinimum} = $args{min_abuse};
194             }
195              
196 2         546 require WebService::AbuseIPDB::BlacklistResponse;
197 2         11 require WebService::AbuseIPDB::BlacklistMember;
198 2         9 return WebService::AbuseIPDB::BlacklistResponse->new (
199             $self->_send_receive ('GET', 'blacklist', $data));
200              
201             }
202              
203             1;
204              
205             __END__