geoip 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/geoip.gemspec ADDED
@@ -0,0 +1,65 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{geoip}
8
+ s.version = "1.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Clifford Heath", "Roland Moriz"]
12
+ s.date = %q{2011-05-05}
13
+ s.default_executable = %q{geoip}
14
+ s.description = %q{GeoIP searches a GeoIP database for a given host or IP address, and
15
+ returns information about the country where the IP address is allocated,
16
+ and the city, ISP and other information, if you have that database version.}
17
+ s.email = ["clifford.heath@gmail.com", "rmoriz@gmail.com"]
18
+ s.executables = ["geoip"]
19
+ s.extra_rdoc_files = [
20
+ "README.rdoc"
21
+ ]
22
+ s.files = [
23
+ "History.rdoc",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "bin/geoip",
27
+ "config/website.yml",
28
+ "data/geoip/country_code.yml",
29
+ "data/geoip/country_code3.yml",
30
+ "data/geoip/country_continent.yml",
31
+ "data/geoip/country_name.yml",
32
+ "data/geoip/time_zone.yml",
33
+ "geoip.gemspec",
34
+ "lib/geoip.rb",
35
+ "script/destroy",
36
+ "script/generate",
37
+ "script/txt2html",
38
+ "test/test_geoip.rb",
39
+ "test/test_helper.rb",
40
+ "website/index.txt",
41
+ "website/javascripts/rounded_corners_lite.inc.js",
42
+ "website/stylesheets/screen.css",
43
+ "website/template.rhtml"
44
+ ]
45
+ s.homepage = %q{http://github.com/cjheath/geoip}
46
+ s.licenses = ["MIT"]
47
+ s.require_paths = ["lib"]
48
+ s.rubygems_version = %q{1.3.7}
49
+ s.summary = %q{GeoIP searches a GeoIP database for a given host or IP address, and returns information about the country where the IP address is allocated, and the city, ISP and other information, if you have that database version.}
50
+ s.test_files = [
51
+ "test/test_geoip.rb",
52
+ "test/test_helper.rb"
53
+ ]
54
+
55
+ if s.respond_to? :specification_version then
56
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
57
+ s.specification_version = 3
58
+
59
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
60
+ else
61
+ end
62
+ else
63
+ end
64
+ end
65
+
data/lib/geoip.rb CHANGED
@@ -1,9 +1,9 @@
1
- $:.unshift File.dirname(__FILE__)
2
1
  #
3
2
  # Native Ruby reader for the GeoIP database
4
3
  # Lookup the country where IP address is allocated
5
4
  #
6
- #= COPYRIGHT
5
+ # = COPYRIGHT
6
+ #
7
7
  # This version Copyright (C) 2005 Clifford Heath
8
8
  # Derived from the C version, Copyright (C) 2003 MaxMind LLC
9
9
  #
@@ -21,17 +21,17 @@ $:.unshift File.dirname(__FILE__)
21
21
  # License along with this library; if not, write to the Free Software
22
22
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
23
  #
24
- #= SYNOPSIS
24
+ # = SYNOPSIS
25
25
  #
26
26
  # require 'geoip'
27
27
  # p GeoIP.new('/usr/share/GeoIP/GeoIP.dat').country("www.netscape.sk")
28
28
  #
29
- #= DESCRIPTION
29
+ # = DESCRIPTION
30
30
  #
31
31
  # GeoIP searches a GeoIP database for a given host or IP address, and
32
32
  # returns information about the country where the IP address is allocated.
33
33
  #
34
- #= PREREQUISITES
34
+ # = PREREQUISITES
35
35
  #
36
36
  # You need at least the free GeoIP.dat, for which the last known download
37
37
  # location is <http://www.maxmind.com/download/geoip/database/GeoIP.dat.gz>
@@ -40,8 +40,8 @@ $:.unshift File.dirname(__FILE__)
40
40
  # information, but this information is not returned by this implementation.
41
41
  # See www.maxmind.com for more information.
42
42
  #
43
- #=end
44
- require 'thread' # Needed for Mutex
43
+
44
+ require 'thread' # Needed for Mutex
45
45
  require 'socket'
46
46
  begin
47
47
  require 'io/extra' # for IO.pread
@@ -49,1039 +49,513 @@ rescue LoadError
49
49
  # oh well, hope they're not forking after initializing
50
50
  end
51
51
 
52
+ require 'yaml'
53
+
52
54
  class GeoIP
53
- # The GeoIP GEM version number
54
- VERSION = "1.0.0"
55
-
56
- private
57
- # Ordered list of the ISO3166 2-character country codes, ordered by GeoIP ID
58
- CountryCode = [
59
- "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN",
60
- "AO","AQ","AR","AS","AT","AU","AW","AZ","BA","BB",
61
- "BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO",
62
- "BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD",
63
- "CF","CG","CH","CI","CK","CL","CM","CN","CO","CR",
64
- "CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO",
65
- "DZ","EC","EE","EG","EH","ER","ES","ET","FI","FJ",
66
- "FK","FM","FO","FR","FX","GA","GB","GD","GE","GF",
67
- "GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT",
68
- "GU","GW","GY","HK","HM","HN","HR","HT","HU","ID",
69
- "IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO",
70
- "JP","KE","KG","KH","KI","KM","KN","KP","KR","KW",
71
- "KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT",
72
- "LU","LV","LY","MA","MC","MD","MG","MH","MK","ML",
73
- "MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV",
74
- "MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI",
75
- "NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF",
76
- "PG","PH","PK","PL","PM","PN","PR","PS","PT","PW",
77
- "PY","QA","RE","RO","RU","RW","SA","SB","SC","SD",
78
- "SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO",
79
- "SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH",
80
- "TJ","TK","TM","TN","TO","TL","TR","TT","TV","TW",
81
- "TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE",
82
- "VG","VI","VN","VU","WF","WS","YE","YT","RS","ZA",
83
- "ZM","ME","ZW","A1","A2","O1","AX","GG","IM","JE",
84
- "BL","MF"
85
- ]
86
-
87
- # Ordered list of the ISO3166 3-character country codes, ordered by GeoIP ID
88
- CountryCode3 = [
89
- "--","AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","ANT",
90
- "AGO","AQ","ARG","ASM","AUT","AUS","ABW","AZE","BIH","BRB",
91
- "BGD","BEL","BFA","BGR","BHR","BDI","BEN","BMU","BRN","BOL",
92
- "BRA","BHS","BTN","BV","BWA","BLR","BLZ","CAN","CC","COD",
93
- "CAF","COG","CHE","CIV","COK","CHL","CMR","CHN","COL","CRI",
94
- "CUB","CPV","CX","CYP","CZE","DEU","DJI","DNK","DMA","DOM",
95
- "DZA","ECU","EST","EGY","ESH","ERI","ESP","ETH","FIN","FJI",
96
- "FLK","FSM","FRO","FRA","FX","GAB","GBR","GRD","GEO","GUF",
97
- "GHA","GIB","GRL","GMB","GIN","GLP","GNQ","GRC","GS","GTM",
98
- "GUM","GNB","GUY","HKG","HM","HND","HRV","HTI","HUN","IDN",
99
- "IRL","ISR","IND","IO","IRQ","IRN","ISL","ITA","JAM","JOR",
100
- "JPN","KEN","KGZ","KHM","KIR","COM","KNA","PRK","KOR","KWT",
101
- "CYM","KAZ","LAO","LBN","LCA","LIE","LKA","LBR","LSO","LTU",
102
- "LUX","LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD","MLI",
103
- "MMR","MNG","MAC","MNP","MTQ","MRT","MSR","MLT","MUS","MDV",
104
- "MWI","MEX","MYS","MOZ","NAM","NCL","NER","NFK","NGA","NIC",
105
- "NLD","NOR","NPL","NRU","NIU","NZL","OMN","PAN","PER","PYF",
106
- "PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT","PLW",
107
- "PRY","QAT","REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN",
108
- "SWE","SGP","SHN","SVN","SJM","SVK","SLE","SMR","SEN","SOM",
109
- "SUR","STP","SLV","SYR","SWZ","TCA","TCD","TF","TGO","THA",
110
- "TJK","TKL","TKM","TUN","TON","TLS","TUR","TTO","TUV","TWN",
111
- "TZA","UKR","UGA","UM","USA","URY","UZB","VAT","VCT","VEN",
112
- "VGB","VIR","VNM","VUT","WLF","WSM","YEM","YT","SRB","ZAF",
113
- "ZMB","MNE","ZWE","A1","A2","O1","ALA","GGY","IMN","JEY",
114
- "BLM","MAF"
115
- ]
116
-
117
- # Ordered list of the English names of the countries, ordered by GeoIP ID
118
- CountryName = [
119
- "N/A",
120
- "Asia/Pacific Region",
121
- "Europe",
122
- "Andorra",
123
- "United Arab Emirates",
124
- "Afghanistan",
125
- "Antigua and Barbuda",
126
- "Anguilla",
127
- "Albania",
128
- "Armenia",
129
- "Netherlands Antilles",
130
- "Angola",
131
- "Antarctica",
132
- "Argentina",
133
- "American Samoa",
134
- "Austria",
135
- "Australia",
136
- "Aruba",
137
- "Azerbaijan",
138
- "Bosnia and Herzegovina",
139
- "Barbados",
140
- "Bangladesh",
141
- "Belgium",
142
- "Burkina Faso",
143
- "Bulgaria",
144
- "Bahrain",
145
- "Burundi",
146
- "Benin",
147
- "Bermuda",
148
- "Brunei Darussalam",
149
- "Bolivia",
150
- "Brazil",
151
- "Bahamas",
152
- "Bhutan",
153
- "Bouvet Island",
154
- "Botswana",
155
- "Belarus",
156
- "Belize",
157
- "Canada",
158
- "Cocos (Keeling) Islands",
159
- "Congo, the Democratic Republic of the",
160
- "Central African Republic",
161
- "Congo",
162
- "Switzerland",
163
- "Cote D'Ivoire",
164
- "Cook Islands",
165
- "Chile",
166
- "Cameroon",
167
- "China",
168
- "Colombia",
169
- "Costa Rica",
170
- "Cuba",
171
- "Cape Verde",
172
- "Christmas Island",
173
- "Cyprus",
174
- "Czech Republic",
175
- "Germany",
176
- "Djibouti",
177
- "Denmark",
178
- "Dominica",
179
- "Dominican Republic",
180
- "Algeria",
181
- "Ecuador",
182
- "Estonia",
183
- "Egypt",
184
- "Western Sahara",
185
- "Eritrea",
186
- "Spain",
187
- "Ethiopia",
188
- "Finland",
189
- "Fiji",
190
- "Falkland Islands (Malvinas)",
191
- "Micronesia, Federated States of",
192
- "Faroe Islands",
193
- "France",
194
- "France, Metropolitan",
195
- "Gabon",
196
- "United Kingdom",
197
- "Grenada",
198
- "Georgia",
199
- "French Guiana",
200
- "Ghana",
201
- "Gibraltar",
202
- "Greenland",
203
- "Gambia",
204
- "Guinea",
205
- "Guadeloupe",
206
- "Equatorial Guinea",
207
- "Greece",
208
- "South Georgia and the South Sandwich Islands",
209
- "Guatemala",
210
- "Guam",
211
- "Guinea-Bissau",
212
- "Guyana",
213
- "Hong Kong",
214
- "Heard Island and McDonald Islands",
215
- "Honduras",
216
- "Croatia",
217
- "Haiti",
218
- "Hungary",
219
- "Indonesia",
220
- "Ireland",
221
- "Israel",
222
- "India",
223
- "British Indian Ocean Territory",
224
- "Iraq",
225
- "Iran, Islamic Republic of",
226
- "Iceland",
227
- "Italy",
228
- "Jamaica",
229
- "Jordan",
230
- "Japan",
231
- "Kenya",
232
- "Kyrgyzstan",
233
- "Cambodia",
234
- "Kiribati",
235
- "Comoros",
236
- "Saint Kitts and Nevis",
237
- "Korea, Democratic People's Republic of",
238
- "Korea, Republic of",
239
- "Kuwait",
240
- "Cayman Islands",
241
- "Kazakhstan",
242
- "Lao People's Democratic Republic",
243
- "Lebanon",
244
- "Saint Lucia",
245
- "Liechtenstein",
246
- "Sri Lanka",
247
- "Liberia",
248
- "Lesotho",
249
- "Lithuania",
250
- "Luxembourg",
251
- "Latvia",
252
- "Libyan Arab Jamahiriya",
253
- "Morocco",
254
- "Monaco",
255
- "Moldova, Republic of",
256
- "Madagascar",
257
- "Marshall Islands",
258
- "Macedonia, the Former Yugoslav Republic of",
259
- "Mali",
260
- "Myanmar",
261
- "Mongolia",
262
- "Macau",
263
- "Northern Mariana Islands",
264
- "Martinique",
265
- "Mauritania",
266
- "Montserrat",
267
- "Malta",
268
- "Mauritius",
269
- "Maldives",
270
- "Malawi",
271
- "Mexico",
272
- "Malaysia",
273
- "Mozambique",
274
- "Namibia",
275
- "New Caledonia",
276
- "Niger",
277
- "Norfolk Island",
278
- "Nigeria",
279
- "Nicaragua",
280
- "Netherlands",
281
- "Norway",
282
- "Nepal",
283
- "Nauru",
284
- "Niue",
285
- "New Zealand",
286
- "Oman",
287
- "Panama",
288
- "Peru",
289
- "French Polynesia",
290
- "Papua New Guinea",
291
- "Philippines",
292
- "Pakistan",
293
- "Poland",
294
- "Saint Pierre and Miquelon",
295
- "Pitcairn",
296
- "Puerto Rico",
297
- "Palestinian Territory, Occupied",
298
- "Portugal",
299
- "Palau",
300
- "Paraguay",
301
- "Qatar",
302
- "Reunion",
303
- "Romania",
304
- "Russian Federation",
305
- "Rwanda",
306
- "Saudi Arabia",
307
- "Solomon Islands",
308
- "Seychelles",
309
- "Sudan",
310
- "Sweden",
311
- "Singapore",
312
- "Saint Helena",
313
- "Slovenia",
314
- "Svalbard and Jan Mayen",
315
- "Slovakia",
316
- "Sierra Leone",
317
- "San Marino",
318
- "Senegal",
319
- "Somalia",
320
- "Suriname",
321
- "Sao Tome and Principe",
322
- "El Salvador",
323
- "Syrian Arab Republic",
324
- "Swaziland",
325
- "Turks and Caicos Islands",
326
- "Chad",
327
- "French Southern Territories",
328
- "Togo",
329
- "Thailand",
330
- "Tajikistan",
331
- "Tokelau",
332
- "Turkmenistan",
333
- "Tunisia",
334
- "Tonga",
335
- "Timor-Leste",
336
- "Turkey",
337
- "Trinidad and Tobago",
338
- "Tuvalu",
339
- "Taiwan, Province of China",
340
- "Tanzania, United Republic of",
341
- "Ukraine",
342
- "Uganda",
343
- "United States Minor Outlying Islands",
344
- "United States",
345
- "Uruguay",
346
- "Uzbekistan",
347
- "Holy See (Vatican City State)",
348
- "Saint Vincent and the Grenadines",
349
- "Venezuela",
350
- "Virgin Islands, British",
351
- "Virgin Islands, U.S.",
352
- "Viet Nam",
353
- "Vanuatu",
354
- "Wallis and Futuna",
355
- "Samoa",
356
- "Yemen",
357
- "Mayotte",
358
- "Serbia",
359
- "South Africa",
360
- "Zambia",
361
- "Montenegro",
362
- "Zimbabwe",
363
- "Anonymous Proxy",
364
- "Satellite Provider",
365
- "Other",
366
- "Aland Islands",
367
- "Guernsey",
368
- "Isle of Man",
369
- "Jersey",
370
- "Saint Barthelemy",
371
- "Saint Martin"
372
- ]
373
-
374
- # Ordered list of the ISO3166 2-character continent code of the countries, ordered by GeoIP ID
375
- CountryContinent = [
376
- "--","AS","EU","EU","AS","AS","SA","SA","EU","AS","SA",
377
- "AF","AN","SA","OC","EU","OC","SA","AS","EU","SA",
378
- "AS","EU","AF","EU","AS","AF","AF","SA","AS","SA",
379
- "SA","SA","AS","AF","AF","EU","SA","NA","AS","AF",
380
- "AF","AF","EU","AF","OC","SA","AF","AS","SA","SA",
381
- "SA","AF","AS","AS","EU","EU","AF","EU","SA","SA",
382
- "AF","SA","EU","AF","AF","AF","EU","AF","EU","OC",
383
- "SA","OC","EU","EU","EU","AF","EU","SA","AS","SA",
384
- "AF","EU","SA","AF","AF","SA","AF","EU","SA","SA",
385
- "OC","AF","SA","AS","AF","SA","EU","SA","EU","AS",
386
- "EU","AS","AS","AS","AS","AS","EU","EU","SA","AS",
387
- "AS","AF","AS","AS","OC","AF","SA","AS","AS","AS",
388
- "SA","AS","AS","AS","SA","EU","AS","AF","AF","EU",
389
- "EU","EU","AF","AF","EU","EU","AF","OC","EU","AF",
390
- "AS","AS","AS","OC","SA","AF","SA","EU","AF","AS",
391
- "AF","NA","AS","AF","AF","OC","AF","OC","AF","SA",
392
- "EU","EU","AS","OC","OC","OC","AS","SA","SA","OC",
393
- "OC","AS","AS","EU","SA","OC","SA","AS","EU","OC",
394
- "SA","AS","AF","EU","AS","AF","AS","OC","AF","AF",
395
- "EU","AS","AF","EU","EU","EU","AF","EU","AF","AF",
396
- "SA","AF","SA","AS","AF","SA","AF","AF","AF","AS",
397
- "AS","OC","AS","AF","OC","AS","AS","SA","OC","AS",
398
- "AF","EU","AF","OC","NA","SA","AS","EU","SA","SA",
399
- "SA","SA","AS","OC","OC","OC","AS","AF","EU","AF",
400
- "AF","EU","AF","--","--","--","EU","EU","EU","EU",
401
- "SA","SA"
402
- ]
403
-
404
- # Hash of the timezone codes mapped to timezone name, per zoneinfo
405
- TimeZone = {
406
- "USAL" => "America/Chicago", "USAK" => "America/Anchorage", "USAZ" => "America/Phoenix",
407
- "USAR" => "America/Chicago", "USCA" => "America/Los_Angeles", "USCO" => "America/Denver",
408
- "USCT" => "America/New_York", "USDE" => "America/New_York", "USDC" => "America/New_York",
409
- "USFL" => "America/New_York", "USGA" => "America/New_York", "USHI" => "Pacific/Honolulu",
410
- "USID" => "America/Denver", "USIL" => "America/Chicago", "USIN" => "America/Indianapolis",
411
- "USIA" => "America/Chicago", "USKS" => "America/Chicago", "USKY" => "America/New_York",
412
- "USLA" => "America/Chicago", "USME" => "America/New_York", "USMD" => "America/New_York",
413
- "USMA" => "America/New_York", "USMI" => "America/New_York", "USMN" => "America/Chicago",
414
- "USMS" => "America/Chicago", "USMO" => "America/Chicago", "USMT" => "America/Denver",
415
- "USNE" => "America/Chicago", "USNV" => "America/Los_Angeles", "USNH" => "America/New_York",
416
- "USNJ" => "America/New_York", "USNM" => "America/Denver", "USNY" => "America/New_York",
417
- "USNC" => "America/New_York", "USND" => "America/Chicago", "USOH" => "America/New_York",
418
- "USOK" => "America/Chicago", "USOR" => "America/Los_Angeles", "USPA" => "America/New_York",
419
- "USRI" => "America/New_York", "USSC" => "America/New_York", "USSD" => "America/Chicago",
420
- "USTN" => "America/Chicago", "USTX" => "America/Chicago", "USUT" => "America/Denver",
421
- "USVT" => "America/New_York", "USVA" => "America/New_York", "USWA" => "America/Los_Angeles",
422
- "USWV" => "America/New_York", "USWI" => "America/Chicago", "USWY" => "America/Denver",
423
- "CAAB" => "America/Edmonton", "CABC" => "America/Vancouver", "CAMB" => "America/Winnipeg",
424
- "CANB" => "America/Halifax", "CANL" => "America/St_Johns", "CANT" => "America/Yellowknife",
425
- "CANS" => "America/Halifax", "CANU" => "America/Rankin_Inlet", "CAON" => "America/Rainy_River",
426
- "CAPE" => "America/Halifax", "CAQC" => "America/Montreal", "CASK" => "America/Regina",
427
- "CAYT" => "America/Whitehorse", "AU01" => "Australia/Canberra", "AU02" => "Australia/NSW",
428
- "AU03" => "Australia/North", "AU04" => "Australia/Queensland", "AU05" => "Australia/South",
429
- "AU06" => "Australia/Tasmania", "AU07" => "Australia/Victoria", "AU08" => "Australia/West",
430
- "AS" => "US/Samoa", "CI" => "Africa/Abidjan", "GH" => "Africa/Accra",
431
- "DZ" => "Africa/Algiers", "ER" => "Africa/Asmera", "ML" => "Africa/Bamako",
432
- "CF" => "Africa/Bangui", "GM" => "Africa/Banjul", "GW" => "Africa/Bissau",
433
- "CG" => "Africa/Brazzaville", "BI" => "Africa/Bujumbura", "EG" => "Africa/Cairo",
434
- "MA" => "Africa/Casablanca", "GN" => "Africa/Conakry", "SN" => "Africa/Dakar",
435
- "DJ" => "Africa/Djibouti", "SL" => "Africa/Freetown", "BW" => "Africa/Gaborone",
436
- "ZW" => "Africa/Harare", "ZA" => "Africa/Johannesburg", "UG" => "Africa/Kampala",
437
- "SD" => "Africa/Khartoum", "RW" => "Africa/Kigali", "NG" => "Africa/Lagos",
438
- "GA" => "Africa/Libreville", "TG" => "Africa/Lome", "AO" => "Africa/Luanda",
439
- "ZM" => "Africa/Lusaka", "GQ" => "Africa/Malabo", "MZ" => "Africa/Maputo",
440
- "LS" => "Africa/Maseru", "SZ" => "Africa/Mbabane", "SO" => "Africa/Mogadishu",
441
- "LR" => "Africa/Monrovia", "KE" => "Africa/Nairobi", "TD" => "Africa/Ndjamena",
442
- "NE" => "Africa/Niamey", "MR" => "Africa/Nouakchott", "BF" => "Africa/Ouagadougou",
443
- "ST" => "Africa/Sao_Tome", "LY" => "Africa/Tripoli", "TN" => "Africa/Tunis",
444
- "AI" => "America/Anguilla", "AG" => "America/Antigua", "AW" => "America/Aruba",
445
- "BB" => "America/Barbados", "BZ" => "America/Belize", "CO" => "America/Bogota",
446
- "VE" => "America/Caracas", "KY" => "America/Cayman", "CR" => "America/Costa_Rica",
447
- "DM" => "America/Dominica", "SV" => "America/El_Salvador", "GD" => "America/Grenada",
448
- "FR" => "Europe/Paris", "GP" => "America/Guadeloupe", "GT" => "America/Guatemala",
449
- "GY" => "America/Guyana", "CU" => "America/Havana", "JM" => "America/Jamaica",
450
- "BO" => "America/La_Paz", "PE" => "America/Lima", "NI" => "America/Managua",
451
- "MQ" => "America/Martinique", "UY" => "America/Montevideo", "MS" => "America/Montserrat",
452
- "BS" => "America/Nassau", "PA" => "America/Panama", "SR" => "America/Paramaribo",
453
- "PR" => "America/Puerto_Rico", "KN" => "America/St_Kitts", "LC" => "America/St_Lucia",
454
- "VC" => "America/St_Vincent", "HN" => "America/Tegucigalpa", "YE" => "Asia/Aden",
455
- "JO" => "Asia/Amman", "TM" => "Asia/Ashgabat", "IQ" => "Asia/Baghdad",
456
- "BH" => "Asia/Bahrain", "AZ" => "Asia/Baku", "TH" => "Asia/Bangkok",
457
- "LB" => "Asia/Beirut", "KG" => "Asia/Bishkek", "BN" => "Asia/Brunei",
458
- "IN" => "Asia/Calcutta", "MN" => "Asia/Choibalsan", "LK" => "Asia/Colombo",
459
- "BD" => "Asia/Dhaka", "AE" => "Asia/Dubai", "TJ" => "Asia/Dushanbe",
460
- "HK" => "Asia/Hong_Kong", "TR" => "Asia/Istanbul", "IL" => "Asia/Jerusalem",
461
- "AF" => "Asia/Kabul", "PK" => "Asia/Karachi", "NP" => "Asia/Katmandu",
462
- "KW" => "Asia/Kuwait", "MO" => "Asia/Macao", "PH" => "Asia/Manila",
463
- "OM" => "Asia/Muscat", "CY" => "Asia/Nicosia", "KP" => "Asia/Pyongyang",
464
- "QA" => "Asia/Qatar", "MM" => "Asia/Rangoon", "SA" => "Asia/Riyadh",
465
- "KR" => "Asia/Seoul", "SG" => "Asia/Singapore", "TW" => "Asia/Taipei",
466
- "GE" => "Asia/Tbilisi", "BT" => "Asia/Thimphu", "JP" => "Asia/Tokyo",
467
- "LA" => "Asia/Vientiane", "AM" => "Asia/Yerevan", "BM" => "Atlantic/Bermuda",
468
- "CV" => "Atlantic/Cape_Verde", "FO" => "Atlantic/Faeroe", "IS" => "Atlantic/Reykjavik",
469
- "GS" => "Atlantic/South_Georgia", "SH" => "Atlantic/St_Helena", "CL" => "Chile/Continental",
470
- "NL" => "Europe/Amsterdam", "AD" => "Europe/Andorra", "GR" => "Europe/Athens",
471
- "YU" => "Europe/Belgrade", "DE" => "Europe/Berlin", "SK" => "Europe/Bratislava",
472
- "BE" => "Europe/Brussels", "RO" => "Europe/Bucharest", "HU" => "Europe/Budapest",
473
- "DK" => "Europe/Copenhagen", "IE" => "Europe/Dublin", "GI" => "Europe/Gibraltar",
474
- "FI" => "Europe/Helsinki", "SI" => "Europe/Ljubljana", "GB" => "Europe/London",
475
- "LU" => "Europe/Luxembourg", "MT" => "Europe/Malta", "BY" => "Europe/Minsk",
476
- "MC" => "Europe/Monaco", "NO" => "Europe/Oslo", "CZ" => "Europe/Prague",
477
- "LV" => "Europe/Riga", "IT" => "Europe/Rome", "SM" => "Europe/San_Marino",
478
- "BA" => "Europe/Sarajevo", "MK" => "Europe/Skopje", "BG" => "Europe/Sofia",
479
- "SE" => "Europe/Stockholm", "EE" => "Europe/Tallinn", "AL" => "Europe/Tirane",
480
- "LI" => "Europe/Vaduz", "VA" => "Europe/Vatican", "AT" => "Europe/Vienna",
481
- "LT" => "Europe/Vilnius", "PL" => "Europe/Warsaw", "HR" => "Europe/Zagreb",
482
- "IR" => "Asia/Tehran", "MG" => "Indian/Antananarivo", "CX" => "Indian/Christmas",
483
- "CC" => "Indian/Cocos", "KM" => "Indian/Comoro", "MV" => "Indian/Maldives",
484
- "MU" => "Indian/Mauritius", "YT" => "Indian/Mayotte", "RE" => "Indian/Reunion",
485
- "FJ" => "Pacific/Fiji", "TV" => "Pacific/Funafuti", "GU" => "Pacific/Guam",
486
- "NR" => "Pacific/Nauru", "NU" => "Pacific/Niue", "NF" => "Pacific/Norfolk",
487
- "PW" => "Pacific/Palau", "PN" => "Pacific/Pitcairn", "CK" => "Pacific/Rarotonga",
488
- "WS" => "Pacific/Samoa", "KI" => "Pacific/Tarawa", "TO" => "Pacific/Tongatapu",
489
- "WF" => "Pacific/Wallis", "TZ" => "Africa/Dar_es_Salaam", "VN" => "Asia/Phnom_Penh",
490
- "KH" => "Asia/Phnom_Penh", "CM" => "Africa/Lagos", "DO" => "America/Santo_Domingo",
491
- "ET" => "Africa/Addis_Ababa", "FX" => "Europe/Paris", "HT" => "America/Port-au-Prince",
492
- "CH" => "Europe/Zurich", "AN" => "America/Curacao", "BJ" => "Africa/Porto-Novo",
493
- "EH" => "Africa/El_Aaiun", "FK" => "Atlantic/Stanley", "GF" => "America/Cayenne",
494
- "IO" => "Indian/Chagos", "MD" => "Europe/Chisinau", "MP" => "Pacific/Saipan",
495
- "MW" => "Africa/Blantyre", "NA" => "Africa/Windhoek", "NC" => "Pacific/Noumea",
496
- "PG" => "Pacific/Port_Moresby", "PM" => "America/Miquelon", "PS" => "Asia/Gaza",
497
- "PY" => "America/Asuncion", "SB" => "Pacific/Guadalcanal", "SC" => "Indian/Mahe",
498
- "SJ" => "Arctic/Longyearbyen", "SY" => "Asia/Damascus", "TC" => "America/Grand_Turk",
499
- "TF" => "Indian/Kerguelen", "TK" => "Pacific/Fakaofo", "TT" => "America/Port_of_Spain",
500
- "VG" => "America/Tortola", "VI" => "America/St_Thomas", "VU" => "Pacific/Efate",
501
- "RS" => "Europe/Belgrade", "ME" => "Europe/Podgorica", "AX" => "Europe/Mariehamn",
502
- "GG" => "Europe/Guernsey", "IM" => "Europe/Isle_of_Man", "JE" => "Europe/Jersey",
503
- "BL" => "America/St_Barthelemy", "MF" => "America/Marigot", "AR01" => "America/Argentina/Buenos_Aires",
504
- "AR02" => "America/Argentina/Catamarca", "AR03" => "America/Argentina/Tucuman", "AR04" => "America/Argentina/Rio_Gallegos",
505
- "AR05" => "America/Argentina/Cordoba", "AR06" => "America/Argentina/Tucuman", "AR07" => "America/Argentina/Buenos_Aires",
506
- "AR08" => "America/Argentina/Buenos_Aires", "AR09" => "America/Argentina/Tucuman", "AR10" => "America/Argentina/Jujuy",
507
- "AR11" => "America/Argentina/San_Luis", "AR12" => "America/Argentina/La_Rioja", "AR13" => "America/Argentina/Mendoza",
508
- "AR14" => "America/Argentina/Buenos_Aires", "AR15" => "America/Argentina/San_Luis", "AR16" => "America/Argentina/Buenos_Aires",
509
- "AR17" => "America/Argentina/Salta", "AR18" => "America/Argentina/San_Juan", "AR19" => "America/Argentina/San_Luis",
510
- "AR20" => "America/Argentina/Rio_Gallegos", "AR21" => "America/Argentina/Buenos_Aires", "AR22" => "America/Argentina/Catamarca",
511
- "AR23" => "America/Argentina/Ushuaia", "AR24" => "America/Argentina/Tucuman", "BR01" => "America/Rio_Branco",
512
- "BR02" => "America/Maceio", "BR03" => "America/Sao_Paulo", "BR04" => "America/Manaus",
513
- "BR05" => "America/Bahia", "BR06" => "America/Fortaleza", "BR07" => "America/Sao_Paulo",
514
- "BR08" => "America/Sao_Paulo", "BR11" => "America/Campo_Grande", "BR13" => "America/Belem",
515
- "BR14" => "America/Cuiaba", "BR15" => "America/Sao_Paulo", "BR16" => "America/Belem",
516
- "BR17" => "America/Recife", "BR18" => "America/Sao_Paulo", "BR20" => "America/Fortaleza",
517
- "BR21" => "America/Sao_Paulo", "BR22" => "America/Recife", "BR23" => "America/Sao_Paulo",
518
- "BR24" => "America/Porto_Velho", "BR25" => "America/Boa_Vista", "BR26" => "America/Sao_Paulo",
519
- "BR27" => "America/Sao_Paulo", "BR28" => "America/Maceio", "BR29" => "America/Sao_Paulo",
520
- "BR30" => "America/Recife", "BR31" => "America/Araguaina", "CD02" => "Africa/Kinshasa",
521
- "CD05" => "Africa/Lubumbashi", "CD06" => "Africa/Kinshasa", "CD08" => "Africa/Kinshasa",
522
- "CD10" => "Africa/Lubumbashi", "CD11" => "Africa/Lubumbashi", "CD12" => "Africa/Lubumbashi",
523
- "CN01" => "Asia/Shanghai", "CN02" => "Asia/Shanghai", "CN03" => "Asia/Shanghai",
524
- "CN04" => "Asia/Shanghai", "CN05" => "Asia/Harbin", "CN06" => "Asia/Chongqing",
525
- "CN07" => "Asia/Shanghai", "CN08" => "Asia/Harbin", "CN09" => "Asia/Shanghai",
526
- "CN10" => "Asia/Shanghai", "CN11" => "Asia/Chongqing", "CN12" => "Asia/Shanghai",
527
- "CN13" => "Asia/Urumqi", "CN14" => "Asia/Chongqing", "CN15" => "Asia/Chongqing",
528
- "CN16" => "Asia/Chongqing", "CN18" => "Asia/Chongqing", "CN19" => "Asia/Harbin",
529
- "CN20" => "Asia/Harbin", "CN21" => "Asia/Chongqing", "CN22" => "Asia/Harbin",
530
- "CN23" => "Asia/Shanghai", "CN24" => "Asia/Chongqing", "CN25" => "Asia/Shanghai",
531
- "CN26" => "Asia/Chongqing", "CN28" => "Asia/Shanghai", "CN29" => "Asia/Chongqing",
532
- "CN30" => "Asia/Chongqing", "CN31" => "Asia/Chongqing", "CN32" => "Asia/Chongqing",
533
- "CN33" => "Asia/Chongqing", "EC01" => "Pacific/Galapagos", "EC02" => "America/Guayaquil",
534
- "EC03" => "America/Guayaquil", "EC04" => "America/Guayaquil", "EC05" => "America/Guayaquil",
535
- "EC06" => "America/Guayaquil", "EC07" => "America/Guayaquil", "EC08" => "America/Guayaquil",
536
- "EC09" => "America/Guayaquil", "EC10" => "America/Guayaquil", "EC11" => "America/Guayaquil",
537
- "EC12" => "America/Guayaquil", "EC13" => "America/Guayaquil", "EC14" => "America/Guayaquil",
538
- "EC15" => "America/Guayaquil", "EC17" => "America/Guayaquil", "EC18" => "America/Guayaquil",
539
- "EC19" => "America/Guayaquil", "EC20" => "America/Guayaquil", "EC22" => "America/Guayaquil",
540
- "ES07" => "Europe/Madrid", "ES27" => "Europe/Madrid", "ES29" => "Europe/Madrid",
541
- "ES31" => "Europe/Madrid", "ES32" => "Europe/Madrid", "ES34" => "Europe/Madrid",
542
- "ES39" => "Europe/Madrid", "ES51" => "Africa/Ceuta", "ES52" => "Europe/Madrid",
543
- "ES53" => "Atlantic/Canary", "ES54" => "Europe/Madrid", "ES55" => "Europe/Madrid",
544
- "ES56" => "Europe/Madrid", "ES57" => "Europe/Madrid", "ES58" => "Europe/Madrid",
545
- "ES59" => "Europe/Madrid", "ES60" => "Europe/Madrid", "GL01" => "America/Thule",
546
- "GL02" => "America/Godthab", "GL03" => "America/Godthab", "ID01" => "Asia/Pontianak",
547
- "ID02" => "Asia/Makassar", "ID03" => "Asia/Jakarta", "ID04" => "Asia/Jakarta",
548
- "ID05" => "Asia/Jakarta", "ID06" => "Asia/Jakarta", "ID07" => "Asia/Jakarta",
549
- "ID08" => "Asia/Jakarta", "ID09" => "Asia/Jayapura", "ID10" => "Asia/Jakarta",
550
- "ID11" => "Asia/Pontianak", "ID12" => "Asia/Makassar", "ID13" => "Asia/Makassar",
551
- "ID14" => "Asia/Makassar", "ID15" => "Asia/Jakarta", "ID16" => "Asia/Makassar",
552
- "ID17" => "Asia/Makassar", "ID18" => "Asia/Makassar", "ID19" => "Asia/Pontianak",
553
- "ID20" => "Asia/Makassar", "ID21" => "Asia/Makassar", "ID22" => "Asia/Makassar",
554
- "ID23" => "Asia/Makassar", "ID24" => "Asia/Jakarta", "ID25" => "Asia/Pontianak",
555
- "ID26" => "Asia/Pontianak", "ID30" => "Asia/Jakarta", "ID31" => "Asia/Makassar",
556
- "ID33" => "Asia/Jakarta", "KZ01" => "Asia/Almaty", "KZ02" => "Asia/Almaty",
557
- "KZ03" => "Asia/Qyzylorda", "KZ04" => "Asia/Aqtobe", "KZ05" => "Asia/Qyzylorda",
558
- "KZ06" => "Asia/Aqtau", "KZ07" => "Asia/Oral", "KZ08" => "Asia/Qyzylorda",
559
- "KZ09" => "Asia/Aqtau", "KZ10" => "Asia/Qyzylorda", "KZ11" => "Asia/Almaty",
560
- "KZ12" => "Asia/Qyzylorda", "KZ13" => "Asia/Aqtobe", "KZ14" => "Asia/Qyzylorda",
561
- "KZ15" => "Asia/Almaty", "KZ16" => "Asia/Aqtobe", "KZ17" => "Asia/Almaty",
562
- "MX01" => "America/Mexico_City", "MX02" => "America/Tijuana", "MX03" => "America/Hermosillo",
563
- "MX04" => "America/Merida", "MX05" => "America/Mexico_City", "MX06" => "America/Chihuahua",
564
- "MX07" => "America/Monterrey", "MX08" => "America/Mexico_City", "MX09" => "America/Mexico_City",
565
- "MX10" => "America/Mazatlan", "MX11" => "America/Mexico_City", "MX12" => "America/Mexico_City",
566
- "MX13" => "America/Mexico_City", "MX14" => "America/Mazatlan", "MX15" => "America/Chihuahua",
567
- "MX16" => "America/Mexico_City", "MX17" => "America/Mexico_City", "MX18" => "America/Mazatlan",
568
- "MX19" => "America/Monterrey", "MX20" => "America/Mexico_City", "MX21" => "America/Mexico_City",
569
- "MX22" => "America/Mexico_City", "MX23" => "America/Cancun", "MX24" => "America/Mexico_City",
570
- "MX25" => "America/Mazatlan", "MX26" => "America/Hermosillo", "MX27" => "America/Merida",
571
- "MX28" => "America/Monterrey", "MX29" => "America/Mexico_City", "MX30" => "America/Mexico_City",
572
- "MX31" => "America/Merida", "MX32" => "America/Monterrey", "MY01" => "Asia/Kuala_Lumpur",
573
- "MY02" => "Asia/Kuala_Lumpur", "MY03" => "Asia/Kuala_Lumpur", "MY04" => "Asia/Kuala_Lumpur",
574
- "MY05" => "Asia/Kuala_Lumpur", "MY06" => "Asia/Kuala_Lumpur", "MY07" => "Asia/Kuala_Lumpur",
575
- "MY08" => "Asia/Kuala_Lumpur", "MY09" => "Asia/Kuala_Lumpur", "MY11" => "Asia/Kuching",
576
- "MY12" => "Asia/Kuala_Lumpur", "MY13" => "Asia/Kuala_Lumpur", "MY14" => "Asia/Kuala_Lumpur",
577
- "MY15" => "Asia/Kuching", "MY16" => "Asia/Kuching", "NZ85" => "Pacific/Auckland",
578
- "NZE7" => "Pacific/Auckland", "NZE8" => "Pacific/Auckland", "NZE9" => "Pacific/Auckland",
579
- "NZF1" => "Pacific/Auckland", "NZF2" => "Pacific/Auckland", "NZF3" => "Pacific/Auckland",
580
- "NZF4" => "Pacific/Auckland", "NZF5" => "Pacific/Auckland", "NZF7" => "Pacific/Chatham",
581
- "NZF8" => "Pacific/Auckland", "NZF9" => "Pacific/Auckland", "NZG1" => "Pacific/Auckland",
582
- "NZG2" => "Pacific/Auckland", "NZG3" => "Pacific/Auckland", "PT02" => "Europe/Lisbon",
583
- "PT03" => "Europe/Lisbon", "PT04" => "Europe/Lisbon", "PT05" => "Europe/Lisbon",
584
- "PT06" => "Europe/Lisbon", "PT07" => "Europe/Lisbon", "PT08" => "Europe/Lisbon",
585
- "PT09" => "Europe/Lisbon", "PT10" => "Atlantic/Madeira", "PT11" => "Europe/Lisbon",
586
- "PT13" => "Europe/Lisbon", "PT14" => "Europe/Lisbon", "PT16" => "Europe/Lisbon",
587
- "PT17" => "Europe/Lisbon", "PT18" => "Europe/Lisbon", "PT19" => "Europe/Lisbon",
588
- "PT20" => "Europe/Lisbon", "PT21" => "Europe/Lisbon", "PT22" => "Europe/Lisbon",
589
- "RU01" => "Europe/Volgograd", "RU02" => "Asia/Irkutsk", "RU03" => "Asia/Novokuznetsk",
590
- "RU04" => "Asia/Novosibirsk", "RU05" => "Asia/Vladivostok", "RU06" => "Europe/Moscow",
591
- "RU07" => "Europe/Volgograd", "RU08" => "Europe/Samara", "RU09" => "Europe/Moscow",
592
- "RU10" => "Europe/Moscow", "RU11" => "Asia/Irkutsk", "RU13" => "Asia/Yekaterinburg",
593
- "RU14" => "Asia/Irkutsk", "RU15" => "Asia/Anadyr", "RU16" => "Europe/Samara",
594
- "RU17" => "Europe/Volgograd", "RU18" => "Asia/Krasnoyarsk", "RU20" => "Asia/Irkutsk",
595
- "RU21" => "Europe/Moscow", "RU22" => "Europe/Volgograd", "RU23" => "Europe/Kaliningrad",
596
- "RU24" => "Europe/Volgograd", "RU25" => "Europe/Moscow", "RU26" => "Asia/Kamchatka",
597
- "RU27" => "Europe/Volgograd", "RU28" => "Europe/Moscow", "RU29" => "Asia/Novokuznetsk",
598
- "RU30" => "Asia/Vladivostok", "RU31" => "Asia/Krasnoyarsk", "RU32" => "Asia/Omsk",
599
- "RU33" => "Asia/Yekaterinburg", "RU34" => "Asia/Yekaterinburg", "RU35" => "Asia/Yekaterinburg",
600
- "RU36" => "Asia/Anadyr", "RU37" => "Europe/Moscow", "RU38" => "Europe/Volgograd",
601
- "RU39" => "Asia/Krasnoyarsk", "RU40" => "Asia/Yekaterinburg", "RU41" => "Europe/Moscow",
602
- "RU42" => "Europe/Moscow", "RU43" => "Europe/Moscow", "RU44" => "Asia/Magadan",
603
- "RU45" => "Europe/Samara", "RU46" => "Europe/Samara", "RU47" => "Europe/Moscow",
604
- "RU48" => "Europe/Moscow", "RU49" => "Europe/Moscow", "RU50" => "Asia/Yekaterinburg",
605
- "RU51" => "Europe/Moscow", "RU52" => "Europe/Moscow", "RU53" => "Asia/Novosibirsk",
606
- "RU54" => "Asia/Omsk", "RU55" => "Europe/Samara", "RU56" => "Europe/Moscow",
607
- "RU57" => "Europe/Samara", "RU58" => "Asia/Yekaterinburg", "RU59" => "Asia/Vladivostok",
608
- "RU60" => "Europe/Kaliningrad", "RU61" => "Europe/Volgograd", "RU62" => "Europe/Moscow",
609
- "RU63" => "Asia/Yakutsk", "RU64" => "Asia/Sakhalin", "RU65" => "Europe/Samara",
610
- "RU66" => "Europe/Moscow", "RU67" => "Europe/Samara", "RU68" => "Europe/Volgograd",
611
- "RU69" => "Europe/Moscow", "RU70" => "Europe/Volgograd", "RU71" => "Asia/Yekaterinburg",
612
- "RU72" => "Europe/Moscow", "RU73" => "Europe/Samara", "RU74" => "Asia/Krasnoyarsk",
613
- "RU75" => "Asia/Novosibirsk", "RU76" => "Europe/Moscow", "RU77" => "Europe/Moscow",
614
- "RU78" => "Asia/Yekaterinburg", "RU79" => "Asia/Irkutsk", "RU80" => "Asia/Yekaterinburg",
615
- "RU81" => "Europe/Samara", "RU82" => "Asia/Irkutsk", "RU83" => "Europe/Moscow",
616
- "RU84" => "Europe/Volgograd", "RU85" => "Europe/Moscow", "RU86" => "Europe/Moscow",
617
- "RU87" => "Asia/Novosibirsk", "RU88" => "Europe/Moscow", "RU89" => "Asia/Vladivostok",
618
- "UA01" => "Europe/Kiev", "UA02" => "Europe/Kiev", "UA03" => "Europe/Uzhgorod",
619
- "UA04" => "Europe/Zaporozhye", "UA05" => "Europe/Zaporozhye", "UA06" => "Europe/Uzhgorod",
620
- "UA07" => "Europe/Zaporozhye", "UA08" => "Europe/Simferopol", "UA09" => "Europe/Kiev",
621
- "UA10" => "Europe/Zaporozhye", "UA11" => "Europe/Simferopol", "UA13" => "Europe/Kiev",
622
- "UA14" => "Europe/Zaporozhye", "UA15" => "Europe/Uzhgorod", "UA16" => "Europe/Zaporozhye",
623
- "UA17" => "Europe/Simferopol", "UA18" => "Europe/Zaporozhye", "UA19" => "Europe/Kiev",
624
- "UA20" => "Europe/Simferopol", "UA21" => "Europe/Kiev", "UA22" => "Europe/Uzhgorod",
625
- "UA23" => "Europe/Kiev", "UA24" => "Europe/Uzhgorod", "UA25" => "Europe/Uzhgorod",
626
- "UA26" => "Europe/Zaporozhye", "UA27" => "Europe/Kiev", "UZ01" => "Asia/Tashkent",
627
- "UZ02" => "Asia/Samarkand", "UZ03" => "Asia/Tashkent", "UZ06" => "Asia/Tashkent",
628
- "UZ07" => "Asia/Samarkand", "UZ08" => "Asia/Samarkand", "UZ09" => "Asia/Samarkand",
629
- "UZ10" => "Asia/Samarkand", "UZ12" => "Asia/Samarkand", "UZ13" => "Asia/Tashkent",
630
- "UZ14" => "Asia/Tashkent", "TL" => "Asia/Dili", "PF" => "Pacific/Marquesas"
631
- }
632
-
633
- public
634
- GEOIP_COUNTRY_EDITION = 1
635
- GEOIP_CITY_EDITION_REV1 = 2
636
- GEOIP_REGION_EDITION_REV1 = 3
637
- GEOIP_ISP_EDITION = 4
638
- GEOIP_ORG_EDITION = 5
639
- GEOIP_CITY_EDITION_REV0 = 6
640
- GEOIP_REGION_EDITION_REV0 = 7
641
- GEOIP_PROXY_EDITION = 8
642
- GEOIP_ASNUM_EDITION = 9
643
- GEOIP_NETSPEED_EDITION = 10
644
-
645
- private
646
- COUNTRY_BEGIN = 16776960 #:nodoc:
647
- STATE_BEGIN_REV0 = 16700000 #:nodoc:
648
- STATE_BEGIN_REV1 = 16000000 #:nodoc:
649
- STRUCTURE_INFO_MAX_SIZE = 20 #:nodoc:
650
- DATABASE_INFO_MAX_SIZE = 100 #:nodoc:
651
- MAX_ORG_RECORD_LENGTH = 300 #:nodoc:
652
- MAX_ASN_RECORD_LENGTH = 300 #:nodoc: unverified
653
- US_OFFSET = 1 #:nodoc:
654
- CANADA_OFFSET = 677 #:nodoc:
655
- WORLD_OFFSET = 1353 #:nodoc:
656
- FIPS_RANGE = 360 #:nodoc:
657
- FULL_RECORD_LENGTH = 50 #:nodoc:
658
-
659
- STANDARD_RECORD_LENGTH = 3 #:nodoc:
660
- SEGMENT_RECORD_LENGTH = 3 #:nodoc:
661
-
662
- public
663
- # The Edition number that identifies which kind of database you've opened
664
- attr_reader :databaseType
665
-
666
- # Open the GeoIP database and determine the file format version
667
- #
668
- # +filename+ is a String holding the path to the GeoIP.dat file
669
- # +options+ is an integer holding caching flags (unimplemented)
670
- def initialize(filename, flags = 0)
671
- @mutex = IO.respond_to?(:pread) ? false : Mutex.new
672
- @flags = flags
673
- @databaseType = GEOIP_COUNTRY_EDITION
674
- @record_length = STANDARD_RECORD_LENGTH
675
- @file = File.open(filename, 'rb')
676
- @file.seek(-3, IO::SEEK_END)
677
- 0.upto(STRUCTURE_INFO_MAX_SIZE-1) { |i|
678
- if @file.read(3).bytes.all?{|byte| 255 == byte}
679
- @databaseType = @file.respond_to?(:getbyte) ? @file.getbyte : @file.getc
680
- @databaseType -= 105 if @databaseType >= 106
681
-
682
- if (@databaseType == GEOIP_REGION_EDITION_REV0)
683
- # Region Edition, pre June 2003
684
- @databaseSegments = [ STATE_BEGIN_REV0 ]
685
- elsif (@databaseType == GEOIP_REGION_EDITION_REV1)
686
- # Region Edition, post June 2003
687
- @databaseSegments = [ STATE_BEGIN_REV1 ]
688
- elsif (@databaseType == GEOIP_CITY_EDITION_REV0 ||
689
- @databaseType == GEOIP_CITY_EDITION_REV1 ||
690
- @databaseType == GEOIP_ORG_EDITION ||
691
- @databaseType == GEOIP_ISP_EDITION ||
692
- @databaseType == GEOIP_ASNUM_EDITION)
693
- # City/Org Editions have two segments, read offset of second segment
694
- @databaseSegments = [ 0 ]
695
- sr = @file.read(3).unpack("C*")
696
- @databaseSegments[0] += le_to_ui(sr)
697
-
698
- if (@databaseType == GEOIP_ORG_EDITION ||
699
- @databaseType == GEOIP_ISP_EDITION)
700
- @record_length = 4
701
- end
702
- end
703
- break
704
-
705
- else
706
- @file.seek(-4, IO::SEEK_CUR)
707
- end
708
- }
709
- if (@databaseType == GEOIP_COUNTRY_EDITION ||
710
- @databaseType == GEOIP_PROXY_EDITION ||
711
- @databaseType == GEOIP_NETSPEED_EDITION)
712
- @databaseSegments = [ COUNTRY_BEGIN ]
713
- end
55
+
56
+ # The GeoIP GEM version number
57
+ VERSION = "1.1.0"
58
+
59
+ # The +data/+ directory for geoip
60
+ DATA_DIR = File.expand_path(File.join(File.dirname(__FILE__),'..','data','geoip'))
61
+
62
+ # Ordered list of the ISO3166 2-character country codes, ordered by
63
+ # GeoIP ID
64
+ CountryCode = YAML.load_file(File.join(DATA_DIR,'country_code.yml'))
65
+
66
+ # Ordered list of the ISO3166 3-character country codes, ordered by
67
+ # GeoIP ID
68
+ CountryCode3 = YAML.load_file(File.join(DATA_DIR,'country_code3.yml'))
69
+
70
+ # Ordered list of the English names of the countries, ordered by GeoIP ID
71
+ CountryName = YAML.load_file(File.join(DATA_DIR,'country_name.yml'))
72
+
73
+ # Ordered list of the ISO3166 2-character continent code of the countries,
74
+ # ordered by GeoIP ID
75
+ CountryContinent = YAML.load_file(File.join(DATA_DIR,'country_continent.yml'))
76
+
77
+ # Hash of the timezone codes mapped to timezone name, per zoneinfo
78
+ TimeZone = YAML.load_file(File.join(DATA_DIR,'time_zone.yml'))
79
+
80
+ GEOIP_COUNTRY_EDITION = 1
81
+ GEOIP_CITY_EDITION_REV1 = 2
82
+ GEOIP_REGION_EDITION_REV1 = 3
83
+ GEOIP_ISP_EDITION = 4
84
+ GEOIP_ORG_EDITION = 5
85
+ GEOIP_CITY_EDITION_REV0 = 6
86
+ GEOIP_REGION_EDITION_REV0 = 7
87
+ GEOIP_PROXY_EDITION = 8
88
+ GEOIP_ASNUM_EDITION = 9
89
+ GEOIP_NETSPEED_EDITION = 10
90
+
91
+ COUNTRY_BEGIN = 16776960 #:nodoc:
92
+ STATE_BEGIN_REV0 = 16700000 #:nodoc:
93
+ STATE_BEGIN_REV1 = 16000000 #:nodoc:
94
+ STRUCTURE_INFO_MAX_SIZE = 20 #:nodoc:
95
+ DATABASE_INFO_MAX_SIZE = 100 #:nodoc:
96
+ MAX_ORG_RECORD_LENGTH = 300 #:nodoc:
97
+ MAX_ASN_RECORD_LENGTH = 300 #:nodoc: unverified
98
+ US_OFFSET = 1 #:nodoc:
99
+ CANADA_OFFSET = 677 #:nodoc:
100
+ WORLD_OFFSET = 1353 #:nodoc:
101
+ FIPS_RANGE = 360 #:nodoc:
102
+ FULL_RECORD_LENGTH = 50 #:nodoc:
103
+
104
+ STANDARD_RECORD_LENGTH = 3 #:nodoc:
105
+ SEGMENT_RECORD_LENGTH = 3 #:nodoc:
106
+
107
+ class Country < Struct.new(:request, :ip, :country_code, :country_code2, :country_code3, :country_name, :continent_code)
108
+
109
+ def to_hash
110
+ Hash[each_pair.to_a]
714
111
  end
715
112
 
716
- # Search the GeoIP database for the specified host, returning country info
717
- #
718
- # +hostname+ is a String holding the host's DNS name or numeric IP address.
719
- # If the database is a City database (normal), return the result that +city+ would return.
720
- # Otherwise, return an array of seven elements:
721
- # * The host or IP address string as requested
722
- # * The IP address string after looking up the host
723
- # * The GeoIP country-ID as an integer (N.B. this is excluded from the city results!)
724
- # * The two-character country code (ISO 3166-1 alpha-2)
725
- # * The three-character country code (ISO 3166-2 alpha-3)
726
- # * The ISO 3166 English-language name of the country
727
- # * The two-character continent code
728
- #
729
- # The array has been extended with methods listed in GeoIP::CountryAccessors.ACCESSORS:
730
- # request, ip, country_code, country_code2, country_code3, country_name, continent_code.
731
- # In addition, +to_hash+ provides a symbol-keyed hash for the above values.
732
- #
733
- def country(hostname)
734
- if (@databaseType == GEOIP_CITY_EDITION_REV0 ||
735
- @databaseType == GEOIP_CITY_EDITION_REV1)
736
- return city(hostname)
737
- end
113
+ end
738
114
 
739
- ip = hostname
740
- if ip.kind_of?(String) && ip !~ /^[0-9.]*$/
741
- # Lookup IP address, we were given a name
742
- ip = IPSocket.getaddress(hostname)
743
- ip = '0.0.0.0' if ip == '::1'
744
- end
115
+ class City < Struct.new(:request, :ip, :country_code2, :country_code3, :country_name, :continent_code,
116
+ :region_name, :city_name, :postal_code, :latitude, :longitude, :dma_code, :area_code, :timezone)
745
117
 
746
- # Convert numeric IP address to an integer
747
- ipnum = iptonum(ip)
748
- if (@databaseType != GEOIP_COUNTRY_EDITION &&
749
- @databaseType != GEOIP_PROXY_EDITION &&
750
- @databaseType != GEOIP_NETSPEED_EDITION)
751
- throw "Invalid GeoIP database type, can't look up Country by IP"
752
- end
753
- code = seek_record(ipnum) - COUNTRY_BEGIN;
754
- [ hostname, # Requested hostname
755
- ip, # Ip address as dotted quad
756
- code, # GeoIP's country code
757
- CountryCode[code], # ISO3166-1 alpha-2 code
758
- CountryCode3[code], # ISO3166-2 alpha-3 code
759
- CountryName[code], # Country name, per ISO 3166
760
- CountryContinent[code] # Continent code.
761
- ].extend(CountryAccessors)
118
+ def to_hash
119
+ Hash[each_pair.to_a]
762
120
  end
763
121
 
764
- # Search the GeoIP database for the specified host, returning city info.
765
- #
766
- # +hostname+ is a String holding the host's DNS name or numeric IP address.
767
- # Return an array of fourteen elements:
768
- # * The host or IP address string as requested
769
- # * The IP address string after looking up the host
770
- # * The two-character country code (ISO 3166-1 alpha-2)
771
- # * The three-character country code (ISO 3166-2 alpha-3)
772
- # * The ISO 3166 English-language name of the country
773
- # * The two-character continent code
774
- # * The region name (state or territory)
775
- # * The city name
776
- # * The postal code (zipcode)
777
- # * The latitude
778
- # * The longitude
779
- # * The USA dma_code if known (only REV1 City database)
780
- # * The USA area_code if known (only REV1 City database)
781
- # * The timezone name, if known
782
- #
783
- # The array has been extended with methods listed in GeoIP::CityAccessors.ACCESSORS:
784
- # request, ip, country_code2, country_code3, country_name, continent_code,
785
- # region_name, city_name, postal_code, latitude, longitude, dma_code, area_code, timezone.
786
- # In addition, +to_hash+ provides a symbol-keyed hash for the above values.
787
- #
788
- def city(hostname)
789
- ip = hostname
790
- if ip.kind_of?(String) && ip !~ /^[0-9.]*$/
791
- # Lookup IP address, we were given a name
792
- ip = IPSocket.getaddress(hostname)
793
- ip = '0.0.0.0' if ip == '::1'
794
- end
795
-
796
- # Convert numeric IP address to an integer
797
- ipnum = iptonum(ip)
798
- if (@databaseType != GEOIP_CITY_EDITION_REV0 &&
799
- @databaseType != GEOIP_CITY_EDITION_REV1)
800
- throw "Invalid GeoIP database type, can't look up City by IP"
801
- end
802
- pos = seek_record(ipnum);
803
- # This next statement was added to MaxMind's C version after it was rewritten in Ruby.
804
- # It prevents unassigned IP addresses from returning bogus data. There was concern over
805
- # whether the changes to an application's behaviour were always correct, but this has been
806
- # tested using an exhaustive search of the top 16 bits of the IP address space. The records
807
- # where the change takes effect contained *no* valid data. If you're concerned, email me,
808
- # and I'll send you the test program so you can test whatever IP range you think is causing
809
- # problems, as I don't care to undertake an exhaustive search of the 32-bit space.
810
- return nil if pos == @databaseSegments[0]
811
- read_city(pos, hostname, ip).extend(CityAccessors)
122
+ end
123
+
124
+ class ASN < Struct.new(:number, :asn)
125
+
126
+ alias as_num number
127
+
128
+ end
129
+
130
+ # The Edition number that identifies which kind of database you've opened
131
+ attr_reader :database_type
132
+
133
+ alias databaseType database_type
134
+
135
+ # Open the GeoIP database and determine the file format version.
136
+ #
137
+ # +filename+ is a String holding the path to the GeoIP.dat file
138
+ # +options+ is an integer holding caching flags (unimplemented)
139
+ #
140
+ def initialize(filename, flags = 0)
141
+ @mutex = unless IO.respond_to?(:pread)
142
+ Mutex.new
143
+ end
144
+
145
+ @flags = flags
146
+ @database_type = GEOIP_COUNTRY_EDITION
147
+ @record_length = STANDARD_RECORD_LENGTH
148
+ @file = File.open(filename, 'rb')
149
+
150
+ detect_database_type!
151
+ end
152
+
153
+ # Search the GeoIP database for the specified host, returning country
154
+ # info.
155
+ #
156
+ # +hostname+ is a String holding the host's DNS name or numeric IP
157
+ # address.
158
+ #
159
+ # If the database is a City database (normal), return the result that
160
+ # +city+ would return.
161
+ #
162
+ # Otherwise, return a Country object with the seven elements:
163
+ # * The host or IP address string as requested
164
+ # * The IP address string after looking up the host
165
+ # * The GeoIP country-ID as an integer (N.B. this is excluded from the
166
+ # city results!)
167
+ # * The two-character country code (ISO 3166-1 alpha-2)
168
+ # * The three-character country code (ISO 3166-2 alpha-3)
169
+ # * The ISO 3166 English-language name of the country
170
+ # * The two-character continent code
171
+ #
172
+ def country(hostname)
173
+ if (@database_type == GEOIP_CITY_EDITION_REV0 ||
174
+ @database_type == GEOIP_CITY_EDITION_REV1)
175
+ return city(hostname)
812
176
  end
813
177
 
814
- # Search a ISP GeoIP database for the specified host, returning the ISP
815
- # Not all GeoIP databases contain ISP information. Check http://maxmind.com
816
- #
817
- # +hostname+ is a String holding the host's DNS name or numeric IP address.
818
- # Return the ISP name
819
- #
820
- def isp(hostname)
821
- ip = hostname
822
- if ip.kind_of?(String) && ip !~ /^[0-9.]*$/
823
- # Lookup IP address, we were given a name
824
- ip = IPSocket.getaddress(hostname)
825
- ip = '0.0.0.0' if ip == '::1'
826
- end
178
+ ip = lookup_ip(hostname)
827
179
 
828
- # Convert numeric IP address to an integer
829
- ipnum = iptonum(ip)
830
- if @databaseType != GEOIP_ISP_EDITION
831
- throw "Invalid GeoIP database type, can't look up Organization/ISP by IP"
832
- end
833
- pos = seek_record(ipnum);
834
- off = pos + (2*@record_length-1) * @databaseSegments[0]
835
- record = atomic_read(MAX_ORG_RECORD_LENGTH, off)
836
- record = record.sub(/\000.*/n, '')
837
- record
838
- end
180
+ # Convert numeric IP address to an integer
181
+ ipnum = iptonum(ip)
839
182
 
840
- # Search a ASN GeoIP database for the specified host, returning the AS number + description
841
- #
842
- # +hostname+ is a String holding the host's DNS name or numeric IP address.
843
- # Return the AS number + description
844
- #
845
- # Source:
846
- # http://geolite.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz
847
- #
848
- def asn(hostname)
849
- ip = hostname
850
- if ip.kind_of?(String) && ip !~ /^[0-9.]*$/
851
- # Lookup IP address, we were given a name
852
- ip = IPSocket.getaddress(hostname)
853
- ip = '0.0.0.0' if ip == '::1'
854
- end
183
+ if (@database_type != GEOIP_COUNTRY_EDITION &&
184
+ @database_type != GEOIP_PROXY_EDITION &&
185
+ @database_type != GEOIP_NETSPEED_EDITION)
186
+ throw "Invalid GeoIP database type, can't look up Country by IP"
187
+ end
855
188
 
856
- # Convert numeric IP address to an integer
857
- ipnum = iptonum(ip)
858
- if (@databaseType != GEOIP_ASNUM_EDITION)
859
- throw "Invalid GeoIP database type, can't look up ASN by IP"
860
- end
861
- pos = seek_record(ipnum);
862
- off = pos + (2*@record_length-1) * @databaseSegments[0]
863
- record = atomic_read(MAX_ASN_RECORD_LENGTH, off)
864
- record = record.sub(/\000.*/n, '')
865
-
866
- if record =~ /^(AS\d+)\s(.*)$/
867
- # AS####, Description
868
- return [$1, $2].extend(ASNAccessors)
869
- end
189
+ code = (seek_record(ipnum) - COUNTRY_BEGIN)
190
+
191
+ Country.new(
192
+ hostname, # Requested hostname
193
+ ip, # Ip address as dotted quad
194
+ code, # GeoIP's country code
195
+ CountryCode[code], # ISO3166-1 alpha-2 code
196
+ CountryCode3[code], # ISO3166-2 alpha-3 code
197
+ CountryName[code], # Country name, per ISO 3166
198
+ CountryContinent[code] # Continent code.
199
+ )
200
+ end
201
+
202
+ # Search the GeoIP database for the specified host, returning city info.
203
+ #
204
+ # +hostname+ is a String holding the host's DNS name or numeric IP
205
+ # address.
206
+ #
207
+ # Returns a City object with the fourteen elements:
208
+ # * The host or IP address string as requested
209
+ # * The IP address string after looking up the host
210
+ # * The two-character country code (ISO 3166-1 alpha-2)
211
+ # * The three-character country code (ISO 3166-2 alpha-3)
212
+ # * The ISO 3166 English-language name of the country
213
+ # * The two-character continent code
214
+ # * The region name (state or territory)
215
+ # * The city name
216
+ # * The postal code (zipcode)
217
+ # * The latitude
218
+ # * The longitude
219
+ # * The USA dma_code if known (only REV1 City database)
220
+ # * The USA area_code if known (only REV1 City database)
221
+ # * The timezone name, if known
222
+ #
223
+ def city(hostname)
224
+ ip = lookup_ip(hostname)
225
+
226
+ # Convert numeric IP address to an integer
227
+ ipnum = iptonum(ip)
228
+
229
+ if (@database_type != GEOIP_CITY_EDITION_REV0 &&
230
+ @database_type != GEOIP_CITY_EDITION_REV1)
231
+ throw "Invalid GeoIP database type, can't look up City by IP"
870
232
  end
871
233
 
872
- # Search a ISP GeoIP database for the specified host, returning the organization
873
- #
874
- # +hostname+ is a String holding the host's DNS name or numeric IP address.
875
- # Return the organization associated with it
876
- #
877
- alias_method(:organization, :isp) # Untested, according to Maxmind docs this should work
878
-
879
- # Iterate through a GeoIP city database
880
- def each
881
- if (@databaseType != GEOIP_CITY_EDITION_REV0 &&
882
- @databaseType != GEOIP_CITY_EDITION_REV1)
883
- throw "Invalid GeoIP database type, can't iterate thru non-City database"
884
- end
234
+ pos = seek_record(ipnum)
235
+
236
+ # This next statement was added to MaxMind's C version after it was
237
+ # rewritten in Ruby. It prevents unassigned IP addresses from returning
238
+ # bogus data. There was concern over whether the changes to an
239
+ # application's behaviour were always correct, but this has been tested
240
+ # using an exhaustive search of the top 16 bits of the IP address space.
241
+ # The records where the change takes effect contained *no* valid data.
242
+ # If you're concerned, email me, and I'll send you the test program so
243
+ # you can test whatever IP range you think is causing problems,
244
+ # as I don't care to undertake an exhaustive search of the 32-bit space.
245
+ unless pos == @database_segments[0]
246
+ read_city(pos, hostname, ip)
247
+ end
248
+ end
249
+
250
+ # Search a ISP GeoIP database for the specified host, returning the ISP
251
+ # Not all GeoIP databases contain ISP information.
252
+ # Check http://maxmind.com
253
+ #
254
+ # +hostname+ is a String holding the host's DNS name or numeric IP
255
+ # address.
256
+ #
257
+ # Returns the ISP name.
258
+ #
259
+ def isp(hostname)
260
+ ip = lookup_ip(hostname)
261
+
262
+ # Convert numeric IP address to an integer
263
+ ipnum = iptonum(ip)
264
+
265
+ if (@database_type != GEOIP_ISP_EDITION &&
266
+ @database_type != GEOIP_ORG_EDITION)
267
+ throw "Invalid GeoIP database type, can't look up Organization/ISP by IP"
268
+ end
885
269
 
886
- @iter_pos = @databaseSegments[0] + 1
887
- num = 0
888
- until((rec = read_city(@iter_pos)).nil?)
889
- yield(rec)
890
- print "#{num}: #{@iter_pos}\n" if((num += 1) % 1000 == 0)
891
- end
892
- @iter_pos = nil
893
- self
270
+ pos = seek_record(ipnum)
271
+ off = pos + (2 * (@record_length-1)) * @database_segments[0]
272
+
273
+ record = atomic_read(MAX_ORG_RECORD_LENGTH, off)
274
+ record = record.sub(/\000.*/n, '')
275
+ record
276
+ end
277
+
278
+ # Search a ASN GeoIP database for the specified host, returning the AS
279
+ # number and description.
280
+ #
281
+ # +hostname+ is a String holding the host's DNS name or numeric
282
+ # IP address.
283
+ #
284
+ # Returns the AS number and description.
285
+ #
286
+ # Source:
287
+ # http://geolite.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz
288
+ #
289
+ def asn(hostname)
290
+ ip = lookup_ip(hostname)
291
+
292
+ # Convert numeric IP address to an integer
293
+ ipnum = iptonum(ip)
294
+
295
+ if (@database_type != GEOIP_ASNUM_EDITION)
296
+ throw "Invalid GeoIP database type, can't look up ASN by IP"
894
297
  end
895
298
 
896
- private
897
-
898
- # Search the GeoIP database for the specified host, returning city info
899
- #
900
- # +hostname+ is a String holding the host's DNS name or numeric IP address
901
- # Return an array of fourteen elements:
902
- # * All elements from the country query (except GeoIP's country code, bah!)
903
- # * The region (state or territory) name
904
- # * The city name
905
- # * The postal code (zipcode)
906
- # * The latitude
907
- # * The longitude
908
- # * The dma_code and area_code, if available (REV1 City database)
909
- # * The timezone name, if known
910
- def read_city(pos, hostname = '', ip = '') #:nodoc:
911
- off = pos + (2*@record_length-1) * @databaseSegments[0]
912
- record = atomic_read(FULL_RECORD_LENGTH, off)
913
- return nil unless record && record.size == FULL_RECORD_LENGTH
914
-
915
- # The country code is the first byte:
916
- code = record[0]
917
- code = code.ord if code.respond_to?(:ord)
918
- record = record[1..-1]
919
- @iter_pos += 1 unless @iter_pos.nil?
920
-
921
- spl = record.split("\x00", 4)
922
- # Get the region:
923
- region = spl[0]
924
- @iter_pos += (region.size + 1) unless @iter_pos.nil?
925
-
926
- # Get the city:
927
- city = spl[1]
928
- @iter_pos += (city.size + 1) unless @iter_pos.nil?
929
- # set the correct encoding in ruby 1.9 compatible environments:
930
- city.force_encoding('iso-8859-1') if city.respond_to?(:force_encoding)
931
-
932
- # Get the postal code:
933
- postal_code = spl[2]
934
- @iter_pos += (postal_code.size + 1) unless @iter_pos.nil?
935
-
936
- record = spl[3]
937
- # Get the latitude/longitude:
938
- if(record && record[0,3]) then
939
- latitude = le_to_ui(record[0,3].unpack('C*')) / 10000.0 - 180
940
- record = record[3..-1]
941
- @iter_pos += 3 unless @iter_pos.nil?
942
- else
943
- latitude = ''
944
- end
945
- if(record && record[0,3]) then
946
- longitude = le_to_ui(record[0,3].unpack('C*')) / 10000.0 - 180
947
- record = record[3..-1]
948
- @iter_pos += 3 unless @iter_pos.nil?
949
- else
950
- longitude = ''
951
- end
299
+ pos = seek_record(ipnum)
300
+ off = pos + (2 * (@record_length - 1)) * @database_segments[0]
301
+
302
+ record = atomic_read(MAX_ASN_RECORD_LENGTH, off)
303
+ record = record.sub(/\000.*/n, '')
304
+
305
+ # AS####, Description
306
+ ASN.new($1, $2) if record =~ /^(AS\d+)\s(.*)$/
307
+ end
308
+
309
+ # Search a ISP GeoIP database for the specified host, returning the
310
+ # organization.
311
+ #
312
+ # +hostname+ is a String holding the host's DNS name or numeric
313
+ # IP address.
314
+ #
315
+ # Returns the organization associated with it.
316
+ #
317
+ alias_method(:organization, :isp) # Untested, according to Maxmind docs this should work
318
+
319
+ # Iterate through a GeoIP city database
320
+ def each
321
+ return enum_for unless block_given?
322
+
323
+ if (@database_type != GEOIP_CITY_EDITION_REV0 &&
324
+ @database_type != GEOIP_CITY_EDITION_REV1)
325
+ throw "Invalid GeoIP database type, can't iterate thru non-City database"
326
+ end
952
327
 
953
- if (record &&
954
- record[0,3] &&
955
- @databaseType == GEOIP_CITY_EDITION_REV1 &&
956
- CountryCode[code] == "US") # UNTESTED
957
- dmaarea_combo = le_to_ui(record[0,3].unpack('C*'))
958
- dma_code = dmaarea_combo / 1000;
959
- area_code = dmaarea_combo % 1000;
960
- @iter_pos += 3 unless @iter_pos.nil?
961
- else
962
- dma_code, area_code = nil, nil
963
- end
328
+ @iter_pos = @database_segments[0] + 1
329
+ num = 0
964
330
 
965
- [ hostname, # Requested hostname
966
- ip, # Ip address as dotted quad
967
- CountryCode[code], # ISO3166-1 code
968
- CountryCode3[code], # ISO3166-2 code
969
- CountryName[code], # Country name, per IS03166
970
- CountryContinent[code], # Continent code.
971
- region, # Region name
972
- city, # City name
973
- postal_code, # Postal code
974
- latitude,
975
- longitude,
976
- dma_code,
977
- area_code
978
- ] +
979
- [ TimeZone["#{CountryCode[code]}#{region}"] || TimeZone["#{CountryCode[code]}"] ]
331
+ until ((rec = read_city(@iter_pos)).nil?)
332
+ yield rec
333
+ print "#{num}: #{@iter_pos}\n" if((num += 1) % 1000 == 0)
980
334
  end
981
335
 
982
- def iptonum(ip) #:nodoc: Convert numeric IP address to integer
983
- if ip.kind_of?(String) &&
984
- ip =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/
985
- ip = be_to_ui(Regexp.last_match().to_a.slice(1..4))
336
+ @iter_pos = nil
337
+ return self
338
+ end
339
+
340
+ private
341
+
342
+ # Detects the type of the database.
343
+ def detect_database_type! # :nodoc:
344
+ @file.seek(-3, IO::SEEK_END)
345
+
346
+ 0.upto(STRUCTURE_INFO_MAX_SIZE - 1) do |i|
347
+ if @file.read(3).bytes.all? { |byte| byte == 255 }
348
+ @database_type = if @file.respond_to?(:getbyte)
349
+ @file.getbyte
350
+ else
351
+ @file.getc
352
+ end
353
+
354
+ @database_type -= 105 if @database_type >= 106
355
+
356
+ if (@database_type == GEOIP_REGION_EDITION_REV0)
357
+ # Region Edition, pre June 2003
358
+ @database_segments = [STATE_BEGIN_REV0]
359
+ elsif (@database_type == GEOIP_REGION_EDITION_REV1)
360
+ # Region Edition, post June 2003
361
+ @database_segments = [STATE_BEGIN_REV1]
362
+ elsif (@database_type == GEOIP_CITY_EDITION_REV0 ||
363
+ @database_type == GEOIP_CITY_EDITION_REV1 ||
364
+ @database_type == GEOIP_ORG_EDITION ||
365
+ @database_type == GEOIP_ISP_EDITION ||
366
+ @database_type == GEOIP_ASNUM_EDITION)
367
+
368
+ # City/Org Editions have two segments, read offset of second segment
369
+ @database_segments = [0]
370
+ sr = @file.read(3).unpack("C*")
371
+ @database_segments[0] += le_to_ui(sr)
372
+
373
+ if (@database_type == GEOIP_ORG_EDITION ||
374
+ @database_type == GEOIP_ISP_EDITION)
375
+ @record_length = 4
376
+ end
986
377
  end
987
- ip
378
+
379
+ break
380
+ else
381
+ @file.seek(-4, IO::SEEK_CUR)
382
+ end
988
383
  end
989
384
 
990
- def seek_record(ipnum) #:nodoc:
991
- # Binary search in the file.
992
- # Records are pairs of little-endian integers, each of @record_length.
993
- offset = 0
994
- mask = 0x80000000
995
- 31.downto(0) { |depth|
996
- off = @record_length * 2 * offset
997
- buf = atomic_read(@record_length * 2, off)
998
- buf.slice!(0...@record_length) if ((ipnum & mask) != 0)
999
- offset = le_to_ui(buf[0...@record_length].unpack("C*"))
1000
- return offset if (offset >= @databaseSegments[0])
1001
- mask >>= 1
1002
- }
385
+ if (@database_type == GEOIP_COUNTRY_EDITION ||
386
+ @database_type == GEOIP_PROXY_EDITION ||
387
+ @database_type == GEOIP_NETSPEED_EDITION)
388
+ @database_segments = [COUNTRY_BEGIN]
389
+ end
390
+ end
391
+
392
+ # Search the GeoIP database for the specified host, returning city info.
393
+ #
394
+ # +hostname+ is a String holding the host's DNS name or numeric
395
+ # IP address.
396
+ #
397
+ # Returns an array of fourteen elements:
398
+ # * All elements from the country query (except GeoIP's country code,
399
+ # bah!)
400
+ # * The region (state or territory) name
401
+ # * The city name
402
+ # * The postal code (zipcode)
403
+ # * The latitude
404
+ # * The longitude
405
+ # * The dma_code and area_code, if available (REV1 City database)
406
+ # * The timezone name, if known
407
+ #
408
+ def read_city(pos, hostname = '', ip = '') #:nodoc:
409
+ off = pos + (2*@record_length - 1) * @database_segments[0]
410
+ record = atomic_read(FULL_RECORD_LENGTH, off)
411
+
412
+ return unless (record && record.size == FULL_RECORD_LENGTH)
413
+
414
+ # The country code is the first byte:
415
+ code = record[0]
416
+ code = code.ord if code.respond_to?(:ord)
417
+ record = record[1..-1]
418
+ @iter_pos += 1 unless @iter_pos.nil?
419
+
420
+ spl = record.split("\x00", 4)
421
+ # Get the region:
422
+ region = spl[0]
423
+ @iter_pos += (region.size + 1) unless @iter_pos.nil?
424
+
425
+ # Get the city:
426
+ city = spl[1]
427
+ @iter_pos += (city.size + 1) unless @iter_pos.nil?
428
+ # set the correct encoding in ruby 1.9 compatible environments:
429
+ city.force_encoding('iso-8859-1') if city.respond_to?(:force_encoding)
430
+
431
+ # Get the postal code:
432
+ postal_code = spl[2]
433
+ @iter_pos += (postal_code.size + 1) unless @iter_pos.nil?
434
+
435
+ record = spl[3]
436
+
437
+ # Get the latitude/longitude:
438
+ if (record && record[0,3])
439
+ latitude = (le_to_ui(record[0,3].unpack('C*')) / 10000.0) - 180
440
+ record = record[3..-1]
441
+
442
+ @iter_pos += 3 unless @iter_pos.nil?
443
+ else
444
+ latitude = ''
1003
445
  end
1004
446
 
1005
- # Convert a big-endian array of numeric bytes to unsigned int
1006
- def be_to_ui(s) #:nodoc:
1007
- s.inject(0) { |m, o|
1008
- (m << 8) + o.to_i
1009
- }
447
+ if (record && record[0,3])
448
+ longitude = le_to_ui(record[0,3].unpack('C*')) / 10000.0 - 180
449
+ record = record[3..-1]
450
+
451
+ @iter_pos += 3 unless @iter_pos.nil?
452
+ else
453
+ longitude = ''
1010
454
  end
1011
455
 
1012
- # Same for little-endian
1013
- def le_to_ui(s) #:nodoc:
1014
- be_to_ui(s.reverse)
456
+ # UNTESTED
457
+ if (record &&
458
+ record[0,3] &&
459
+ @database_type == GEOIP_CITY_EDITION_REV1 &&
460
+ CountryCode[code] == "US")
461
+
462
+ dmaarea_combo = le_to_ui(record[0,3].unpack('C*'))
463
+ dma_code = (dmaarea_combo / 1000)
464
+ area_code = (dmaarea_combo % 1000)
465
+
466
+ @iter_pos += 3 unless @iter_pos.nil?
467
+ else
468
+ dma_code, area_code = nil, nil
1015
469
  end
1016
470
 
1017
- # reads +length+ bytes from +offset+ as atomically as possible
1018
- # if IO.pread is available, it'll use that (making it both multithread
1019
- # and multiprocess-safe).  Otherwise we'll use a mutex to synchronize
1020
- # access (only providing protection against multiple threads, but not
1021
- # file descriptors shared across multiple processes).
1022
- def atomic_read(length, offset) #:nodoc:
1023
- if @mutex
1024
- @mutex.synchronize {
1025
- @file.seek(offset)
1026
- @file.read(length)
1027
- }
1028
- else
1029
- IO.pread(@file.fileno, length, offset)
1030
- end
471
+ City.new(
472
+ hostname, # Requested hostname
473
+ ip, # Ip address as dotted quad
474
+ CountryCode[code], # ISO3166-1 code
475
+ CountryCode3[code], # ISO3166-2 code
476
+ CountryName[code], # Country name, per IS03166
477
+ CountryContinent[code], # Continent code.
478
+ region, # Region name
479
+ city, # City name
480
+ postal_code, # Postal code
481
+ latitude,
482
+ longitude,
483
+ dma_code,
484
+ area_code,
485
+ (TimeZone["#{CountryCode[code]}#{region}"] || TimeZone["#{CountryCode[code]}"])
486
+ )
487
+ end
488
+
489
+ def lookup_ip(ip_or_hostname) # :nodoc:
490
+ return ip_or_hostname unless (ip_or_hostname.kind_of?(String) && ip_or_hostname !~ /^[0-9.]*$/)
491
+
492
+ # Lookup IP address, we were given a name
493
+ ip = IPSocket.getaddress(ip_or_hostname)
494
+ ip = '0.0.0.0' if ip == '::1'
495
+ ip
496
+ end
497
+
498
+ # Convert numeric IP address to Integer.
499
+ def iptonum(ip) #:nodoc:
500
+ if (ip.kind_of?(String) &&
501
+ ip =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/)
502
+ ip = be_to_ui(Regexp.last_match().to_a.slice(1..4))
1031
503
  end
1032
504
 
1033
- module CountryAccessors #:nodoc:
1034
- ACCESSORS = [
1035
- :request, :ip, :country_code, :country_code2, :country_code3, :country_name, :continent_code
1036
- ]
1037
- ACCESSORS.each_with_index do |method, i|
1038
- define_method(method) { self[i] }
1039
- end
505
+ return ip
506
+ end
1040
507
 
1041
- def to_hash
1042
- ACCESSORS.inject({}) do |hash, key|
1043
- hash[key] = self.send(key)
1044
- hash
1045
- end
1046
- end
1047
- end
508
+ def seek_record(ipnum) #:nodoc:
509
+ # Binary search in the file.
510
+ # Records are pairs of little-endian integers, each of @record_length.
511
+ offset = 0
512
+ mask = 0x80000000
1048
513
 
1049
- module CityAccessors #:nodoc:
1050
- ACCESSORS = [
1051
- :request, :ip, :country_code2, :country_code3, :country_name, :continent_code,
1052
- :region_name, :city_name, :postal_code, :latitude, :longitude, :dma_code, :area_code, :timezone
1053
- ]
1054
- ACCESSORS.each_with_index do |method, i|
1055
- define_method(method) { self[i] }
1056
- end
514
+ 31.downto(0) do |depth|
515
+ off = (@record_length * 2 * offset)
516
+ buf = atomic_read(@record_length * 2, off)
1057
517
 
1058
- def to_hash
1059
- ACCESSORS.inject({}) do |hash, key|
1060
- hash[key] = self.send(key)
1061
- hash
1062
- end
1063
- end
1064
- end
518
+ buf.slice!(0...@record_length) if ((ipnum & mask) != 0)
519
+ offset = le_to_ui(buf[0...@record_length].unpack("C*"))
1065
520
 
1066
- module ASNAccessors #:nodoc:
1067
- def as_num
1068
- self[0]
521
+ if (offset >= @database_segments[0])
522
+ return offset
1069
523
  end
1070
524
 
1071
- def asn
1072
- self[1]
525
+ mask >>= 1
526
+ end
527
+ end
528
+
529
+ # Convert a big-endian array of numeric bytes to unsigned int.
530
+ #
531
+ # Returns the unsigned Integer.
532
+ #
533
+ def be_to_ui(s) #:nodoc:
534
+ i = 0
535
+
536
+ s.each { |b| i = ((i << 8) | (b.to_i & 0x0ff)) }
537
+ return i
538
+ end
539
+
540
+ # Same for little-endian
541
+ def le_to_ui(s) #:nodoc:
542
+ be_to_ui(s.reverse)
543
+ end
544
+
545
+ # reads +length+ bytes from +offset+ as atomically as possible
546
+ # if IO.pread is available, it'll use that (making it both multithread
547
+ # and multiprocess-safe). Otherwise we'll use a mutex to synchronize
548
+ # access (only providing protection against multiple threads, but not
549
+ # file descriptors shared across multiple processes).
550
+ def atomic_read(length, offset) #:nodoc:
551
+ if @mutex
552
+ @mutex.synchronize do
553
+ @file.seek(offset)
554
+ @file.read(length)
1073
555
  end
556
+ else
557
+ IO.pread(@file.fileno, length, offset)
1074
558
  end
1075
- end
559
+ end
1076
560
 
1077
- if $0 == __FILE__
1078
- data = '/usr/share/GeoIP/GeoIP.dat'
1079
- data = ARGV.shift if ARGV[0] =~ /\.dat\Z/
1080
- g = GeoIP.new data
1081
-
1082
- req = ([GeoIP::GEOIP_CITY_EDITION_REV1, GeoIP::GEOIP_CITY_EDITION_REV0].include?(g.databaseType)) ? :city : :country
1083
- ARGV.each { |a|
1084
- p g.send(req, a)
1085
- }
1086
561
  end
1087
-