File Coverage

blib/lib/Geo/IP/PurePerl.pm
Criterion Covered Total %
statement 114 418 27.2
branch 2 118 1.6
condition 0 42 0.0
subroutine 37 66 56.0
pod 17 24 70.8
total 170 668 25.4


line stmt bran cond sub pod time code
1             package Geo::IP::PurePerl;
2              
3 1     1   457 use strict;
  1         1  
  1         24  
4 1     1   366 use FileHandle;
  1         7593  
  1         5  
5 1     1   223 use File::Spec;
  1         5  
  1         80  
6              
7             BEGIN {
8 1 50   1   78 if ( $] >= 5.008 ) {
9 1         467 require Encode;
10 1         6535 Encode->import(qw/ decode /);
11             }
12             else {
13             *decode = sub {
14 0         0 local $_ = $_[1];
15 1     1   463 use bytes;
  1         7  
  1         4  
16 0         0 s/([\x80-\xff])/my $c = ord($1);
  0         0  
17 0 0       0 my $p = $c >= 192 ? 1 : 0;
18 0         0 pack ( 'CC' => 0xc2 + $p , $c & ~0x40 ); /ge;
19 0         0 return $_;
20 0         0 };
21             }
22             };
23              
24              
25 1     1   6 use vars qw( @ISA $VERSION @EXPORT $OPEN_TYPE_PATH );
  1         1  
  1         55  
26              
27 1     1   3 use constant GEOIP_CHARSET_ISO_8859_1 => 0;
  1         1  
  1         46  
28 1     1   3 use constant GEOIP_CHARSET_UTF8 => 1;
  1         1  
  1         33  
29              
30 1     1   3 use constant FULL_RECORD_LENGTH => 50;
  1         1  
  1         33  
31 1     1   3 use constant GEOIP_COUNTRY_BEGIN => 16776960;
  1         1  
  1         36  
32 1     1   3 use constant RECORD_LENGTH => 3;
  1         1  
  1         30  
33 1     1   3 use constant GEOIP_STATE_BEGIN_REV0 => 16700000;
  1         1  
  1         28  
34 1     1   2 use constant GEOIP_STATE_BEGIN_REV1 => 16000000;
  1         1  
  1         31  
35 1     1   7 use constant STRUCTURE_INFO_MAX_SIZE => 20;
  1         1  
  1         33  
36 1     1   3 use constant DATABASE_INFO_MAX_SIZE => 100;
  1         1  
  1         29  
37 1     1   3 use constant GEOIP_COUNTRY_EDITION => 1;
  1         1  
  1         34  
38 1     1   3 use constant GEOIP_REGION_EDITION_REV0 => 7;
  1         1  
  1         42  
39 1     1   5 use constant GEOIP_REGION_EDITION_REV1 => 3;
  1         2  
  1         57  
40 1     1   5 use constant GEOIP_CITY_EDITION_REV0 => 6;
  1         1  
  1         61  
41 1     1   6 use constant GEOIP_CITY_EDITION_REV1 => 2;
  1         4  
  1         50  
42 1     1   5 use constant GEOIP_ORG_EDITION => 5;
  1         2  
  1         55  
43 1     1   5 use constant GEOIP_ISP_EDITION => 4;
  1         1  
  1         49  
44 1     1   5 use constant GEOIP_PROXY_EDITION => 8;
  1         2  
  1         54  
45 1     1   6 use constant GEOIP_ASNUM_EDITION => 9;
  1         1  
  1         59  
46 1     1   5 use constant GEOIP_NETSPEED_EDITION => 10;
  1         1  
  1         56  
47 1     1   6 use constant GEOIP_DOMAIN_EDITION => 11;
  1         1  
  1         54  
48 1     1   5 use constant SEGMENT_RECORD_LENGTH => 3;
  1         1  
  1         54  
49 1     1   5 use constant STANDARD_RECORD_LENGTH => 3;
  1         1  
  1         61  
50 1     1   6 use constant ORG_RECORD_LENGTH => 4;
  1         1  
  1         50  
51 1     1   4 use constant MAX_RECORD_LENGTH => 4;
  1         2  
  1         54  
52 1     1   4 use constant MAX_ORG_RECORD_LENGTH => 300;
  1         2  
  1         47  
53 1     1   4 use constant US_OFFSET => 1;
  1         2  
  1         51  
54 1     1   4 use constant CANADA_OFFSET => 677;
  1         1  
  1         45  
55 1     1   5 use constant WORLD_OFFSET => 1353;
  1         1  
  1         72  
56 1     1   5 use constant FIPS_RANGE => 360;
  1         15  
  1         105  
