File Coverage

blib/lib/Geo/IP2Location/Lite.pm
Criterion Covered Total %
statement 148 155 95.4
branch 47 52 90.3
condition 6 9 66.6
subroutine 37 38 97.3
pod 0 35 0.0
total 238 289 82.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   3608 use strict;
  2         5  
  2         71  
20 2     2   12 use warnings;
  2         3  
  2         4925  
21              
22             $Geo::IP2Location::Lite::VERSION = '0.12';
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 1906 my ($class, $db_file) = @_;
83 5 100       35 $db_file || die "Geo::IP2Location::Lite::open() requires a database path name";
84 3 100       142 CORE::open( my $handle,'<',"$db_file" ) or die "Geo::IP2Location::Lite::open() error opening $db_file: $!";
85 2         9 binmode($handle);
86 2         10 my $obj = bless {filehandle => $handle}, $class;
87 2         7 return $obj->initialize;
88             }
89              
90             sub initialize {
91 2     2 0 6 my ($obj) = @_;
92 2         10 $obj->{"databasetype"} = $obj->read8($obj->{filehandle}, 1);
93 2         6 $obj->{"databasecolumn"} = $obj->read8($obj->{filehandle}, 2);
94 2         7 $obj->{"databaseyear"} = $obj->read8($obj->{filehandle}, 3);
95 2         6 $obj->{"databasemonth"} = $obj->read8($obj->{filehandle}, 4);
96 2         13 $obj->{"databaseday"} = $obj->read8($obj->{filehandle}, 5);
97 2         8 $obj->{"ipv4databasecount"} = $obj->read32($obj->{filehandle}, 6);
98 2         6 $obj->{"ipv4databaseaddr"} = $obj->read32($obj->{filehandle}, 10);
99 2         5 $obj->{"ipv4indexbaseaddr"} = $obj->read32($obj->{filehandle}, 22);
100 2         15 return $obj;
101             }
102              
103 1     1 0 539 sub get_module_version { return $Geo::IP2Location::Lite::VERSION; }
104              
105             sub get_database_version {
106 1     1 0 10 my $obj = shift(@_);
107 1         6 return $obj->{"databaseyear"} . "." . $obj->{"databasemonth"} . "." . $obj->{"databaseday"};
108             }
109              
110             sub _get_by_pos {
111 226     226   467 my ( $obj,$ipaddr,$pos ) = @_;
112              
113 226 100       546 return $INVALID_IP_ADDRESS
114             if ! $pos;
115              
116 225         512 my ( $ipv,$ipnum ) = $obj->validate_ip( $ipaddr );
117              
118 225 100       646 return $ipv == 4
119             ? $obj->get_record( $ipnum,$pos )
120             : $INVALID_IP_ADDRESS;
121             }
122              
123 10     10 0 5389 sub get_country { return ( _get_by_pos( @_,$COUNTRYSHORT ),_get_by_pos( @_,$COUNTRYLONG ) ) }
124 14     14 0 4891 sub get_country_short { return _get_by_pos( @_,$COUNTRYSHORT ); }
125 10     10 0 24 sub get_country_long { return _get_by_pos( @_,$COUNTRYLONG ); }
126 10     10 0 24 sub get_region { return _get_by_pos( @_,$REGION ); }
127 10     10 0 25 sub get_city { return _get_by_pos( @_,$CITY ); }
128 10     10 0 22 sub get_isp { return _get_by_pos( @_,$ISP ); }
129 11     11 0 33 sub get_latitude { return _get_by_pos( @_,$LATITUDE ); }
130 10     10 0 26 sub get_zipcode { return _get_by_pos( @_,$ZIPCODE ); }
131 10     10 0 24 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 22 sub get_timezone { return _get_by_pos( @_,$TIMEZONE ); }
134 10     10 0 24 sub get_netspeed { return _get_by_pos( @_,$NETSPEED ); }
135 10     10 0 27 sub get_iddcode { return _get_by_pos( @_,$IDDCODE ); }
136 10     10 0 26 sub get_areacode { return _get_by_pos( @_,$AREACODE ); }
137 10     10 0 25 sub get_weatherstationcode { return _get_by_pos( @_,$WEATHERSTATIONCODE ); }
138 10     10 0 26 sub get_weatherstationname { return _get_by_pos( @_,$WEATHERSTATIONNAME ); }
139 10     10 0 26 sub get_mcc { return _get_by_pos( @_,$MCC ); }
140 10     10 0 25 sub get_mnc { return _get_by_pos( @_,$MNC ); }
141 10     10 0 26 sub get_mobilebrand { return _get_by_pos( @_,$MOBILEBRAND ); }
142 10     10 0 28 sub get_elevation { return _get_by_pos( @_,$ELEVATION ); }
143 10     10 0 29 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       14 if ( $res[0] eq $INVALID_IP_ADDRESS ) {
149 1         48 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 448 my ( $obj,$ipnum,$mode ) = @_;
157 227         387 my $dbtype= $obj->{"databasetype"};
158              
159 227 100       466 $mode = 0 if ! defined $mode;
160              
161 227 100       500 if ($ipnum eq "") {
162 2 100       5 if ($mode == $ALL) {
163 1         27 return ( $NO_IP x $NUMBER_OF_FIELDS );
164             } else {
165 1         9 return $NO_IP;
166             }
167             }
168              
169 225 100       421 if ( $mode != $ALL ) {
170 224 100       566 if ( $POSITIONS->{$mode}[$dbtype] == 0 ) {
171 181         694 return $NOT_SUPPORTED;
172             }
173             }
174            
175 44         76 my $realipno = $ipnum;
176 44         87 my $handle = $obj->{"filehandle"};
177 44         67 my $baseaddr = $obj->{"ipv4databaseaddr"};
178 44         67 my $dbcount = $obj->{"ipv4databasecount"};
179 44         62 my $dbcolumn = $obj->{"databasecolumn"};
180 44         78 my $indexbaseaddr = $obj->{"ipv4indexbaseaddr"};
181              
182 44         76 my $ipnum1_2 = int($ipnum >> 16);
183 44         73 my $indexaddr = $indexbaseaddr + ($ipnum1_2 << 3);
184              
185 44         60 my $low = 0;
186 44         64 my $high = $dbcount;
187 44 50       86 if ($indexbaseaddr > 0) {
188 0         0 $low = $obj->read32($handle, $indexaddr);
189 0         0 $high = $obj->read32($handle, $indexaddr + 4);
190             }
191 44         55 my $mid = 0;
192 44         57 my $ipfrom = 0;
193 44         64 my $ipto = 0;
194 44         56 my $ipno = 0;
195              
196 44 100       85 if ($realipno == $MAX_IPV4_RANGE) {
197 1         3 $ipno = $realipno - 1;
198             } else {
199 43         64 $ipno = $realipno;
200             }
201              
202 44         90 while ($low <= $high) {
203 570         810 $mid = int(($low + $high) >> 1);
204 570         1163 $ipfrom = $obj->read32($handle, $baseaddr + $mid * $dbcolumn * 4);
205 570         1387 $ipto = $obj->read32($handle, $baseaddr + ($mid + 1) * $dbcolumn * 4);
206 570 100 100     1846 if (($ipno >= $ipfrom) && ($ipno < $ipto)) {
207             # read whole results string into temp string and parse results from memory
208 44         63 my $raw_positions_row;
209 44         432 seek($handle, ($baseaddr + $mid * $dbcolumn * 4) - 1, 0);
210 44         329 read($handle, $raw_positions_row, $dbcolumn * 4);
211              
212 44         97 my @return_vals;
213              
214 44 100       119 foreach my $pos (
215             $mode == $ALL
216             ? ( $COUNTRYSHORT .. $NUMBER_OF_FIELDS )
217             : $mode
218             ) {
219              
220 63 100       159 if ( $POSITIONS->{$pos}[$dbtype] == 0 ) {
221 18         34 push( @return_vals, $NOT_SUPPORTED );
222             } else {
223 45 50 33     220 if ( $pos == $LATITUDE or $pos == $LONGITUDE ) {
    100          
224              
225             push( @return_vals, sprintf( "%.6f",$obj->readFloat(
226 0         0 substr($raw_positions_row, 4 * ( $POSITIONS->{$pos}[$dbtype] -1 ), 4)
227             ) ) );
228              
229             } elsif ( $pos == $COUNTRYLONG ) {
230              
231             push( @return_vals, $obj->readStr(
232             $handle,
233 21         108 unpack("V", substr($raw_positions_row, 4 * ( $POSITIONS->{$pos}[$dbtype] -1 ), 4 ) ) + 3
234             ) );
235              
236             } else {
237              
238             my $return_val = $obj->readStr(
239             $handle,
240 24         113 unpack("V", substr($raw_positions_row, 4 * ( $POSITIONS->{$pos}[$dbtype]-1), 4) )
241             );
242              
243 24 100 66     120 if ( $pos == $COUNTRYSHORT && $return_val eq 'UK' ) {
244 6         11 $return_val = 'GB';
245             }
246              
247 24         65 push( @return_vals,$return_val );
248             }
249             }
250             }
251              
252 44 100       290 return ( $mode == $ALL ) ? @return_vals : $return_vals[0];
253              
254             } else {
255 526 100       865 if ($ipno < $ipfrom) {
256 290         605 $high = $mid - 1;
257             } else {
258 236         490 $low = $mid + 1;
259             }
260             }
261             }
262              
263 0         0 return $UNKNOWN;
264             }
265              
266             sub read32 {
267 1146     1146 0 1905 my ($obj, $handle, $position) = @_;
268 1146         1600 my $data = "";
269 1146         10627 seek($handle, $position-1, 0);
270 1146         8235 read($handle, $data, 4);
271 1146         3664 return unpack("V", $data);
272             }
273              
274             sub read8 {
275 10     10 0 21 my ($obj, $handle, $position) = @_;
276 10         17 my $data = "";
277 10         85 seek($handle, $position-1, 0);
278 10         114 read($handle, $data, 1);
279 10         43 return unpack("C", $data);
280             }
281              
282             sub readStr {
283 45     45 0 97 my ($obj, $handle, $position) = @_;
284 45         66 my $data = "";
285 45         67 my $string = "";
286 45         405 seek($handle, $position, 0);
287 45         302 read($handle, $data, 1);
288 45         176 read($handle, $string, unpack("C", $data));
289 45         165 return $string;
290             }
291              
292             sub readFloat {
293 0     0 0 0 my ($obj, $data) = @_;
294 0 0       0 return $IS_LITTLE_ENDIAN
295             ? unpack("f", $data) # "LITTLE ENDIAN - x86\n";
296             : unpack("f", reverse($data)); # "BIG ENDIAN - MAC\n";
297             }
298              
299             sub validate_ip {
300 225     225 0 399 my ( $obj,$ip ) = @_;
301 225         327 my $ipv = -1;
302 225         292 my $ipnum = -1;
303             #name server lookup if domain name
304 225         459 $ip = $obj->name2ip($ip);
305            
306 225 100       517 if ($obj->ip_is_ipv4($ip)) {
307             #ipv4 address
308 223         318 $ipv = 4;
309 223         462 $ipnum = $obj->ip2no($ip);
310             }
311 225         502 return ($ipv, $ipnum);
312             }
313              
314             sub ip2no {
315 223     223 0 390 my ( $obj,$ip ) = @_;
316 223         727 my @block = split(/\./, $ip);
317 223         351 my $no = 0;
318 223         353 $no = $block[3];
319 223         592 $no = $no + $block[2] * 256;
320 223         377 $no = $no + $block[1] * 256 * 256;
321 223         334 $no = $no + $block[0] * 256 * 256 * 256;
322 223         501 return $no;
323             }
324              
325             sub name2ip {
326 227     227 0 434 my ( $obj,$host ) = @_;
327 227 100       465 return "" if ! defined($host);
328 226         356 my $ip_address = "";
329 226 100       1768 if ($host =~ $IPv4_re){
330 223         402 $ip_address = $host;
331             } else {
332 3 100       116707 if ( my $ip = gethostbyname($host) ) {
333 1         62 $ip_address = join('.', unpack('C4',($ip)[4]));
334             }
335             }
336 226         459 return $ip_address;
337             }
338              
339             sub ip_is_ipv4 {
340 225     225 0 401 my ( $obj,$ip ) = @_;
341 225 100       908 if ($ip =~ $IPv4_re) {
342 223         623 return 1;
343             } else {
344 2         12 return 0;
345             }
346             }
347              
348             1;
349              
350             __END__