File Coverage

blib/lib/WebService/AbuseIPDB.pm
Criterion Covered Total %
statement 100 100 100.0
branch 36 36 100.0
condition 9 9 100.0
subroutine 13 13 100.0
pod 4 4 100.0
total 162 162 100.0


line stmt bran cond sub pod time code
1             package WebService::AbuseIPDB;
2              
3 7     7   464746 use 5.010;
  7         87  
4 7     7   44 use strict;
  7         11  
  7         156  
5 7     7   35 use warnings;
  7         21  
  7         177  
6              
7             # use other modules
8 7     7   5953 use IO::Socket::SSL;
  7         606776  
  7         55  
9 7     7   4863 use REST::Client;
  7         356220  
  7         244  
10 7     7   59 use Carp;
  7         15  
  7         377  
11 7     7   2769 use JSON::XS;
  7         19848  
  7         429  
12 7     7   51 use URI; # The GET requests need URI-escaping
  7         17  
  7         7438  
13              
14             our $VERSION = '0.03';
15              
16             sub new {
17 7     7 1 6522 my ($class, %opts) = @_;
18             croak "Only version 2 is supported."
19 7 100 100     156 if exists $opts{ver} && 2 != $opts{ver};
20 6 100       203 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 5   100     83 };
      100        
      100        
42 5         20080 bless ($self, $class);
43 5         42 return $self;
44             }
45              
46             sub _send_receive {
47 9     9   117 my ($self, $meth, $path, $data) = @_;
48              
49 9         38 $path = "/api/v$self->{api_ver}/$path";
50 9         30 my $ct = {'Content-type' => 'application/json'};
51             my $headers = {
52             Accept => 'application/json',
53             Key => $self->{key}
54 9         41 };
55 9 100       34 if ($meth eq 'GET') {
56 5         38 my $u = URI->new ($path);
57 5         12806 $u->query_form (%$data);
58 5         666 $path = $u->as_string;
59             }
60 9         59 my $tries_left = $self->{retry} + 1;
61 9         31 while ($tries_left) {
62              
63 11 100       666 if ($meth eq 'GET') {
    100          
64 7         43 $self->{ua}->GET ($path, $headers);
65             } elsif ($meth eq 'POST') {
66 3         6 $headers->{'Content-type'} = 'application/json';
67 3         25 $self->{ua}->POST ($path, encode_json ($data), $headers);
68             } else {
69 1         156 croak "Unrecognised method '$meth'";
70             }
71              
72 10 100       18895732 if ($self->{ua}->responseCode !~ /[45]00/) {
73             return decode_json $self->{ua}->responseContent
74 7 100       72 if $self->{ua}->responseHeader ('Content-type') eq
75             'application/json';
76 1         67 return undef;
77             }
78 3         69 $tries_left--;
79 3         15 carp "REST error " . $self->{ua}->responseCode;
80             }
81              
82 1         377 carp "Problem with $meth $path";
83 1         132 carp "Data was " . encode_json ($data);
84 1         54 carp "Client warning: ", $self->{ua}->responseHeader ('Client-Warning');
85 1         194 return undef;
86             }
87              
88             sub check {
89 4     4 1 4767 my ($self, %args) = @_;
90 4 100       24 unless (exists $args{ip}) {
91 1         192 carp "No IP in argument hash";
92 1         98 return;
93             }
94              
95             # Validate this here TODO
96 3         13 my $data = {ipAddress => $args{ip}};
97 3 100       14 $data->{maxAgeInDays} = $args{max_age} if exists $args{max_age};
98             # TODO $data->{verbose} = 1 if $args{verbose};
99 3         1249 require WebService::AbuseIPDB::CheckResponse;
100 3         17 return WebService::AbuseIPDB::CheckResponse->new (
101             $self->_send_receive ('GET', 'check', $data));
102              
103             }
104              
105             sub report {
106 5     5 1 2976 my ($self, %args) = @_;
107 5         15 for my $mand (qw/ip categories/) {
108 9 100       26 unless (exists $args{$mand}) {
109 2         236 carp "No '$mand' key in argument hash";
110 2         167 return;
111             }
112             }
113              
114             # More validation here
115 3         28 my $data = {ip => $args{ip}};
116              
117             # Form the category string
118             # More validation here too
119 3         517 require WebService::AbuseIPDB::Category;
120             my @categories =
121 3         6 map { WebService::AbuseIPDB::Category->new ($_) } @{$args{categories}};
  6         24  
  3         9  
122 3         8 $data->{categories} = join (',', map { $_->id } @categories);
  4         8  
123              
124             # Trim the comment
125             $data->{comment} = substr ($args{comment}, 0, 1024)
126 3 100       13 if defined $args{comment};
127              
128             # Run it
129 3         465 require WebService::AbuseIPDB::ReportResponse;
130 3         13 return WebService::AbuseIPDB::ReportResponse->new (
131             $self->_send_receive ('POST', 'report', $data));
132             }
133              
134             sub blacklist {
135 7     7 1 4794 my ($self, %args) = @_;
136 7         21 my $data = {
137             limit => 1000,
138             confidenceMinimum => 75
139             };
140              
141 7 100       26 if (exists $args{limit}) {
142 4 100       25 unless ($args{limit} =~ /^[0-9]+$/) {
143 1         167 carp "limit must be a whole number";
144 1         96 return;
145             }
146 3 100       8 if ($args{limit} < 1) {
147 1         76 carp "limit must be greater than zero";
148 1         52 return;
149             }
150 2         5 $data->{limit} = $args{limit};
151             }
152              
153 5 100       14 if (exists $args{min_abuse}) {
154 4 100       22 unless ($args{min_abuse} =~ /^[0-9]+$/) {
155 1         73 carp "min_abuse must be a whole number";
156 1         51 return;
157             }
158 3 100       10 if ($args{min_abuse} < 25) {
159 1         114 carp "min_abuse is $args{min_abuse} but must be greater than 24";
160 1         54 return;
161             }
162 2 100       5 if ($args{min_abuse} > 100) {
163 1         72 carp "min_abuse is $args{min_abuse} but must be less than 100";
164 1         49 return;
165             }
166 1         3 $data->{confidenceMinimum} = $args{min_abuse};
167             }
168              
169 2         513 require WebService::AbuseIPDB::BlacklistResponse;
170 2         10 require WebService::AbuseIPDB::BlacklistMember;
171 2         7 return WebService::AbuseIPDB::BlacklistResponse->new (
172             $self->_send_receive ('GET', 'blacklist', $data));
173              
174             }
175              
176             1;
177              
178             __END__