geoip 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
-