File Coverage

blib/lib/Geo/IP2Location/Lite.pm
Criterion Covered Total %
statement 149 156 95.5
branch 48 54 88.8
condition 7 12 58.3
subroutine 37 38 97.3
pod 0 35 0.0
total 241 295 81.6


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   3377 use strict;
  2         5  
  2         59  
20 2     2   10 use warnings;
  2         4  
  2         5057  
21              
22             $Geo::IP2Location::Lite::VERSION = '0.13';
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 1968 my ($class, $db_file) = @_;
83 5 100       35 $db_file || die "Geo::IP2Location::Lite::open() requires a database path name";
84 3 100       165 CORE::open( my $handle,'<',"$db_file" ) or die "Geo::IP2Location::Lite::open() error opening $db_file: $!";
85 2         10 binmode($handle);
86 2         10 my $obj = bless {filehandle => $handle}, $class;
87 2         8 return $obj->initialize;
88             }
89              
90             sub initialize {
91 2     2 0 5 my ($obj) = @_;
92 2         13 $obj->{"databasetype"} = $obj->read8($obj->{filehandle}, 1);
93 2         8 $obj->{"databasecolumn"} = $obj->read8($obj->{filehandle}, 2);
94 2         7 $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         7 $obj->{"ipv4databasecount"} = $obj->read32($obj->{filehandle}, 6);
98 2         6 $obj->{"ipv4databaseaddr"} = $obj->read32($obj->{filehandle}, 10);
99 2         7 $obj->{"ipv4indexbaseaddr"} = $obj->read32($obj->{filehandle}, 22);
100 2         18 return $obj;
101             }
102              
103 1     1 0 550 sub get_module_version { return $Geo::IP2Location::Lite::VERSION; }
104              
105             sub get_database_version {
106 1     1 0 3 my $obj = shift(@_);
107 1         9 return $obj->{"databaseyear"} . "." . $obj->{"databasemonth"} . "." . $obj->{"databaseday"};
108             }
109              
110             sub _get_by_pos {
111 226     226   482 my ( $obj,$ipaddr,$pos ) = @_;
112              
113 226 100       510 return $INVALID_IP_ADDRESS
114             if ! $pos;
115              
116 225         515 my ( $ipv,$ipnum ) = $obj->validate_ip( $ipaddr );
117              
118 225 100       680 return $ipv == 4
119             ? $obj->get_record( $ipnum,$pos )
120             : $INVALID_IP_ADDRESS;
121             }
122              
123 10     10 0 5731 sub get_country { return ( _get_by_pos( @_,$COUNTRYSHORT ),_get_by_pos( @_,$COUNTRYLONG ) ) }
124 14     14 0 4901 sub get_country_short { return _get_by_pos( @_,$COUNTRYSHORT ); }
125 10     10 0 32 sub get_country_long { return _get_by_pos( @_,$COUNTRYLONG ); }
126 10     10 0 36 sub get_region { return _get_by_pos( @_,$REGION ); }
127 10     10 0 34 sub get_city { return _get_by_pos( @_,$CITY ); }
128 10     10 0 41 sub get_isp { return _get_by_pos( @_,$ISP ); }
129 11     11 0 37 sub get_latitude { return _get_by_pos( @_,$LATITUDE ); }
130 10     10 0 35 sub get_zipcode { return _get_by_pos( @_,$ZIPCODE ); }
131 10     10 0 45 sub get_longitude { return _get_by_pos( @_,$LONGITUDE ); }
132 10     10 0 32 sub get_domain { return _get_by_pos( @_,$DOMAIN ); }
133 10     10 0 35 sub get_timezone { return _get_by_pos( @_,$TIMEZONE ); }
134 10     10 0 30 sub get_netspeed { return _get_by_pos( @_,$NETSPEED ); }
135 10     10 0 30 sub get_iddcode { return _get_by_pos( @_,$IDDCODE ); }
136 10     10 0 32 sub get_areacode { return _get_by_pos( @_,$AREACODE ); }
137 10     10 0 32 sub get_weatherstationcode { return _get_by_pos( @_,$WEATHERSTATIONCODE ); }
138 10     10 0 35 sub get_weatherstationname { return _get_by_pos( @_,$WEATHERSTATIONNAME ); }
139 10     10 0 34 sub get_mcc { return _get_by_pos( @_,$MCC ); }
140 10     10 0 36 sub get_mnc { return _get_by_pos( @_,$MNC ); }
141 10     10 0 32 sub get_mobilebrand { return _get_by_pos( @_,$MOBILEBRAND ); }
142 10     10 0 33 sub get_elevation { return _get_by_pos( @_,$ELEVATION ); }
143 10     10 0 31 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       52 if ( $res[0] eq $INVALID_IP_ADDRESS ) {
149 1         26 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 420 my ( $obj,$ipnum,$mode ) = @_;
157 227         454 my $dbtype= $obj->{"databasetype"};
158              
159 227 100       496 $mode = 0 if ! defined $mode;
160              
161 227 100       491 if ($ipnum eq "") {
162 2 100       8 if ($mode == $ALL) {
163 1         29 return ( $NO_IP x $NUMBER_OF_FIELDS );
164             } else {
165 1         9 return $NO_IP;
166             }
167             }
168              
169 225 100       462 if ( $mode != $ALL ) {
170 224 100       560 if ( $POSITIONS->{$mode}[$dbtype] == 0 ) {
171 181         730 return $NOT_SUPPORTED;
172             }
173             }
174            
175 44         79 my $realipno = $ipnum;
176 44         79 my $handle = $obj->{"filehandle"};
177 44         63 my $baseaddr = $obj->{"ipv4databaseaddr"};
178 44         74 my $dbcount = $obj->{"ipv4databasecount"};
179 44         64 my $dbcolumn = $obj->{"databasecolumn"};
180 44         72 my $indexbaseaddr = $obj->{"ipv4indexbaseaddr"};
181              
182 44         78 my $ipnum1_2 = int($ipnum >> 16);
183 44         78 my $indexaddr = $indexbaseaddr + ($ipnum1_2 << 3);
184              
185 44         58 my $low = 0;
186 44         67 my $high = $dbcount;
187 44 50       90 if ($indexbaseaddr > 0) {
188 0         0 $low = $obj->read32($handle, $indexaddr);
189 0         0 $high = $obj->read32($handle, $indexaddr + 4);
190             }
191 44         62 my $mid = 0;
192 44         61 my $ipfrom = 0;
193 44         60 my $ipto = 0;
194 44         57 my $ipno = 0;
195              
196 44 100       80 if ($realipno == $MAX_IPV4_RANGE) {
197 1         3 $ipno = $realipno - 1;
198             } else {
199 43         65 $ipno = $realipno;
200             }
201              
202 44         91 while ($low <= $high) {
203 570         847 $mid = int(($low + $high) >> 1);
204 570         1286 $ipfrom = $obj->read32($handle, $baseaddr + $mid * $dbcolumn * 4);
205 570         1419 $ipto = $obj->read32($handle, $baseaddr + ($mid + 1) * $dbcolumn * 4);
206              
207 570 50 33     2167 return $UNKNOWN if ( ! defined( $ipfrom ) || ! defined( $ipto ) );
208            
209 570 100 100     1403 if (($ipno >= $ipfrom) && ($ipno < $ipto)) {
210             # read whole results string into temp string and parse results from memory
211 44         58 my $raw_positions_row;
212 44         458 seek($handle, ($baseaddr + $mid * $dbcolumn * 4) - 1, 0);
213 44         359 read($handle, $raw_positions_row, $dbcolumn * 4);
214              
215 44         104 my @return_vals;
216              
217 44 100       131 foreach my $pos (
218             $mode == $ALL
219             ? ( $COUNTRYSHORT .. $NUMBER_OF_FIELDS )
220             : $mode
221             ) {
222              
223 63 100       156 if ( $POSITIONS->{$pos}[$dbtype] == 0 ) {
224 18         51 push( @return_vals, $NOT_SUPPORTED );
225             } else {
226 45 50 33     191 if ( $pos == $LATITUDE or $pos == $LONGITUDE ) {
    100          
227              
228             push( @return_vals, sprintf( "%.6f",$obj->readFloat(
229 0         0 substr($raw_positions_row, 4 * ( $POSITIONS->{$pos}[$dbtype] -1 ), 4)
230             ) ) );
231              
232             } elsif ( $pos == $COUNTRYLONG ) {
233              
234             push( @return_vals, $obj->readStr(
235             $handle,
236 21         105 unpack("V", substr($raw_positions_row, 4 * ( $POSITIONS->{$pos}[$dbtype] -1 ), 4 ) ) + 3
237             ) );
238              
239             } else {
240              
241             my $return_val = $obj->readStr(
242             $handle,
243 24         112 unpack("V", substr($raw_positions_row, 4 * ( $POSITIONS->{$pos}[$dbtype]-1), 4) )
244             );
245              
246 24 100 66     114 if ( $pos == $COUNTRYSHORT && $return_val eq 'UK' ) {
247 6         11 $return_val = 'GB';
248             }
249              
250 24         71 push( @return_vals,$return_val );
251             }
252             }
253             }
254              
255 44 100       293 return ( $mode == $ALL ) ? @return_vals : $return_vals[0];
256              
257             } else {
258 526 100       1002 if ($ipno < $ipfrom) {
259 290         582 $high = $mid - 1;
260             } else {
261 236         464 $low = $mid + 1;
262             }
263             }
264             }
265              
266 0         0 return $UNKNOWN;
267             }
268              
269             sub read32 {
270 1146     1146 0 1922 my ($obj, $handle, $position) = @_;
271 1146         1624 my $data = "";
272 1146         11288 seek($handle, $position-1, 0);
273 1146         9564 read($handle, $data, 4);
274 1146         3978 return unpack("V", $data);
275             }
276              
277             sub read8 {
278 10     10 0 66 my ($obj, $handle, $position) = @_;
279 10         21 my $data = "";
280 10         90 seek($handle, $position-1, 0);
281 10         120 read($handle, $data, 1);
282 10         43 return unpack("C", $data);
283             }
284              
285             sub readStr {
286 45     45 0 96 my ($obj, $handle, $position) = @_;
287 45         80 my $data = "";
288 45         70 my $string = "";
289 45         458 seek($handle, $position, 0);
290 45         341 read($handle, $data, 1);
291 45         178 read($handle, $string, unpack("C", $data));
292 45         165 return $string;
293             }
294              
295             sub readFloat {
296 0     0 0 0 my ($obj, $data) = @_;
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 359 my ( $obj,$ip ) = @_;
304 225         332 my $ipv = -1;
305 225         295 my $ipnum = -1;
306             #name server lookup if domain name
307 225         442 $ip = $obj->name2ip($ip);
308            
309 225 100       480 if ($obj->ip_is_ipv4($ip)) {
310             #ipv4 address
311 223         337 $ipv = 4;
312 223         443 $ipnum = $obj->ip2no($ip);
313             }
314 225         494 return ($ipv, $ipnum);
315             }
316              
317             sub ip2no {
318 223     223 0 395 my ( $obj,$ip ) = @_;
319 223         777 my @block = split(/\./, $ip);
320 223         370 my $no = 0;
321 223         342 $no = $block[3];
322 223         582 $no = $no + $block[2] * 256;
323 223         365 $no = $no + $block[1] * 256 * 256;
324 223         332 $no = $no + $block[0] * 256 * 256 * 256;
325 223         516 return $no;
326             }
327              
328             sub name2ip {
329 227     227 0 420 my ( $obj,$host ) = @_;
330 227 100       430 return "" if ! defined($host);
331 226         321 my $ip_address = "";
332 226 100       1923 if ($host =~ $IPv4_re){
333 223         451 $ip_address = $host;
334             } else {
335 3 100       128108 if ( my $ip = gethostbyname($host) ) {
336 1         63 $ip_address = join('.', unpack('C4',($ip)[4]));
337             }
338             }
339 226         468 return $ip_address;
340             }
341              
342             sub ip_is_ipv4 {
343 225     225 0 411 my ( $obj,$ip ) = @_;
344 225 100       916 if ($ip =~ $IPv4_re) {
345 223         585 return 1;
346             } else {
347 2         14 return 0;
348             }
349             }
350              
351             1;
352              
353             __END__