File Coverage

blib/lib/Geo/IP2Location/Lite.pm
Criterion Covered Total %
statement 145 155 93.5
branch 47 52 90.3
condition 6 9 66.6
subroutine 37 38 97.3
pod 0 35 0.0
total 235 289 81.3


line stmt bran cond sub pod time code
1             package Geo::IP2Location::Lite;
2              
3             # Copyright (C) 2005-2014 IP2Location.com
4             # All Rights Reserved
5             #
6             # This library is free software: you can redistribute it and/or
7             # modify it under the terms of the GNU Lesser General Public
8             # License as published by the Free Software Foundation, either
9             # version 3 of the License, or (at your option) any later version.
10             #
11             # This library is distributed in the hope that it will be useful,
12             # but WITHOUT ANY WARRANTY; without even the implied warranty of
13             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14             # Lesser General Public License for more details.
15             #
16             # You should have received a copy of the GNU Lesser General Public
17             # License along with this library; If not, see .
18              
19 2     2   3771 use strict;
  2         6  
  2         62  
20 2     2   14 use warnings;
  2         5  
  2         5212  
21              
22             $Geo::IP2Location::Lite::VERSION = '0.11';
23              
24             my $UNKNOWN = "UNKNOWN IP ADDRESS";
25             my $NO_IP = "MISSING IP ADDRESS";
26             my $INVALID_IP_ADDRESS = "INVALID IP ADDRESS";
27             my $NOT_SUPPORTED = "This parameter is unavailable in selected .BIN data file. Please upgrade data file.";
28             my $MAX_IPV4_RANGE = 4294967295;
29              
30             my $COUNTRYSHORT = 1;
31             my $COUNTRYLONG = 2;
32             my $REGION = 3;
33             my $CITY = 4;
34             my $ISP = 5;
35             my $LATITUDE = 6;
36             my $LONGITUDE = 7;
37             my $DOMAIN = 8;
38             my $ZIPCODE = 9;
39             my $TIMEZONE = 10;
40             my $NETSPEED = 11;
41             my $IDDCODE = 12;
42             my $AREACODE = 13;
43             my $WEATHERSTATIONCODE = 14;
44             my $WEATHERSTATIONNAME = 15;
45             my $MCC = 16;
46             my $MNC = 17;
47             my $MOBILEBRAND = 18;
48             my $ELEVATION = 19;
49             my $USAGETYPE = 20;
50              
51             my $NUMBER_OF_FIELDS = 20;
52             my $ALL = 100;
53              
54             my $IS_LITTLE_ENDIAN = unpack("h*", pack("s", 1)) =~ m/^1/;
55              
56             my $POSITIONS = {
57             $COUNTRYSHORT => [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
58             $COUNTRYLONG => [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
59             $REGION => [0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
60             $CITY => [0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
61             $LATITUDE => [0, 0, 0, 0, 0, 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
62             $LONGITUDE => [0, 0, 0, 0, 0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
63             $ZIPCODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 0, 7, 7, 7, 0, 7, 0, 7, 7, 7, 0, 7],
64             $TIMEZONE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 7, 8, 8, 8, 7, 8, 0, 8, 8, 8, 0, 8],
65             $ISP => [0, 0, 3, 0, 5, 0, 7, 5, 7, 0, 8, 0, 9, 0, 9, 0, 9, 0, 9, 7, 9, 0, 9, 7, 9],
66             $DOMAIN => [0, 0, 0, 0, 0, 0, 0, 6, 8, 0, 9, 0, 10, 0, 10, 0, 10, 0, 10, 8, 10, 0, 10, 8, 10],
67             $NETSPEED => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 11, 0, 11, 8, 11, 0, 11, 0, 11, 0, 11],
68             $IDDCODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 12, 0, 12, 0, 12, 9, 12, 0, 12],
69             $AREACODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 13, 0, 13, 0, 13, 10, 13, 0, 13],
70             $WEATHERSTATIONCODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 14, 0, 14, 0, 14, 0, 14],
71             $WEATHERSTATIONNAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 15, 0, 15, 0, 15, 0, 15],
72             $MCC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 16, 0, 16, 9, 16],
73             $MNC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 17, 0, 17, 10, 17],
74             $MOBILEBRAND => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 18, 0, 18, 11, 18],
75             $ELEVATION => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 19, 0, 19],
76             $USAGETYPE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 20],
77             };
78              
79             my $IPv4_re = qr/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
80              
81             sub open {
82 5     5 0 2614 my ($class, $db_file) = @_;
83 5 100       31 $db_file || die "Geo::IP2Location::Lite::open() requires a database path name";
84 3 100       107 CORE::open( my $handle,'<',"$db_file" ) or die "Geo::IP2Location::Lite::open() error opening $db_file: $!";
85 2         8 binmode($handle);
86 2         11 my $obj = bless {filehandle => $handle}, $class;
87 2         8 return $obj->initialize;
88             }
89              
90             sub initialize {
91 2     2 0 6 my ($obj) = @_;
92 2         13 $obj->{"databasetype"} = $obj->read8($obj->{filehandle}, 1);
93 2         8 $obj->{"databasecolumn"} = $obj->read8($obj->{filehandle}, 2);
94 2         8 $obj->{"databaseyear"} = $obj->read8($obj->{filehandle}, 3);
95 2         7 $obj->{"databasemonth"} = $obj->read8($obj->{filehandle}, 4);
96 2         12 $obj->{"databaseday"} = $obj->read8($obj->{filehandle}, 5);
97 2         8 $obj->{"ipv4databasecount"} = $obj->read32($obj->{filehandle}, 6);
98 2         7 $obj->{"ipv4databaseaddr"} = $obj->read32($obj->{filehandle}, 10);
99 2         8 $obj->{"ipv4indexbaseaddr"} = $obj->read32($obj->{filehandle}, 22);
100 2         16 return $obj;
101             }
102              
103 1     1 0 753 sub get_module_version { return $Geo::IP2Location::Lite::VERSION; }
104              
105             sub get_database_version {
106 1     1 0 4 my $obj = shift(@_);
107 1         10 return $obj->{"databaseyear"} . "." . $obj->{"databasemonth"} . "." . $obj->{"databaseday"};
108             }
109              
110             sub _get_by_pos {
111 226     226   708 my ( $obj,$ipaddr,$pos ) = @_;
112              
113 226 100       712 return $INVALID_IP_ADDRESS
114             if ! $pos;
115              
116 225         700 my ( $ipv,$ipnum ) = $obj->validate_ip( $ipaddr );
117              
118 225 100       904 return $ipv == 4
119             ? $obj->get_record( $ipnum,$pos )
120             : $INVALID_IP_ADDRESS;
121             }
122              
123 10     10 0 7615 sub get_country { return ( _get_by_pos( @_,$COUNTRYSHORT ),_get_by_pos( @_,$COUNTRYLONG ) ) }
124 14     14 0 6450 sub get_country_short { return _get_by_pos( @_,$COUNTRYSHORT ); }
125 10     10 0 42 sub get_country_long { return _get_by_pos( @_,$COUNTRYLONG ); }
126 10     10 0 57 sub get_region { return _get_by_pos( @_,$REGION ); }
127 10     10 0 46 sub get_city { return _get_by_pos( @_,$CITY ); }
128 10     10 0 39 sub get_isp { return _get_by_pos( @_,$ISP ); }
129 11     11 0 49 sub get_latitude { return _get_by_pos( @_,$LATITUDE ); }
130 10     10 0 45 sub get_zipcode { return _get_by_pos( @_,$ZIPCODE ); }
131 10     10 0 43 sub get_longitude { return _get_by_pos( @_,$LONGITUDE ); }
132 10     10 0 44 sub get_domain { return _get_by_pos( @_,$DOMAIN ); }
133 10     10 0 65 sub get_timezone { return _get_by_pos( @_,$TIMEZONE ); }
134 10     10 0 44 sub get_netspeed { return _get_by_pos( @_,$NETSPEED ); }
135 10     10 0 40 sub get_iddcode { return _get_by_pos( @_,$IDDCODE ); }
136 10     10 0 43 sub get_areacode { return _get_by_pos( @_,$AREACODE ); }
137 10     10 0 45 sub get_weatherstationcode { return _get_by_pos( @_,$WEATHERSTATIONCODE ); }
138 10     10 0 45 sub get_weatherstationname { return _get_by_pos( @_,$WEATHERSTATIONNAME ); }
139 10     10 0 44 sub get_mcc { return _get_by_pos( @_,$MCC ); }
140 10     10 0 44 sub get_mnc { return _get_by_pos( @_,$MNC ); }
141 10     10 0 46 sub get_mobilebrand { return _get_by_pos( @_,$MOBILEBRAND ); }
142 10     10 0 43 sub get_elevation { return _get_by_pos( @_,$ELEVATION ); }
143 10     10 0 45 sub get_usagetype { return _get_by_pos( @_,$USAGETYPE ); }
144              
145             sub get_all {
146 1     1 0 4 my @res = _get_by_pos( @_,$ALL );
147              
148 1 50       13 if ( $res[0] eq $INVALID_IP_ADDRESS ) {
149 1         23 return ( $INVALID_IP_ADDRESS x $NUMBER_OF_FIELDS );
150             }
151              
152 0         0 return @res;
153             }
154              
155             sub get_record {
156 227     227 0 641 my ( $obj,$ipnum,$mode ) = @_;
157 227         1114 my $dbtype= $obj->{"databasetype"};
158              
159 227 100       623 $mode = 0 if ! defined $mode;
160              
161 227 100       682 if ($ipnum eq "") {
162 2 100       8 if ($mode == $ALL) {
163 1         11 return ( $NO_IP x $NUMBER_OF_FIELDS );
164             } else {
165 1         10 return $NO_IP;
166             }
167             }
168              
169 225 100       615 if ( $mode != $ALL ) {
170 224 100       798 if ( $POSITIONS->{$mode}[$dbtype] == 0 ) {
171 181         1292 return $NOT_SUPPORTED;
172             }
173             }
174            
175 44         101 my $realipno = $ipnum;
176 44         106 my $handle = $obj->{"filehandle"};
177 44         104 my $baseaddr = $obj->{"ipv4databaseaddr"};
178 44         117 my $dbcount = $obj->{"ipv4databasecount"};
179 44         101 my $dbcolumn = $obj->{"databasecolumn"};
180 44         101 my $indexbaseaddr = $obj->{"ipv4indexbaseaddr"};
181              
182 44         114 my $ipnum1_2 = int($ipnum >> 16);
183 44         111 my $indexaddr = $indexbaseaddr + ($ipnum1_2 << 3);
184              
185 44         103 my $low = 0;
186 44         100 my $high = $dbcount;
187              
188 44 50       132 if ($indexbaseaddr > 0) {
189 0         0 $low = $obj->read32($handle, $indexaddr);
190 0         0 $high = $obj->read32($handle, $indexaddr + 4);
191             }
192              
193 44         96 my $mid = 0;
194 44         85 my $ipfrom = 0;
195 44         86 my $ipto = 0;
196 44         89 my $ipno = 0;
197              
198 44 100       119 if ($realipno == $MAX_IPV4_RANGE) {
199 1         3 $ipno = $realipno - 1;
200             } else {
201 43         96 $ipno = $realipno;
202             }
203              
204 44         136 while ($low <= $high) {
205 570         1202 $mid = int(($low + $high) >> 1);
206 570         1643 $ipfrom = $obj->read32($handle, $baseaddr + $mid * $dbcolumn * 4);
207 570         1890 $ipto = $obj->read32($handle, $baseaddr + ($mid + 1) * $dbcolumn * 4);
208 570 100 100     2407 if (($ipno >= $ipfrom) && ($ipno < $ipto)) {
209              
210 44         93 my @return_vals;
211              
212 44 100       148 foreach my $pos (
213             $mode == $ALL
214             ? ( $COUNTRYSHORT .. $NUMBER_OF_FIELDS )
215             : $mode
216             ) {
217              
218 63 100       228 if ( $POSITIONS->{$pos}[$dbtype] == 0 ) {
219 18         50 push( @return_vals, $NOT_SUPPORTED );
220             } else {
221 45 50 33     249 if ( $pos == $LATITUDE or $pos == $LONGITUDE ) {
    100          
222              
223             push( @return_vals, sprintf( "%.6f",$obj->readFloat(
224             $handle,
225 0         0 $baseaddr + ( $mid * $dbcolumn * 4 ) + 4 * ( $POSITIONS->{$pos}[$dbtype] -1 )
226             ) ) );
227              
228             } elsif ( $pos == $COUNTRYLONG ) {
229              
230             push( @return_vals, $obj->readStr(
231             $handle,
232 21         99 $obj->read32( $handle,$baseaddr + ( $mid * $dbcolumn * 4 ) + 4 * ( $POSITIONS->{$pos}[$dbtype] -1 ) ) +3
233             ) );
234              
235             } else {
236              
237             my $return_val = $obj->readStr(
238             $handle,
239 24         107 $obj->read32( $handle,$baseaddr + ( $mid * $dbcolumn * 4 ) + 4 * ( $POSITIONS->{$pos}[$dbtype] -1 ) )
240             );
241              
242 24 100 66     167 if ( $pos == $COUNTRYSHORT && $return_val eq 'UK' ) {
243 6         15 $return_val = 'GB';
244             }
245              
246 24         93 push( @return_vals,$return_val );
247             }
248             }
249             }
250              
251 44 100       381 return ( $mode == $ALL ) ? @return_vals : $return_vals[0];
252              
253             } else {
254 526 100       1282 if ($ipno < $ipfrom) {
255 290         828 $high = $mid - 1;
256             } else {
257 236         667 $low = $mid + 1;
258             }
259             }
260             }
261              
262 0         0 return $UNKNOWN;
263             }
264              
265             sub read32 {
266 1191     1191 0 2936 my ($obj, $handle, $position) = @_;
267 1191         2392 my $data = "";
268 1191         4362 seek($handle, $position-1, 0);
269 1191         6112 read($handle, $data, 4);
270 1191         4018 return unpack("V", $data);
271             }
272              
273             sub read8 {
274 10     10 0 23 my ($obj, $handle, $position) = @_;
275 10         18 my $data = "";
276 10         35 seek($handle, $position-1, 0);
277 10         72 read($handle, $data, 1);
278 10         42 return unpack("C", $data);
279             }
280              
281             sub readStr {
282 45     45 0 131 my ($obj, $handle, $position) = @_;
283 45         100 my $data = "";
284 45         92 my $string = "";
285 45         165 seek($handle, $position, 0);
286 45         205 read($handle, $data, 1);
287 45         178 read($handle, $string, unpack("C", $data));
288 45         221 return $string;
289             }
290              
291             sub readFloat {
292 0     0 0 0 my ($obj, $handle, $position) = @_;
293 0         0 my $data = "";
294 0         0 seek($handle, $position-1, 0);
295 0         0 read($handle, $data, 4);
296              
297 0 0       0 return $IS_LITTLE_ENDIAN
298             ? unpack("f", $data) # "LITTLE ENDIAN - x86\n";
299             : unpack("f", reverse($data)); # "BIG ENDIAN - MAC\n";
300             }
301              
302             sub validate_ip {
303 225     225 0 569 my ( $obj,$ip ) = @_;
304 225         455 my $ipv = -1;
305 225         439 my $ipnum = -1;
306             #name server lookup if domain name
307 225         666 $ip = $obj->name2ip($ip);
308            
309 225 100       747 if ($obj->ip_is_ipv4($ip)) {
310             #ipv4 address
311 223         480 $ipv = 4;
312 223         650 $ipnum = $obj->ip2no($ip);
313             }
314 225         830 return ($ipv, $ipnum);
315             }
316              
317             sub ip2no {
318 223     223 0 559 my ( $obj,$ip ) = @_;
319 223         941 my @block = split(/\./, $ip);
320 223         543 my $no = 0;
321 223         514 $no = $block[3];
322 223         739 $no = $no + $block[2] * 256;
323 223         551 $no = $no + $block[1] * 256 * 256;
324 223         531 $no = $no + $block[0] * 256 * 256 * 256;
325 223         784 return $no;
326             }
327              
328             sub name2ip {
329 227     227 0 596 my ( $obj,$host ) = @_;
330 227 100       599 return "" if ! defined($host);
331 226         491 my $ip_address = "";
332 226 100       2291 if ($host =~ $IPv4_re){
333 223         714 $ip_address = $host;
334             } else {
335 3 100       150148 if ( my $ip = gethostbyname($host) ) {
336 1         52 $ip_address = join('.', unpack('C4',($ip)[4]));
337             }
338             }
339 226         645 return $ip_address;
340             }
341              
342             sub ip_is_ipv4 {
343 225     225 0 599 my ( $obj,$ip ) = @_;
344 225 100       1565 if ($ip =~ $IPv4_re) {
345 223         951 return 1;
346             } else {
347 2         14 return 0;
348             }
349             }
350              
351             1;
352              
353             __END__