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/{History.txt → History.rdoc} +0 -0
- data/Rakefile +50 -16
- data/bin/geoip +23 -0
- data/config/website.yml +2 -0
- data/data/geoip/country_code.yml +255 -0
- data/data/geoip/country_code3.yml +255 -0
- data/data/geoip/country_continent.yml +255 -0
- data/data/geoip/country_name.yml +255 -0
- data/data/geoip/time_zone.yml +677 -0
- data/geoip.gemspec +65 -0
- data/lib/geoip.rb +475 -1001
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +67 -0
- data/website/index.txt +79 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +138 -0
- data/website/template.rhtml +48 -0
- metadata +31 -50
- data/Manifest.txt +0 -7
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
44
|
-
require 'thread'
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
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
|
-
|
740
|
-
|
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
|
-
|
747
|
-
|
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
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
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
|
-
|
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
|
-
|
829
|
-
|
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
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
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
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
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
|
-
|
873
|
-
|
874
|
-
#
|
875
|
-
#
|
876
|
-
#
|
877
|
-
|
878
|
-
|
879
|
-
#
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
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
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
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
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
#
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
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
|
-
|
954
|
-
|
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
|
-
|
966
|
-
|
967
|
-
|
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
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
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
|
-
|
378
|
+
|
379
|
+
break
|
380
|
+
else
|
381
|
+
@file.seek(-4, IO::SEEK_CUR)
|
382
|
+
end
|
988
383
|
end
|
989
384
|
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
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
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
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
|
-
#
|
1013
|
-
|
1014
|
-
|
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
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
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
|
-
|
1034
|
-
|
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
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
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
|
-
|
1050
|
-
|
1051
|
-
|
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
|
-
|
1059
|
-
|
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
|
-
|
1067
|
-
|
1068
|
-
self[0]
|
521
|
+
if (offset >= @database_segments[0])
|
522
|
+
return offset
|
1069
523
|
end
|
1070
524
|
|
1071
|
-
|
1072
|
-
|
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
|
-
|