File Coverage

blib/lib/Geo/IPfree.pm
Criterion Covered Total %
statement 139 149 93.2
branch 52 66 78.7
condition 11 19 57.8
subroutine 15 16 93.7
pod 11 11 100.0
total 228 261 87.3


line stmt bran cond sub pod time code
1             package Geo::IPfree;
2 8     8   2654634 use 5.006;
  8         33  
3 8     8   59 use strict;
  8         38  
  8         284  
4 8     8   87 use warnings;
  8         23  
  8         563  
5              
6 8     8   47 use Carp qw();
  8         32  
  8         23017  
7              
8             require Exporter;
9             our @ISA = qw(Exporter);
10              
11             our $VERSION = '1.160001'; # VERSION
12              
13             # ABSTRACT: Geo::IPfree - Look up the country of an IPv4 address
14              
15             our @EXPORT = qw(LookUp LoadDB);
16             our @EXPORT_OK = @EXPORT;
17              
18             my $DEFAULT_DB = 'ipscountry.dat';
19             my $cache_expire = 5000;
20             my @baseX = (
21             0 .. 9,
22             'A' .. 'Z',
23             'a' .. 'z',
24             split( m{}, q(.,;'"`<>{}[]=+-~*@#%$&!?) )
25             );
26              
27             my ( %baseX, $base, $THIS, %countrys, $base0, $base1, $base2, $base3, $base4 );
28              
29             {
30             my $c = 0;
31             %baseX = map { $_ => ( $c++ ) } @baseX;
32             $base = @baseX;
33             $base0 = $base**0;
34             $base1 = $base**1;
35             $base2 = $base**2;
36             $base3 = $base**3;
37             $base4 = $base**4;
38              
39             my @data;
40             while () {
41             last if m{^__END__};
42             chomp;
43             push @data, split m{ }, $_, 2;
44             }
45             %countrys = @data;
46             }
47              
48             sub new {
49 5     5 1 1060058 my ( $class, $db_file ) = @_;
50              
51 5 50 33     65 if ( !defined $_[0] || $_[0] !~ /^[\w:]+$/ ) {
52 0         0 $class = 'Geo::IPfree';
53 0         0 $db_file = $_[0];
54             }
55              
56 5         18 my $this = bless( {}, $class );
57              
58 5 50       19 if ( !defined $db_file ) { $db_file = _find_db_file(); }
  5         33  
59              
60 5         31 $this->LoadDB($db_file);
61              
62 5         38 $this->Clean_Cache();
63 5         16 $this->{cache} = 1;
64              
65 5         34 return $this;
66             }
67              
68             sub get_all_countries {
69 0     0 1 0 return {%countrys}; # copy
70             }
71              
72             sub _find_db_file {
73             my @locations = (
74             qw(/usr/local/share /usr/local/share/GeoIPfree),
75 5     5   19 map { $_, "$_/Geo" } @INC
  40         146  
76             );
77              
78             # lastly, find where this module was loaded, and try that dir
79 5         78 my ($lib) = ( $INC{'Geo/IPfree.pm'} =~ /^(.*?)[\\\/]+[^\\\/]+$/gs );
80 5         17 push @locations, $lib;
81              
82 5         14 for my $file ( map { "$_/$DEFAULT_DB" } @locations ) {
  95         194  
83 30 100       743 return $file if -e $file;
84             }
85             }
86              
87             sub LoadDB {
88 5     5 1 11 my $this = shift;
89 5         23 my ($db_file) = @_;
90              
91 5 50       78 if ( -d $db_file ) { $db_file .= "/$DEFAULT_DB"; }
  0         0  
92              
93 5 50       62 if ( !-s $db_file ) {
94 0         0 Carp::croak("Can't load database, blank or not there: $db_file");
95             }
96              
97 5         13 my $buffer = '';
98 5 50       279 open( my $handler, '<', $db_file )
99             || Carp::croak("Failed to open database file $db_file for read!");
100 5         37 binmode($handler);
101 5         53 $this->{dbfile} = $db_file;
102              
103 5 50       25 delete $this->{pos} if $this->{pos};
104              
105 5         148 while ( read( $handler, $buffer, 1, length($buffer) ) ) {
106 85100 100       694296 if ( $buffer =~ /##headers##(\d+)##$/s ) {
    100          
107 5         36 my $headers;
108 5         125 read( $handler, $headers, $1 );
109 5         2485 my (%head) = ( $headers =~ /(\d+)=(\d+)/gs );
110 5         1385 $this->{pos}{$_} = $head{$_} for keys %head;
111 5         281 $buffer = '';
112             }
113             elsif ( $buffer =~ /##start##$/s ) {
114 5         21 $this->{start} = tell($handler);
115 5         16 last;
116             }
117             }
118              
119 5         10 $this->{searchorder} = [ sort { $a <=> $b } keys %{ $this->{pos} } ];
  8627         12370  
  5         258  
120 5         132 $this->{handler} = $handler;
121             }
122              
123             sub LookUp {
124 28     28 1 374256 my $this;
125              
126 28 100       108 if ( $#_ == 0 ) {
127 6 100       24 if ( !$THIS ) { $THIS = Geo::IPfree->new(); }
  1         12  
128 6         34 $this = $THIS;
129             }
130 22         35 else { $this = shift; }
131              
132 28         74 my ($ip) = @_;
133              
134 28 50       141 $ip =~ s/\.+/\./gs if index( $ip, '..' ) > -1;
135 28 50       89 substr( $ip, 0, 1, '' ) if substr( $ip, 0, 1 ) eq '.';
136 28 50       112 chop $ip if substr( $ip, -1 ) eq '.';
137              
138 28 100       201 if ( $ip !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
139 3         14 $ip = nslookup($ip);
140             }
141              
142 28 100       113 return unless length $ip;
143              
144             ## Cache key uses /25 granularity: .0 for last octet 0-127, .128 for 128-255.
145             ## This fixes lookups in split /24 blocks while keeping cache compact.
146 25         56 my $ip_cache = $ip;
147 25 100       179 $ip_cache =~ s/\.(\d+)$/ '.' . ($1 < 128 ? '0' : '128') /e;
  25         145  
148              
149 25 100 66     169 if ( $this->{cache} && $this->{CACHE}{$ip_cache} ) {
150 2         4 return ( @{ $this->{CACHE}{$ip_cache} }, $ip );
  2         12  
151             }
152              
153 23         65 my $ipnb = ip2nb($ip);
154              
155 23         38 my $buf_pos = 0;
156              
157 23         40 foreach my $Key ( @{ $this->{searchorder} } ) {
  23         71  
158 3399 100       6554 if ( $ipnb <= $Key ) { $buf_pos = $this->{pos}{$Key}; last; }
  23         67  
  23         52  
159             }
160              
161 23         65 my ( $buffer, $country, $iprange, $basex2 );
162              
163             ## Will use the DB in the memory:
164 23 100       65 if ( $this->{FASTER} ) {
165 7   100     33 my $base_cache = $this->{'baseX2dec'} ||= {};
166 7         29 while ( $buf_pos < $this->{DB_SIZE} ) {
167 1798 100 66     6778 if ( $ipnb >= ( $base_cache->{ ( $basex2 = substr( $this->{DB}, $buf_pos + 2, 5 ) ) } ||= baseX2dec($basex2) ) ) {
168 7         19 $country = substr( $this->{DB}, $buf_pos, 2 );
169 7         17 last;
170             }
171 1791         4175 $buf_pos += 7;
172             }
173 7   33     25 $country ||= substr( $this->{DB}, $buf_pos - 7, 2 );
174             }
175             ## Will read the DB in the disk:
176             else {
177 16 50       70 seek( $this->{handler}, 0, 0 )
178             if $] < 5.006001; ## Fix bug on Perl 5.6.0
179 16         222 seek( $this->{handler}, $buf_pos + $this->{start}, 0 );
180 16         229 while ( read( $this->{handler}, $buffer, 7 ) ) {
181 3743 100       6316 if ( $ipnb >= baseX2dec( substr( $buffer, 2 ) ) ) {
182 16         30 $country = substr( $buffer, 0, 2 );
183 16         37 last;
184             }
185             }
186             }
187              
188 23 50       92 if ( $this->{cache} ) {
189 23 50       64 if ( $this->{CACHE_COUNT} > $cache_expire ) {
190 0         0 keys %{ $this->{CACHE} };
  0         0  
191 0         0 my ($d_key) = each( %{ $this->{CACHE} } );
  0         0  
192 0         0 delete $this->{CACHE}{$d_key};
193             }
194             else {
195 23         41 $this->{CACHE_COUNT}++;
196             }
197 23         143 $this->{CACHE}{$ip_cache} = [ $country, $countrys{$country} ];
198             }
199              
200 23         168 return ( $country, $countrys{$country}, $ip );
201             }
202              
203             sub Faster {
204 2     2 1 14 my $this = shift;
205 2         7 my $handler = $this->{handler};
206              
207 2         30 seek( $handler, 0, 0 ); ## Fix bug on Perl 5.6.0
208 2         12 seek( $handler, $this->{start}, 0 );
209              
210 2         5 $this->{DB} = do { local $/; <$handler>; };
  2         13  
  2         4281  
211 2         33 $this->{DB_SIZE} = length( $this->{DB} );
212 2         38 $this->{FASTER} = 1;
213             }
214              
215             sub Clean_Cache {
216 8     8 1 5526 my $this = shift;
217 8         28 $this->{CACHE_COUNT} = 0;
218 8         36 delete $this->{CACHE};
219 8         656 delete $this->{'baseX2dec'};
220 8         25 return 1;
221             }
222              
223             sub nslookup {
224 6     6 1 24 my ( $host, $last_lookup ) = @_;
225 6         8027 require Socket;
226 6   50     687896 my $iaddr = Socket::inet_aton($host) || '';
227 6         61 my @ip = unpack( 'C4', $iaddr );
228              
229 6 100 66     78 return nslookup( "www.${host}", 1 ) if !@ip && !$last_lookup;
230 3         42 return join( '.', @ip );
231             }
232              
233             sub ip2nb {
234 24     24 1 803 my @ip = split( /\./, $_[0] );
235 24         128 return ( $ip[0] << 24 ) + ( $ip[1] << 16 ) + ( $ip[2] << 8 ) + $ip[3];
236             }
237              
238             sub nb2ip {
239 1     1 1 3 my ($input) = @_;
240 1         8 my @ip;
241              
242 1         4 while ( $input > 1 ) {
243 4         11 my $int = int( $input / 256 );
244 4         7 push @ip, $input - ( $int << 8 );
245 4         10 $input = $int;
246             }
247              
248 1 50       9 push @ip, $input if $input > 0;
249 1         3 push @ip, (0) x ( 4 - @ip );
250              
251 1         9 return join( '.', reverse @ip );
252             }
253              
254             sub dec2baseX {
255 86     86 1 83811 my ($dec) = @_;
256 86         181 my @base;
257              
258 86         291 while ( $dec > 1 ) {
259 84         279 my $int = int( $dec / $base );
260 84         208 push @base, $dec - $int * $base;
261 84         229 $dec = $int;
262             }
263              
264 86 100       199 push @base, $dec if $dec > 0;
265 86         295 push @base, (0) x ( 5 - @base );
266              
267 86         226 return join( '', map { $baseX[$_] } reverse @base );
  430         1841  
268             }
269              
270             sub baseX2dec {
271 5558     5558 1 12581 my $string = reverse $_[0];
272 5558         7323 my $length = length $string;
273             return #
274             (
275             0 + ( $length > 4 ? ( $baseX{ substr( $string, 4, 1 ) } * $base4 ) : 0 ) + #
276             ( $length > 3 ? ( $baseX{ substr( $string, 3, 1 ) } * $base3 ) : 0 ) + #
277             ( $length > 2 ? ( $baseX{ substr( $string, 2, 1 ) } * $base2 ) : 0 ) + #
278             ( $length > 1 ? ( $baseX{ substr( $string, 1, 1 ) } * $base1 ) : 0 ) + #
279 5558 100       32095 ( $length ? ( $baseX{ substr( $string, 0, 1 ) } * $base0 ) : 0 ) #
    100          
    100          
    100          
    50          
280             ); #
281             }
282              
283             1;
284              
285             =pod
286              
287             =encoding UTF-8
288              
289             =head1 NAME
290              
291             Geo::IPfree - Geo::IPfree - Look up the country of an IPv4 address
292              
293             =head1 VERSION
294              
295             version 1.160001
296              
297             =head1 AUTHOR
298              
299             Graciliano M. P.
300              
301             =head1 COPYRIGHT AND LICENSE
302              
303             This software is copyright (c) 2022 by Graciliano M. P.
304              
305             This is free software; you can redistribute it and/or modify it under
306             the same terms as the Perl 5 programming language system itself.
307              
308             =cut
309              
310             __DATA__