57              
58             $VERSION = '1.26';
59              
60             require Exporter;
61             @ISA = qw(Exporter);
62              
63             # cheat --- try to load Sys::Mmap
64             BEGIN {
65 1     1   2 eval {
66             # wrap into eval again, as workaround for centos / mod_perl issue
67             # seems they use $@ without eval somewhere
68            
69             eval "require Sys::Mmap"
70             ? Sys::Mmap->import
71 1 50       65 : do {
72 1         3 for (qw/ PROT_READ MAP_PRIVATE MAP_SHARED /) {
73 1     1   5 no strict 'refs';
  1         1  
  1         70  
74 3         5 my $unused_stub = $_; # we must use a copy
75 3     0   22 *$unused_stub = sub { die 'Sys::Mmap required for mmap support' };
  0         0  
76             } # for
77             }; # do
78 1         4925 1;
79             }; # eval
80             } # begin
81              
82              
83             sub GEOIP_STANDARD(){0;}
84             sub GEOIP_MEMORY_CACHE(){1;}
85              
86             #sub GEOIP_CHECK_CACHE(){2;}
87             #sub GEOIP_INDEX_CACHE(){4;}
88             sub GEOIP_MMAP_CACHE(){8;}
89              
90             sub GEOIP_UNKNOWN_SPEED(){0;}
91             sub GEOIP_DIALUP_SPEED(){1;}
92             sub GEOIP_CABLEDSL_SPEED(){2;}
93             sub GEOIP_CORPORATE_SPEED(){3;}
94              
95             @EXPORT = qw( GEOIP_STANDARD GEOIP_MEMORY_CACHE GEOIP_MMAP_CACHE
96             GEOIP_UNKNOWN_SPEED GEOIP_DIALUP_SPEED GEOIP_CABLEDSL_SPEED GEOIP_CORPORATE_SPEED );
97              
98             my @countries = (
99             undef,"AP","EU","AD","AE","AF","AG","AI","AL","AM","CW","AO","AQ","AR",
100             "AS","AT","AU","AW","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ",
101             "BM","BN","BO","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF",
102             "CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CX","CY","CZ",
103             "DE","DJ","DK","DM","DO","DZ","EC","EE","EG","EH","ER","ES","ET","FI",
104             "FJ","FK","FM","FO","FR","SX","GA","GB","GD","GE","GF","GH","GI","GL",
105             "GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR",
106             "HT","HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP",
107             "KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC",
108             "LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","MG","MH","MK",
109             "ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY",
110             "MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM",
111             "PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY",
112             "QA","RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ",
113             "SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG",
114             "TH","TJ","TK","TM","TN","TO","TL","TR","TT","TV","TW","TZ","UA","UG",
115             "UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE",
116             "YT","RS","ZA","ZM","ME","ZW","A1","A2","O1","AX","GG","IM","JE","BL",
117             "MF","BQ");
118             my @code3s = (
119             undef,"AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","CUW","AGO","ATA","ARG",
120             "ASM","AUT","AUS","ABW","AZE","BIH","BRB","BGD","BEL","BFA","BGR","BHR","BDI","BEN",
121             "BMU","BRN","BOL","BRA","BHS","BTN","BVT","BWA","BLR","BLZ","CAN","CCK","COD","CAF",
122             "COG","CHE","CIV","COK","CHL","CMR","CHN","COL","CRI","CUB","CPV","CXR","CYP","CZE",
123             "DEU","DJI","DNK","DMA","DOM","DZA","ECU","EST","EGY","ESH","ERI","ESP","ETH","FIN",
124             "FJI","FLK","FSM","FRO","FRA","SXM","GAB","GBR","GRD","GEO","GUF","GHA","GIB","GRL",
125             "GMB","GIN","GLP","GNQ","GRC","SGS","GTM","GUM","GNB","GUY","HKG","HMD","HND","HRV",
126             "HTI","HUN","IDN","IRL","ISR","IND","IOT","IRQ","IRN","ISL","ITA","JAM","JOR","JPN",
127             "KEN","KGZ","KHM","KIR","COM","KNA","PRK","KOR","KWT","CYM","KAZ","LAO","LBN","LCA",
128             "LIE","LKA","LBR","LSO","LTU","LUX","LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD",
129             "MLI","MMR","MNG","MAC","MNP","MTQ","MRT","MSR","MLT","MUS","MDV","MWI","MEX","MYS",
130             "MOZ","NAM","NCL","NER","NFK","NGA","NIC","NLD","NOR","NPL","NRU","NIU","NZL","OMN",
131             "PAN","PER","PYF","PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT","PLW","PRY",
132             "QAT","REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN","SWE","SGP","SHN","SVN","SJM",
133             "SVK","SLE","SMR","SEN","SOM","SUR","STP","SLV","SYR","SWZ","TCA","TCD","ATF","TGO",
134             "THA","TJK","TKL","TKM","TUN","TON","TLS","TUR","TTO","TUV","TWN","TZA","UKR","UGA",
135             "UMI","USA","URY","UZB","VAT","VCT","VEN","VGB","VIR","VNM","VUT","WLF","WSM","YEM",
136             "MYT","SRB","ZAF","ZMB","MNE","ZWE","A1","A2","O1","ALA","GGY","IMN","JEY","BLM",
137             "MAF","BES");
138             my @names = (
139             undef,"Asia/Pacific Region","Europe","Andorra","United Arab Emirates",
140             "Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia",
141             "Curacao","Angola","Antarctica","Argentina","American Samoa",
142             "Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina",
143             "Barbados","Bangladesh","Belgium","Burkina Faso","Bulgaria",
144             "Bahrain","Burundi","Benin","Bermuda","Brunei Darussalam",
145             "Bolivia","Brazil","Bahamas","Bhutan","Bouvet Island",
146             "Botswana","Belarus","Belize","Canada","Cocos (Keeling) Islands",
147             "Congo, The Democratic Republic of the","Central African Republic","Congo","Switzerland","Cote D'Ivoire",
148             "Cook Islands","Chile","Cameroon","China","Colombia",
149             "Costa Rica","Cuba","Cape Verde","Christmas Island","Cyprus",
150             "Czech Republic","Germany","Djibouti","Denmark","Dominica",
151             "Dominican Republic","Algeria","Ecuador","Estonia","Egypt",
152             "Western Sahara","Eritrea","Spain","Ethiopia","Finland",
153             "Fiji","Falkland Islands (Malvinas)","Micronesia, Federated States of","Faroe Islands","France",
154             "Sint Maarten (Dutch part)","Gabon","United Kingdom","Grenada","Georgia",
155             "French Guiana","Ghana","Gibraltar","Greenland","Gambia",
156             "Guinea","Guadeloupe","Equatorial Guinea","Greece","South Georgia and the South Sandwich Islands",
157             "Guatemala","Guam","Guinea-Bissau","Guyana","Hong Kong",
158             "Heard Island and McDonald Islands","Honduras","Croatia","Haiti","Hungary",
159             "Indonesia","Ireland","Israel","India","British Indian Ocean Territory",
160             "Iraq","Iran, Islamic Republic of","Iceland","Italy","Jamaica",
161             "Jordan","Japan","Kenya","Kyrgyzstan","Cambodia",
162             "Kiribati","Comoros","Saint Kitts and Nevis","Korea, Democratic People's Republic of","Korea, Republic of",
163             "Kuwait","Cayman Islands","Kazakhstan","Lao People's Democratic Republic","Lebanon",
164             "Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho",
165             "Lithuania","Luxembourg","Latvia","Libya","Morocco",
166             "Monaco","Moldova, Republic of","Madagascar","Marshall Islands","Macedonia",
167             "Mali","Myanmar","Mongolia","Macau","Northern Mariana Islands",
168             "Martinique","Mauritania","Montserrat","Malta","Mauritius",
169             "Maldives","Malawi","Mexico","Malaysia","Mozambique",
170             "Namibia","New Caledonia","Niger","Norfolk Island","Nigeria",
171             "Nicaragua","Netherlands","Norway","Nepal","Nauru",
172             "Niue","New Zealand","Oman","Panama","Peru",
173             "French Polynesia","Papua New Guinea","Philippines","Pakistan","Poland",
174             "Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","Palestinian Territory","Portugal",
175             "Palau","Paraguay","Qatar","Reunion","Romania",
176             "Russian Federation","Rwanda","Saudi Arabia","Solomon Islands","Seychelles",
177             "Sudan","Sweden","Singapore","Saint Helena","Slovenia",
178             "Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino","Senegal",
179             "Somalia","Suriname","Sao Tome and Principe","El Salvador","Syrian Arab Republic",
180             "Swaziland","Turks and Caicos Islands","Chad","French Southern Territories","Togo",
181             "Thailand","Tajikistan","Tokelau","Turkmenistan","Tunisia",
182             "Tonga","Timor-Leste","Turkey","Trinidad and Tobago","Tuvalu",
183             "Taiwan","Tanzania, United Republic of","Ukraine","Uganda","United States Minor Outlying Islands",
184             "United States","Uruguay","Uzbekistan","Holy See (Vatican City State)","Saint Vincent and the Grenadines",
185             "Venezuela","Virgin Islands, British","Virgin Islands, U.S.","Vietnam","Vanuatu",
186             "Wallis and Futuna","Samoa","Yemen","Mayotte","Serbia",
187             "South Africa","Zambia","Montenegro","Zimbabwe","Anonymous Proxy",
188             "Satellite Provider","Other","Aland Islands","Guernsey","Isle of Man",
189             "Jersey","Saint Barthelemy","Saint Martin","Bonaire, Saint Eustatius and Saba");
190              
191             # --- unfortunately we do not know the path so we assume the
192             # default path /usr/local/share/GeoIP
193             # if thats not true, you can set $Geo::IP::PurePerl::OPEN_TYPE_PATH
194             #
195             sub open_type {
196 0     0 0   my ( $class, $type, $flags ) = @_;
197 0           my %type_dat_name_mapper = (
198             GEOIP_COUNTRY_EDITION() => 'GeoIP',
199             GEOIP_REGION_EDITION_REV0() => 'GeoIPRegion',
200             GEOIP_REGION_EDITION_REV1() => 'GeoIPRegion',
201             GEOIP_CITY_EDITION_REV0() => 'GeoIPCity',
202             GEOIP_CITY_EDITION_REV1() => 'GeoIPCity',
203             GEOIP_ISP_EDITION() => 'GeoIPISP',
204             GEOIP_ORG_EDITION() => 'GeoIPOrg',
205             GEOIP_PROXY_EDITION() => 'GeoIPProxy',
206             GEOIP_ASNUM_EDITION() => 'GeoIPASNum',
207             GEOIP_NETSPEED_EDITION() => 'GeoIPNetSpeed',
208             GEOIP_DOMAIN_EDITION() => 'GeoIPDomain',
209             );
210              
211             # backward compatibility for 2003 databases.
212 0 0         $type -= 105 if $type >= 106;
213              
214 0           my $name = $type_dat_name_mapper{$type};
215 0 0         die("Invalid database type $type\n") unless $name;
216              
217 0     0     my $mkpath = sub { File::Spec->catfile( File::Spec->rootdir, @_ ) };
  0            
218              
219             my $path =
220             defined $Geo::IP::PurePerl::OPEN_TYPE_PATH
221             ? $Geo::IP::PurePerl::OPEN_TYPE_PATH
222 0 0         : do {
223             $^O eq 'NetWare'
224             ? $mkpath->(qw/ etc GeoIP /)
225 0 0         : do {
226 0 0         $^O eq 'MSWin32'
227             ? $mkpath->(qw/ GeoIP /)
228             : $mkpath->(qw/ usr local share GeoIP /);
229             }
230             };
231              
232 0           my $filename = File::Spec->catfile( $path, $name . '.dat' );
233 0           return $class->open( $filename, $flags );
234             }
235              
236              
237             sub open {
238 0 0 0 0 1   die "Geo::IP::PurePerl::open() requires a path name"
239             unless( @_ > 1 and $_[1] );
240 0           my ($class, $db_file, $flags) = @_;
241 0           my $fh = FileHandle->new;
242 0           my $gi;
243 0 0         CORE::open $fh, $db_file or die "Error opening $db_file";
244 0           binmode($fh);
245 0 0 0       if ( $flags && ( $flags & ( GEOIP_MEMORY_CACHE | GEOIP_MMAP_CACHE ) ) ) {
246 0           my %self;
247              
248 0 0         if ( $flags & GEOIP_MMAP_CACHE ) {
249 0 0         die "Sys::Mmap required for MMAP support"
250             unless defined $Sys::Mmap::VERSION;
251 0 0         mmap( $self{buf} = undef, 0, PROT_READ, MAP_PRIVATE, $fh )
252             or die "mmap: $!";
253             }
254             else {
255 0           local $/ = undef;
256 0           $self{buf} = <$fh>;
257             }
258 0           $self{fh} = $fh;
259 0           $gi = bless \%self, $class;
260             }
261             else {
262 0           $gi = bless { fh => $fh }, $class;
263             }
264 0           $gi->_setup_segments();
265 0           return $gi;
266             }
267              
268             sub new {
269 0     0 1   my ($class, $db_file, $flags) = @_;
270             # this will be less messy once deprecated new( $path, [$flags] )
271             # is no longer supported (that's what open() is for)
272              
273 0           my $def_db_file = '/usr/local/share/GeoIP/GeoIP.dat';
274 0 0         if ($^O eq 'NetWare') {
    0          
275 0           $def_db_file = 'sys:/etc/GeoIP/GeoIP.dat';
276             } elsif ($^O eq 'MSWin32') {
277 0           $def_db_file = 'c:/GeoIP/GeoIP.dat';
278             }
279 0 0         if ( !defined $db_file ) {
    0          
280             # called as new()
281 0           $db_file = $def_db_file;
282             } elsif ( $db_file =~ /^\d+$/ ) {
283             # db_file is GEOIP_MEMORY_CACHE or GEOIP_STANDARD
284             # called as new( $flags )
285 0           $flags = $db_file;
286 0           $db_file = $def_db_file;
287             } # else called as new( $database_filename, [$flags] );
288              
289 0           $class->open( $db_file, $flags );
290             }
291              
292             #this function setups the database segments
293             sub _setup_segments {
294 0     0     my ($gi) = @_;
295 0           my $a = 0;
296 0           my $i = 0;
297 0           my $j = 0;
298 0           my $delim;
299             my $buf;
300 0           $gi->{_charset} = GEOIP_CHARSET_ISO_8859_1;
301 0           $gi->{"databaseType"} = GEOIP_COUNTRY_EDITION;
302 0           $gi->{"record_length"} = STANDARD_RECORD_LENGTH;
303              
304 0           my $filepos = tell($gi->{fh});
305 0           seek($gi->{fh}, -3, 2);
306 0           for ($i = 0; $i < STRUCTURE_INFO_MAX_SIZE; $i++) {
307 0           read($gi->{fh},$delim,3);
308            
309             #find the delim
310 0 0         if ($delim eq (chr(255).chr(255).chr(255))) {
311 0           read($gi->{fh},$a,1);
312            
313             #read the databasetype
314 0           $gi->{"databaseType"} = ord($a);
315              
316             # backward compatibility for 2003 databases.
317 0 0         $gi->{databaseType} -= 105 if $gi->{databaseType} >= 106;
318              
319             #chose the database segment for the database type
320             #if database Type is GEOIP_REGION_EDITION then use database segment GEOIP_STATE_BEGIN
321 0 0 0       if ($gi->{"databaseType"} == GEOIP_REGION_EDITION_REV0) {
    0 0        
    0 0        
      0        
      0        
322 0           $gi->{"databaseSegments"} = GEOIP_STATE_BEGIN_REV0;
323             } elsif ($gi->{"databaseType"} == GEOIP_REGION_EDITION_REV1) {
324 0           $gi->{"databaseSegments"} = GEOIP_STATE_BEGIN_REV1;
325             }
326              
327             #if database Type is GEOIP_CITY_EDITION, GEOIP_ISP_EDITION or GEOIP_ORG_EDITION then
328             #read in the database segment
329             elsif (($gi->{"databaseType"} == GEOIP_CITY_EDITION_REV0) ||
330             ($gi->{"databaseType"} == GEOIP_CITY_EDITION_REV1) ||
331             ($gi->{"databaseType"} == GEOIP_ORG_EDITION) ||
332             ($gi->{"databaseType"} == GEOIP_ASNUM_EDITION) ||
333             ($gi->{"databaseType"} == GEOIP_DOMAIN_EDITION) ||
334             ($gi->{"databaseType"} == GEOIP_ISP_EDITION)) {
335 0           $gi->{"databaseSegments"} = 0;
336              
337             #read in the database segment for the database type
338 0           read($gi->{fh},$buf,SEGMENT_RECORD_LENGTH);
339 0           for ($j = 0;$j < SEGMENT_RECORD_LENGTH;$j++) {
340 0           $gi->{"databaseSegments"} += (ord(substr($buf,$j,1)) << ($j * 8));
341             }
342              
343             #record length is four for ISP databases and ORG databases
344             #record length is three for country databases, region database and city databases
345 0 0 0       if ($gi->{"databaseType"} == GEOIP_ORG_EDITION ||
      0        
346             $gi->{"databaseType"} == GEOIP_ISP_EDITION ||
347             $gi->{"databaseType"} == GEOIP_DOMAIN_EDITION) {
348 0           $gi->{"record_length"} = ORG_RECORD_LENGTH;
349             }
350             }
351 0           last;
352             } else {
353 0           seek($gi->{fh}, -4 , 1);
354             }
355             }
356             #if database Type is GEOIP_COUNTY_EDITION then use database segment GEOIP_COUNTRY_BEGIN
357 0 0 0       if ($gi->{"databaseType"} == GEOIP_COUNTRY_EDITION ||
358             $gi->{"databaseType"} == GEOIP_NETSPEED_EDITION) {
359 0           $gi->{"databaseSegments"} = GEOIP_COUNTRY_BEGIN;
360             }
361 0           seek($gi->{fh},$filepos,0);
362 0           return $gi;
363             }
364              
365             sub _seek_country {
366 0     0     my ($gi, $ipnum) = @_;
367              
368 0           my $fh = $gi->{fh};
369 0           my $offset = 0;
370              
371 0           my ($x0, $x1);
372              
373 0           my $reclen = $gi->{"record_length"};
374              
375 0           for (my $depth = 31; $depth >= 0; $depth--) {
376 0 0         unless ( exists $gi->{buf} ) {
377 0           seek $fh, $offset * 2 * $reclen, 0;
378 0           read $fh, $x0, $reclen;
379 0           read $fh, $x1, $reclen;
380             } else {
381            
382 0           $x0 = substr($gi->{buf}, $offset * 2 * $reclen, $reclen);
383 0           $x1 = substr($gi->{buf}, $offset * 2 * $reclen + $reclen, $reclen);
384             }
385              
386 0           $x0 = unpack("V1", $x0."\0");
387 0           $x1 = unpack("V1", $x1."\0");
388              
389 0 0         if ($ipnum & (1 << $depth)) {
390 0 0         if ($x1 >= $gi->{"databaseSegments"}) {
391 0           $gi->{last_netmask} = 32 - $depth;
392 0           return $x1;
393             }
394 0           $offset = $x1;
395             } else {
396 0 0         if ($x0 >= $gi->{"databaseSegments"}) {
397 0           $gi->{last_netmask} = 32 - $depth;
398 0           return $x0;
399             }
400 0           $offset = $x0;
401             }
402             }
403              
404 0           print STDERR "Error Traversing Database for ipnum = $ipnum - Perhaps database is corrupt?";
405             }
406             sub charset {
407 0     0 1   return $_[0]->{_charset};
408             }
409              
410             sub set_charset{
411 0     0 1   my ( $gi, $charset ) = @_;
412 0           my $old_charset = $gi->{_charset};
413 0           $gi->{_charset} = $charset;
414              
415 0           return $old_charset;
416             }
417              
418             #this function returns the country code of ip address
419             sub country_code_by_addr {
420 0     0 1   my ($gi, $ip_address) = @_;
421 0 0         return unless $ip_address =~ m!^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$!;
422 0           return $countries[$gi->id_by_addr($ip_address)];
423             }
424              
425             #this function returns the country code3 of ip address
426             sub country_code3_by_addr {
427 0     0 1   my ($gi, $ip_address) = @_;
428 0 0         return unless $ip_address =~ m!^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$!;
429 0           return $code3s[$gi->id_by_addr($ip_address)];
430             }
431              
432             #this function returns the name of ip address
433             sub country_name_by_addr {
434 0     0 1   my ($gi, $ip_address) = @_;
435 0 0         return unless $ip_address =~ m!^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$!;
436 0           return $names[$gi->id_by_addr($ip_address)];
437             }
438              
439             sub id_by_addr {
440 0     0 0   my ($gi, $ip_address) = @_;
441 0 0         return unless $ip_address =~ m!^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$!;
442 0           return $gi->_seek_country(addr_to_num($ip_address)) - GEOIP_COUNTRY_BEGIN;
443             }
444              
445             #this function returns the country code of domain name
446             sub country_code_by_name {
447 0     0 1   my ($gi, $host) = @_;
448 0           my $country_id = $gi->id_by_name($host);
449 0           return $countries[$country_id];
450             }
451              
452             #this function returns the country code3 of domain name
453             sub country_code3_by_name {
454 0     0 1   my ($gi, $host) = @_;
455 0           my $country_id = $gi->id_by_name($host);
456 0           return $code3s[$country_id];
457             }
458              
459             #this function returns the country name of domain name
460             sub country_name_by_name {
461 0     0 1   my ($gi, $host) = @_;
462 0           my $country_id = $gi->id_by_name($host);
463 0           return $names[$country_id];
464             }
465              
466             sub id_by_name {
467 0     0 0   my ($gi, $host) = @_;
468 0           my $ip_address;
469 0 0         if ($host =~ m!^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$!) {
470 0           $ip_address = $host;
471             } else {
472 0           $ip_address = join('.',unpack('C4',(gethostbyname($host))[4]));
473             }
474 0 0         return unless $ip_address;
475 0           return $gi->_seek_country(addr_to_num($ip_address)) - GEOIP_COUNTRY_BEGIN;
476             }
477              
478             #this function returns the city record as a array
479             sub get_city_record {
480 0     0 1   my ($gi, $host) = @_;
481 0           my $ip_address = $gi->get_ip_address($host);
482 0 0         return unless $ip_address;
483 0           my $record_buf;
484             my $record_buf_pos;
485 0           my $char;
486 0           my $metroarea_combo;
487 0           my $record_country_code = "";
488 0           my $record_country_code3 = "";
489 0           my $record_country_name = "";
490 0           my $record_region = "";
491 0           my $record_city = "";
492 0           my $record_postal_code = "";
493 0           my $record_latitude = "";
494 0           my $record_longitude = "";
495 0           my $record_metro_code = "";
496 0           my $record_area_code = "";
497 0           my $str_length = 0;
498 0           my $i;
499             my $j;
500              
501             #lookup the city
502 0           my $seek_country = $gi->_seek_country(addr_to_num($ip_address));
503 0 0         if ($seek_country == $gi->{"databaseSegments"}) {
504 0           return;
505             }
506             #set the record pointer to location of the city record
507 0           my $record_pointer = $seek_country + (2 * $gi->{"record_length"} - 1) * $gi->{"databaseSegments"};
508              
509 0 0         unless ( exists $gi->{buf} ) {
510 0           seek( $gi->{"fh"}, $record_pointer, 0 );
511 0           read( $gi->{"fh"}, $record_buf, FULL_RECORD_LENGTH );
512 0           $record_buf_pos = 0;
513             }
514             else {
515 0           $record_buf = substr($gi->{buf}, $record_pointer, FULL_RECORD_LENGTH);
516 0           $record_buf_pos = 0;
517             }
518              
519             #get the country
520 0           $char = ord(substr($record_buf,$record_buf_pos,1));
521 0           $record_country_code = $countries[$char];#get the country code
522 0           $record_country_code3 = $code3s[$char];#get the country code with 3 letters
523 0           $record_country_name = $names[$char];#get the country name
524 0           $record_buf_pos++;
525              
526             #get the region
527 0           $char = ord(substr($record_buf,$record_buf_pos+$str_length,1));
528 0           while ($char != 0) {
529 0           $str_length++;#get the length of string
530 0           $char = ord(substr($record_buf,$record_buf_pos+$str_length,1));
531             }
532 0 0         if ($str_length > 0) {
533 0           $record_region = substr($record_buf,$record_buf_pos,$str_length);
534             }
535 0           $record_buf_pos += $str_length + 1;
536 0           $str_length = 0;
537              
538             #get the city
539 0           $char = ord(substr($record_buf,$record_buf_pos+$str_length,1));
540 0           while ($char != 0) {
541 0           $str_length++;#get the length of string
542 0           $char = ord(substr($record_buf,$record_buf_pos+$str_length,1));
543             }
544 0 0         if ($str_length > 0) {
545 0           $record_city = substr($record_buf,$record_buf_pos,$str_length);
546             }
547 0           $record_buf_pos += $str_length + 1;
548 0           $str_length = 0;
549              
550             #get the postal code
551 0           $char = ord(substr($record_buf,$record_buf_pos+$str_length,1));
552 0           while ($char != 0) {
553 0           $str_length++;#get the length of string
554 0           $char = ord(substr($record_buf,$record_buf_pos+$str_length,1));
555             }
556 0 0         if ($str_length > 0) {
557 0           $record_postal_code = substr($record_buf,$record_buf_pos,$str_length);
558             }
559 0           $record_buf_pos += $str_length + 1;
560 0           $str_length = 0;
561 0           my $latitude = 0;
562 0           my $longitude = 0;
563              
564             #get the latitude
565 0           for ($j = 0;$j < 3; ++$j) {
566 0           $char = ord(substr($record_buf,$record_buf_pos++,1));
567 0           $latitude += ($char << ($j * 8));
568             }
569 0           $record_latitude = ($latitude/10000) - 180;
570              
571             #get the longitude
572 0           for ($j = 0;$j < 3; ++$j) {
573 0           $char = ord(substr($record_buf,$record_buf_pos++,1));
574 0           $longitude += ($char << ($j * 8));
575             }
576 0           $record_longitude = ($longitude/10000) - 180;
577              
578             #get the metro code and the area code
579 0 0         if (GEOIP_CITY_EDITION_REV1 == $gi->{"databaseType"}) {
580 0           $metroarea_combo = 0;
581 0 0         if ($record_country_code eq "US") {
582             #if the country is US then read the metro area combo
583 0           for ($j = 0;$j < 3;++$j) {
584 0           $char = ord(substr($record_buf,$record_buf_pos++,1));
585 0           $metroarea_combo += ($char << ($j * 8));
586             }
587             #split the metro area combo into the metro code and the area code
588 0           $record_metro_code = int($metroarea_combo/1000);
589 0           $record_area_code = $metroarea_combo%1000;
590             }
591             }
592            
593             # the pureperl API must convert the string by themself to UTF8
594             # using Encode for perl >= 5.008 otherwise use it's own iso-8859-1 to utf8 converter
595 0 0         $record_city = decode( 'iso-8859-1' => $record_city )
596             if $gi->charset == GEOIP_CHARSET_UTF8;
597              
598 0           return ($record_country_code,$record_country_code3,$record_country_name,$record_region,$record_city,$record_postal_code,$record_latitude,$record_longitude,$record_metro_code,$record_area_code);
599             }
600              
601             #this function returns the city record as a hash ref
602             sub get_city_record_as_hash {
603 0     0 1   my ($gi, $host) = @_;
604 0           my %h;
605 0           @h{qw/ country_code country_code3 country_name
606             region city postal_code
607             latitude longitude metro_code
608             area_code /}
609             = $gi->get_city_record($host);
610 0           $h{dma_code} = $h{metro_code}; # alias for depreciated dma_code
611 0           return \%h;
612             }
613              
614             #this function returns isp or org of the domain name
615             sub org_by_name {
616 0     0 1   my ($gi, $host) = @_;
617 0           my $ip_address = $gi->get_ip_address($host);
618 0           my $seek_org = $gi->_seek_country(addr_to_num($ip_address));
619 0           my $char;
620             my $org_buf;
621 0           my $org_buf_length = 0;
622 0           my $record_pointer;
623              
624 0 0         if ($seek_org == $gi->{"databaseSegments"}) {
625 0           return undef;
626             }
627              
628 0           $record_pointer = $seek_org + (2 * $gi->{"record_length"} - 1) * $gi->{"databaseSegments"};
629              
630 0 0         unless ( exists $gi->{buf} ) {
631 0           seek( $gi->{"fh"}, $record_pointer, 0 );
632 0           read( $gi->{"fh"}, $org_buf, MAX_ORG_RECORD_LENGTH );
633             }
634             else {
635 0           $org_buf = substr($gi->{buf}, $record_pointer, MAX_ORG_RECORD_LENGTH );
636             }
637              
638 0           $char = ord(substr($org_buf,0,1));
639 0           while ($char != 0) {
640 0           $org_buf_length++;
641 0           $char = ord(substr($org_buf,$org_buf_length,1));
642             }
643              
644 0           $org_buf = substr($org_buf, 0, $org_buf_length);
645 0           return $org_buf;
646             }
647              
648             #this function returns isp or org of the domain name
649             *isp_by_name = \*org_by_name;
650              
651             *org_by_addr = \*org_by_name;
652             *isp_by_addr = \*org_by_name;
653              
654             #this function returns the region
655             sub region_by_name {
656 0     0 0   my ($gi, $host) = @_;
657 0           my $ip_address = $gi->get_ip_address($host);
658 0 0         return unless $ip_address;
659 0 0         if ($gi->{"databaseType"} == GEOIP_REGION_EDITION_REV0) {
    0          
660 0           my $seek_region = $gi->_seek_country(addr_to_num($ip_address)) - GEOIP_STATE_BEGIN_REV0;
661 0 0         if ($seek_region >= 1000) {
662 0           return ("US",chr(($seek_region - 1000)/26 + 65) . chr(($seek_region - 1000)%26 + 65));
663             } else {
664 0           return ($countries[$seek_region],"");
665             }
666             } elsif ($gi->{"databaseType"} == GEOIP_REGION_EDITION_REV1) {
667 0           my $seek_region = $gi->_seek_country(addr_to_num($ip_address)) - GEOIP_STATE_BEGIN_REV1;
668 0 0         if ($seek_region < US_OFFSET) {
    0          
    0          
669 0           return ("","");
670             } elsif ($seek_region < CANADA_OFFSET) {
671             # return a us state
672 0           return ("US",chr(($seek_region - US_OFFSET)/26 + 65) . chr(($seek_region - US_OFFSET)%26 + 65));
673             } elsif ($seek_region < WORLD_OFFSET) {
674             # return a canada province
675 0           return ("CA",chr(($seek_region - CANADA_OFFSET)/26 + 65) . chr(($seek_region - CANADA_OFFSET)%26 + 65));
676             } else {
677             # return a country of the world
678 0           my $c = $countries[($seek_region - WORLD_OFFSET) / FIPS_RANGE];
679 0           my $a2 = ($seek_region - WORLD_OFFSET) % FIPS_RANGE;
680 0           my $r = chr(($a2 / 100)+48) . chr((($a2 / 10) % 10)+48) . chr(($a2 % 10)+48);
681 0           return ($c,$r);
682             }
683             }
684             }
685              
686             sub get_ip_address {
687 0     0 0   my ($gi, $host) = @_;
688 0           my $ip_address;
689             #check if host is ip address
690 0 0         if ($host =~ m!^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$!) {
691             #host is ip address
692 0           $ip_address = $host;
693             } else {
694             #host is domain name do a dns lookup
695 0           $ip_address = join('.',unpack('C4',(gethostbyname($host))[4]));
696             }
697 0           return $ip_address;
698             }
699              
700 0     0 0   sub addr_to_num { unpack( N => pack( C4 => split( /\./, $_[0] ) ) ) }
701 0     0 0   sub num_to_addr { join q{.}, unpack( C4 => pack( N => $_[0] ) ) }
702              
703             sub database_info {
704 0     0 1   my $gi = shift;
705 0           my $i = 0;
706 0           my $buf;
707             my $retval;
708 0           my $hasStructureInfo;
709 0           seek($gi->{fh},-3,2);
710 0           for (my $i = 0;$i < STRUCTURE_INFO_MAX_SIZE;$i++) {
711 0           read($gi->{fh},$buf,3);
712 0 0         if ($buf eq (chr(255) . chr(255) . chr(255))) {
713 0           $hasStructureInfo = 1;
714 0           last;
715             }
716 0           seek($gi->{fh},-4,1);
717             }
718 0 0         if ($hasStructureInfo == 1) {
719 0           seek($gi->{fh},-6,1);
720             } else {
721             # no structure info, must be pre Sep 2002 database, go back to
722 0           seek($gi->{fh},-3,2);
723             }
724 0           for (my $i = 0;$i < DATABASE_INFO_MAX_SIZE;$i++){
725 0           read($gi->{fh},$buf,3);
726 0 0         if ($buf eq (chr(0). chr(0). chr(0))){
727 0           read($gi->{fh},$retval,$i);
728 0           return $retval;
729             }
730 0           seek($gi->{fh},-4,1);
731             }
732 0           return "";
733             }
734              
735             sub range_by_ip {
736 0     0 1   my $gi = shift;
737 0           my $ipnum = addr_to_num( shift );
738 0           my $c = $gi->_seek_country( $ipnum );
739 0           my $nm = $gi->last_netmask;
740 0           my $m = 0xffffffff << 32 - $nm;
741 0           my $left_seek_num = $ipnum & $m;
742 0           my $right_seek_num = $left_seek_num + ( 0xffffffff & ~$m );
743              
744 0   0       while ( $left_seek_num != 0
745             and $c == $gi->_seek_country( $left_seek_num - 1) ) {
746 0           my $lm = 0xffffffff << 32 - $gi->last_netmask;
747 0           $left_seek_num = ( $left_seek_num - 1 ) & $lm;
748             }
749 0   0       while ( $right_seek_num != 0xffffffff
750             and $c == $gi->_seek_country( $right_seek_num + 1 ) ) {
751 0           my $rm = 0xffffffff << 32 - $gi->last_netmask;
752 0           $right_seek_num = ( $right_seek_num + 1 ) & $rm;
753 0           $right_seek_num += ( 0xffffffff & ~$rm );
754             }
755 0           return ( num_to_addr($left_seek_num), num_to_addr($right_seek_num) );
756             }
757              
758 0     0 1   sub netmask { $_[0]->{last_netmask} = $_[1] }
759              
760             sub last_netmask {
761 0     0 1   return $_[0]->{last_netmask};
762             }
763              
764             sub DESTROY {
765 0     0     my $gi = shift;
766              
767 0 0 0       if ( exists $gi->{buf} && $gi->{flags} && ( $gi->{flags} & GEOIP_MMAP_CACHE ) ) {
      0        
768 0 0         munmap( $gi->{buf} ) or die "munmap: $!";
769 0           delete $gi->{buf};
770             }
771             }
772             1;
773             __END__