rmoriz-geoip 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,12 @@
1
+ == 0.8.0 2008-08-29
2
+
3
+ * Added Mutex protection around file I/O for thread safety
4
+
5
+ == 0.7.0 2008-05-03
6
+
7
+ * Added Hez Ronningen's patch for using the ISP lookup data file
8
+
9
+ == 0.5.0 2007-10-24
10
+
11
+ * 1 major enhancement:
12
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,16 @@
1
+ This version Copyright (C) 2005-2007 Clifford Heath
2
+ Derived from the C version, Copyright (C) 2003 MaxMind LLC
3
+
4
+ This library is free software; you can redistribute it and/or
5
+ modify it under the terms of the GNU General Public
6
+ License as published by the Free Software Foundation; either
7
+ version 2.1 of the License, or (at your option) any later version.
8
+
9
+ This library is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ General Public License for more details.
13
+
14
+ You should have received a copy of the GNU General Public
15
+ License along with this library; if not, write to the Free Software
16
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
data/Manifest.txt ADDED
@@ -0,0 +1,24 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/geoip.rb
9
+ lib/geoip/version.rb
10
+ log/debug.log
11
+ script/destroy
12
+ script/generate
13
+ script/txt2html
14
+ setup.rb
15
+ tasks/deployment.rake
16
+ tasks/environment.rake
17
+ tasks/website.rake
18
+ test/test_geoip.rb
19
+ test/test_helper.rb
20
+ website/index.html
21
+ website/index.txt
22
+ website/javascripts/rounded_corners_lite.inc.js
23
+ website/stylesheets/screen.css
24
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,68 @@
1
+ = GeoIP
2
+
3
+ http://geoip.rubyforge.org/
4
+
5
+ == DESCRIPTION:
6
+
7
+ GeoIP searches a GeoIP database for a given host or IP address, and
8
+ returns information about the country where the IP address is allocated,
9
+ and the city, ISP and other information, if you have that database version.
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ This release applies a Mutex around file I/O operations, which should
14
+ prevent GeoIP blowing up under multi-threaded usage.
15
+
16
+ == SYNOPSIS:
17
+
18
+ require 'geoip'
19
+ GeoIP.new('GeoIP.dat').country("www.netscape.sk")
20
+ => ["www.netscape.sk", "217.67.16.35", 196, "SK", "SVK", "Slovakia", "EU"]
21
+
22
+ GeoIP.new('GeoIPASNum.dat').asn("www.fsb.ru")
23
+ => ["AS8342", "RTComm.RU Autonomous System"]
24
+
25
+ == REQUIREMENTS:
26
+
27
+ You need at least the free GeoIP.dat, for which the last known download
28
+ location is <http://www.maxmind.com/download/geoip/database/GeoIP.dat.gz>,
29
+ or the city database from <http://www.maxmind.com/app/geolitecity>.
30
+ The ASN database location is
31
+ <http://geolite.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz>.
32
+
33
+ This API requires the file to be decompressed for searching. Other versions
34
+ of this database are available for purchase which contain more detailed
35
+ information, but this information is not returned by this implementation.
36
+ See www.maxmind.com for more information.
37
+
38
+ == INSTALL:
39
+
40
+ sudo gem sources -a http://gems.github.com (you only have to do this once)
41
+ sudo gem install rmoriz-geoip
42
+
43
+ == LICENSE:
44
+
45
+ (The MIT License)
46
+
47
+ Copyright (c) 2008 FIX
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining
50
+ a copy of this software and associated documentation files (the
51
+ 'Software'), to deal in the Software without restriction, including
52
+ without limitation the rights to use, copy, modify, merge, publish,
53
+ distribute, sublicense, and/or sell copies of the Software, and to
54
+ permit persons to whom the Software is furnished to do so, subject to
55
+ the following conditions:
56
+
57
+ The above copyright notice and this permission notice shall be
58
+ included in all copies or substantial portions of the Software.
59
+
60
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
61
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
62
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
63
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
64
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
65
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
66
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
67
+
68
+ ==
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/config/hoe.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'geoip/version'
2
+
3
+ AUTHOR = 'Clifford Heath' # can also be an array of Authors
4
+ EMAIL = "cjheath@rubyforge.org"
5
+ DESCRIPTION = "description of gem"
6
+ GEM_NAME = 'geoip' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'geoip' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "unknown"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Geoip::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'geoip documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.author = AUTHOR
52
+ p.description = DESCRIPTION
53
+ p.email = EMAIL
54
+ p.summary = DESCRIPTION
55
+ p.url = HOMEPATH
56
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
57
+ p.test_globs = ["test/**/test_*.rb"]
58
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
59
+
60
+ # == Optional
61
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\\n\\n")
62
+ #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
63
+
64
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
65
+
66
+ end
67
+
68
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
69
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
70
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
71
+ hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'geoip'
data/lib/geoip.rb ADDED
@@ -0,0 +1,771 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ #
3
+ # Native Ruby reader for the GeoIP database
4
+ # Lookup the country where IP address is allocated
5
+ #
6
+ #= COPYRIGHT
7
+ # This version Copyright (C) 2005 Clifford Heath
8
+ # Derived from the C version, Copyright (C) 2003 MaxMind LLC
9
+ #
10
+ # This library is free software; you can redistribute it and/or
11
+ # modify it under the terms of the GNU General Public
12
+ # License as published by the Free Software Foundation; either
13
+ # version 2.1 of the License, or (at your option) any later version.
14
+ #
15
+ # This library is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18
+ # General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU General Public
21
+ # License along with this library; if not, write to the Free Software
22
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
+ #
24
+ #= SYNOPSIS
25
+ #
26
+ # require 'geoip'
27
+ # p GeoIP.new('/usr/share/GeoIP/GeoIP.dat').country("www.netscape.sk")
28
+ #
29
+ #= DESCRIPTION
30
+ #
31
+ # GeoIP searches a GeoIP database for a given host or IP address, and
32
+ # returns information about the country where the IP address is allocated.
33
+ #
34
+ #= PREREQUISITES
35
+ #
36
+ # You need at least the free GeoIP.dat, for which the last known download
37
+ # location is <http://www.maxmind.com/download/geoip/database/GeoIP.dat.gz>
38
+ # This API requires the file to be decompressed for searching. Other versions
39
+ # of this database are available for purchase which contain more detailed
40
+ # information, but this information is not returned by this implementation.
41
+ # See www.maxmind.com for more information.
42
+ #
43
+ #=end
44
+ require 'thread' # Needed for Mutex
45
+
46
+ require 'socket'
47
+
48
+ class GeoIP
49
+ private
50
+ CountryCode = [
51
+ "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN",
52
+ "AO","AQ","AR","AS","AT","AU","AW","AZ","BA","BB",
53
+ "BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO",
54
+ "BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD",
55
+ "CF","CG","CH","CI","CK","CL","CM","CN","CO","CR",
56
+ "CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO",
57
+ "DZ","EC","EE","EG","EH","ER","ES","ET","FI","FJ",
58
+ "FK","FM","FO","FR","FX","GA","GB","GD","GE","GF",
59
+ "GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT",
60
+ "GU","GW","GY","HK","HM","HN","HR","HT","HU","ID",
61
+ "IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO",
62
+ "JP","KE","KG","KH","KI","KM","KN","KP","KR","KW",
63
+ "KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT",
64
+ "LU","LV","LY","MA","MC","MD","MG","MH","MK","ML",
65
+ "MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV",
66
+ "MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI",
67
+ "NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF",
68
+ "PG","PH","PK","PL","PM","PN","PR","PS","PT","PW",
69
+ "PY","QA","RE","RO","RU","RW","SA","SB","SC","SD",
70
+ "SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO",
71
+ "SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH",
72
+ "TJ","TK","TM","TN","TO","TL","TR","TT","TV","TW",
73
+ "TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE",
74
+ "VG","VI","VN","VU","WF","WS","YE","YT","RS","ZA",
75
+ "ZM","ME","ZW","A1","A2","O1","AX","GG","IM","JE",
76
+ "BL","MF"
77
+ ]
78
+
79
+ CountryCode3 = [
80
+ "--","AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","ANT",
81
+ "AGO","AQ","ARG","ASM","AUT","AUS","ABW","AZE","BIH","BRB",
82
+ "BGD","BEL","BFA","BGR","BHR","BDI","BEN","BMU","BRN","BOL",
83
+ "BRA","BHS","BTN","BV","BWA","BLR","BLZ","CAN","CC","COD",
84
+ "CAF","COG","CHE","CIV","COK","CHL","CMR","CHN","COL","CRI",
85
+ "CUB","CPV","CX","CYP","CZE","DEU","DJI","DNK","DMA","DOM",
86
+ "DZA","ECU","EST","EGY","ESH","ERI","ESP","ETH","FIN","FJI",
87
+ "FLK","FSM","FRO","FRA","FX","GAB","GBR","GRD","GEO","GUF",
88
+ "GHA","GIB","GRL","GMB","GIN","GLP","GNQ","GRC","GS","GTM",
89
+ "GUM","GNB","GUY","HKG","HM","HND","HRV","HTI","HUN","IDN",
90
+ "IRL","ISR","IND","IO","IRQ","IRN","ISL","ITA","JAM","JOR",
91
+ "JPN","KEN","KGZ","KHM","KIR","COM","KNA","PRK","KOR","KWT",
92
+ "CYM","KAZ","LAO","LBN","LCA","LIE","LKA","LBR","LSO","LTU",
93
+ "LUX","LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD","MLI",
94
+ "MMR","MNG","MAC","MNP","MTQ","MRT","MSR","MLT","MUS","MDV",
95
+ "MWI","MEX","MYS","MOZ","NAM","NCL","NER","NFK","NGA","NIC",
96
+ "NLD","NOR","NPL","NRU","NIU","NZL","OMN","PAN","PER","PYF",
97
+ "PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT","PLW",
98
+ "PRY","QAT","REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN",
99
+ "SWE","SGP","SHN","SVN","SJM","SVK","SLE","SMR","SEN","SOM",
100
+ "SUR","STP","SLV","SYR","SWZ","TCA","TCD","TF","TGO","THA",
101
+ "TJK","TKL","TKM","TUN","TON","TLS","TUR","TTO","TUV","TWN",
102
+ "TZA","UKR","UGA","UM","USA","URY","UZB","VAT","VCT","VEN",
103
+ "VGB","VIR","VNM","VUT","WLF","WSM","YEM","YT","SRB","ZAF",
104
+ "ZMB","MNE","ZWE","A1","A2","O1","ALA","GGY","IMN","JEY",
105
+ "BLM","MAF"
106
+ ]
107
+
108
+ CountryName = [
109
+ "N/A",
110
+ "Asia/Pacific Region",
111
+ "Europe",
112
+ "Andorra",
113
+ "United Arab Emirates",
114
+ "Afghanistan",
115
+ "Antigua and Barbuda",
116
+ "Anguilla",
117
+ "Albania",
118
+ "Armenia",
119
+ "Netherlands Antilles",
120
+ "Angola",
121
+ "Antarctica",
122
+ "Argentina",
123
+ "American Samoa",
124
+ "Austria",
125
+ "Australia",
126
+ "Aruba",
127
+ "Azerbaijan",
128
+ "Bosnia and Herzegovina",
129
+ "Barbados",
130
+ "Bangladesh",
131
+ "Belgium",
132
+ "Burkina Faso",
133
+ "Bulgaria",
134
+ "Bahrain",
135
+ "Burundi",
136
+ "Benin",
137
+ "Bermuda",
138
+ "Brunei Darussalam",
139
+ "Bolivia",
140
+ "Brazil",
141
+ "Bahamas",
142
+ "Bhutan",
143
+ "Bouvet Island",
144
+ "Botswana",
145
+ "Belarus",
146
+ "Belize",
147
+ "Canada",
148
+ "Cocos (Keeling) Islands",
149
+ "Congo, the Democratic Republic of the",
150
+ "Central African Republic",
151
+ "Congo",
152
+ "Switzerland",
153
+ "Cote D'Ivoire",
154
+ "Cook Islands",
155
+ "Chile",
156
+ "Cameroon",
157
+ "China",
158
+ "Colombia",
159
+ "Costa Rica",
160
+ "Cuba",
161
+ "Cape Verde",
162
+ "Christmas Island",
163
+ "Cyprus",
164
+ "Czech Republic",
165
+ "Germany",
166
+ "Djibouti",
167
+ "Denmark",
168
+ "Dominica",
169
+ "Dominican Republic",
170
+ "Algeria",
171
+ "Ecuador",
172
+ "Estonia",
173
+ "Egypt",
174
+ "Western Sahara",
175
+ "Eritrea",
176
+ "Spain",
177
+ "Ethiopia",
178
+ "Finland",
179
+ "Fiji",
180
+ "Falkland Islands (Malvinas)",
181
+ "Micronesia, Federated States of",
182
+ "Faroe Islands",
183
+ "France",
184
+ "France, Metropolitan",
185
+ "Gabon",
186
+ "United Kingdom",
187
+ "Grenada",
188
+ "Georgia",
189
+ "French Guiana",
190
+ "Ghana",
191
+ "Gibraltar",
192
+ "Greenland",
193
+ "Gambia",
194
+ "Guinea",
195
+ "Guadeloupe",
196
+ "Equatorial Guinea",
197
+ "Greece",
198
+ "South Georgia and the South Sandwich Islands",
199
+ "Guatemala",
200
+ "Guam",
201
+ "Guinea-Bissau",
202
+ "Guyana",
203
+ "Hong Kong",
204
+ "Heard Island and McDonald Islands",
205
+ "Honduras",
206
+ "Croatia",
207
+ "Haiti",
208
+ "Hungary",
209
+ "Indonesia",
210
+ "Ireland",
211
+ "Israel",
212
+ "India",
213
+ "British Indian Ocean Territory",
214
+ "Iraq",
215
+ "Iran, Islamic Republic of",
216
+ "Iceland",
217
+ "Italy",
218
+ "Jamaica",
219
+ "Jordan",
220
+ "Japan",
221
+ "Kenya",
222
+ "Kyrgyzstan",
223
+ "Cambodia",
224
+ "Kiribati",
225
+ "Comoros",
226
+ "Saint Kitts and Nevis",
227
+ "Korea, Democratic People's Republic of",
228
+ "Korea, Republic of",
229
+ "Kuwait",
230
+ "Cayman Islands",
231
+ "Kazakhstan",
232
+ "Lao People's Democratic Republic",
233
+ "Lebanon",
234
+ "Saint Lucia",
235
+ "Liechtenstein",
236
+ "Sri Lanka",
237
+ "Liberia",
238
+ "Lesotho",
239
+ "Lithuania",
240
+ "Luxembourg",
241
+ "Latvia",
242
+ "Libyan Arab Jamahiriya",
243
+ "Morocco",
244
+ "Monaco",
245
+ "Moldova, Republic of",
246
+ "Madagascar",
247
+ "Marshall Islands",
248
+ "Macedonia, the Former Yugoslav Republic of",
249
+ "Mali",
250
+ "Myanmar",
251
+ "Mongolia",
252
+ "Macau",
253
+ "Northern Mariana Islands",
254
+ "Martinique",
255
+ "Mauritania",
256
+ "Montserrat",
257
+ "Malta",
258
+ "Mauritius",
259
+ "Maldives",
260
+ "Malawi",
261
+ "Mexico",
262
+ "Malaysia",
263
+ "Mozambique",
264
+ "Namibia",
265
+ "New Caledonia",
266
+ "Niger",
267
+ "Norfolk Island",
268
+ "Nigeria",
269
+ "Nicaragua",
270
+ "Netherlands",
271
+ "Norway",
272
+ "Nepal",
273
+ "Nauru",
274
+ "Niue",
275
+ "New Zealand",
276
+ "Oman",
277
+ "Panama",
278
+ "Peru",
279
+ "French Polynesia",
280
+ "Papua New Guinea",
281
+ "Philippines",
282
+ "Pakistan",
283
+ "Poland",
284
+ "Saint Pierre and Miquelon",
285
+ "Pitcairn",
286
+ "Puerto Rico",
287
+ "Palestinian Territory, Occupied",
288
+ "Portugal",
289
+ "Palau",
290
+ "Paraguay",
291
+ "Qatar",
292
+ "Reunion",
293
+ "Romania",
294
+ "Russian Federation",
295
+ "Rwanda",
296
+ "Saudi Arabia",
297
+ "Solomon Islands",
298
+ "Seychelles",
299
+ "Sudan",
300
+ "Sweden",
301
+ "Singapore",
302
+ "Saint Helena",
303
+ "Slovenia",
304
+ "Svalbard and Jan Mayen",
305
+ "Slovakia",
306
+ "Sierra Leone",
307
+ "San Marino",
308
+ "Senegal",
309
+ "Somalia",
310
+ "Suriname",
311
+ "Sao Tome and Principe",
312
+ "El Salvador",
313
+ "Syrian Arab Republic",
314
+ "Swaziland",
315
+ "Turks and Caicos Islands",
316
+ "Chad",
317
+ "French Southern Territories",
318
+ "Togo",
319
+ "Thailand",
320
+ "Tajikistan",
321
+ "Tokelau",
322
+ "Turkmenistan",
323
+ "Tunisia",
324
+ "Tonga",
325
+ "Timor-Leste",
326
+ "Turkey",
327
+ "Trinidad and Tobago",
328
+ "Tuvalu",
329
+ "Taiwan, Province of China",
330
+ "Tanzania, United Republic of",
331
+ "Ukraine",
332
+ "Uganda",
333
+ "United States Minor Outlying Islands",
334
+ "United States",
335
+ "Uruguay",
336
+ "Uzbekistan",
337
+ "Holy See (Vatican City State)",
338
+ "Saint Vincent and the Grenadines",
339
+ "Venezuela",
340
+ "Virgin Islands, British",
341
+ "Virgin Islands, U.S.",
342
+ "Viet Nam",
343
+ "Vanuatu",
344
+ "Wallis and Futuna",
345
+ "Samoa",
346
+ "Yemen",
347
+ "Mayotte",
348
+ "Serbia",
349
+ "South Africa",
350
+ "Zambia",
351
+ "Montenegro",
352
+ "Zimbabwe",
353
+ "Anonymous Proxy",
354
+ "Satellite Provider",
355
+ "Other",
356
+ "Aland Islands",
357
+ "Guernsey",
358
+ "Isle of Man",
359
+ "Jersey",
360
+ "Saint Barthelemy",
361
+ "Saint Martin"
362
+ ]
363
+
364
+ CountryContinent = [
365
+ "--","AS","EU","EU","AS","AS","SA","SA","EU","AS","SA",
366
+ "AF","AN","SA","OC","EU","OC","SA","AS","EU","SA",
367
+ "AS","EU","AF","EU","AS","AF","AF","SA","AS","SA",
368
+ "SA","SA","AS","AF","AF","EU","SA","NA","AS","AF",
369
+ "AF","AF","EU","AF","OC","SA","AF","AS","SA","SA",
370
+ "SA","AF","AS","AS","EU","EU","AF","EU","SA","SA",
371
+ "AF","SA","EU","AF","AF","AF","EU","AF","EU","OC",
372
+ "SA","OC","EU","EU","EU","AF","EU","SA","AS","SA",
373
+ "AF","EU","SA","AF","AF","SA","AF","EU","SA","SA",
374
+ "OC","AF","SA","AS","AF","SA","EU","SA","EU","AS",
375
+ "EU","AS","AS","AS","AS","AS","EU","EU","SA","AS",
376
+ "AS","AF","AS","AS","OC","AF","SA","AS","AS","AS",
377
+ "SA","AS","AS","AS","SA","EU","AS","AF","AF","EU",
378
+ "EU","EU","AF","AF","EU","EU","AF","OC","EU","AF",
379
+ "AS","AS","AS","OC","SA","AF","SA","EU","AF","AS",
380
+ "AF","NA","AS","AF","AF","OC","AF","OC","AF","SA",
381
+ "EU","EU","AS","OC","OC","OC","AS","SA","SA","OC",
382
+ "OC","AS","AS","EU","SA","OC","SA","AS","EU","OC",
383
+ "SA","AS","AF","EU","AS","AF","AS","OC","AF","AF",
384
+ "EU","AS","AF","EU","EU","EU","AF","EU","AF","AF",
385
+ "SA","AF","SA","AS","AF","SA","AF","AF","AF","AS",
386
+ "AS","OC","AS","AF","OC","AS","AS","SA","OC","AS",
387
+ "AF","EU","AF","OC","NA","SA","AS","EU","SA","SA",
388
+ "SA","SA","AS","OC","OC","OC","AS","AF","EU","AF",
389
+ "AF","EU","AF","--","--","--","EU","EU","EU","EU",
390
+ "SA","SA"
391
+ ]
392
+
393
+ public
394
+ # Edition enumeration:
395
+ (GEOIP_COUNTRY_EDITION,
396
+ GEOIP_CITY_EDITION_REV1,
397
+ GEOIP_REGION_EDITION_REV1,
398
+ GEOIP_ISP_EDITION,
399
+ GEOIP_ORG_EDITION,
400
+ GEOIP_CITY_EDITION_REV0,
401
+ GEOIP_REGION_EDITION_REV0,
402
+ GEOIP_PROXY_EDITION,
403
+ GEOIP_ASNUM_EDITION,
404
+ GEOIP_NETSPEED_EDITION,
405
+ ) = *1..10
406
+
407
+ private
408
+ COUNTRY_BEGIN = 16776960
409
+ STATE_BEGIN_REV0 = 16700000
410
+ STATE_BEGIN_REV1 = 16000000
411
+ STRUCTURE_INFO_MAX_SIZE = 20
412
+ DATABASE_INFO_MAX_SIZE = 100
413
+ MAX_ORG_RECORD_LENGTH = 300
414
+ MAX_ASN_RECORD_LENGTH = 300 # unverified
415
+ US_OFFSET = 1
416
+ CANADA_OFFSET = 677
417
+ WORLD_OFFSET = 1353
418
+ FIPS_RANGE = 360
419
+ FULL_RECORD_LENGTH = 50
420
+
421
+ STANDARD_RECORD_LENGTH = 3
422
+ SEGMENT_RECORD_LENGTH = 3
423
+
424
+ public
425
+ attr_reader :databaseType
426
+
427
+ # Open the GeoIP database and determine the file format version
428
+ #
429
+ # +filename+ is a String holding the path to the GeoIP.dat file
430
+ # +options+ is an integer holding caching flags (unimplemented)
431
+ def initialize(filename, flags = 0)
432
+ @mutex = Mutex.new
433
+ @flags = flags
434
+ @databaseType = GEOIP_COUNTRY_EDITION
435
+ @record_length = STANDARD_RECORD_LENGTH
436
+ @file = File.open(filename, 'rb')
437
+ @file.seek(-3, IO::SEEK_END)
438
+ 0.upto(STRUCTURE_INFO_MAX_SIZE-1) { |i|
439
+ if @file.read(3) == "\xFF\xFF\xFF"
440
+ @databaseType = @file.getc
441
+ @databaseType -= 105 if @databaseType >= 106
442
+
443
+ if (@databaseType == GEOIP_REGION_EDITION_REV0)
444
+ # Region Edition, pre June 2003
445
+ @databaseSegments = [ STATE_BEGIN_REV0 ]
446
+ elsif (@databaseType == GEOIP_REGION_EDITION_REV1)
447
+ # Region Edition, post June 2003
448
+ @databaseSegments = [ STATE_BEGIN_REV1 ]
449
+ elsif (@databaseType == GEOIP_CITY_EDITION_REV0 ||
450
+ @databaseType == GEOIP_CITY_EDITION_REV1 ||
451
+ @databaseType == GEOIP_ORG_EDITION ||
452
+ @databaseType == GEOIP_ISP_EDITION ||
453
+ @databaseType == GEOIP_ASNUM_EDITION)
454
+ # City/Org Editions have two segments, read offset of second segment
455
+ @databaseSegments = [ 0 ]
456
+ sr = @file.read(3).unpack("C*")
457
+ @databaseSegments[0] += le_to_ui(sr)
458
+
459
+ if (@databaseType == GEOIP_ORG_EDITION ||
460
+ @databaseType == GEOIP_ISP_EDITION)
461
+ @record_length = 4
462
+ end
463
+ end
464
+ break
465
+
466
+ else
467
+ @file.seek(-4, IO::SEEK_CUR)
468
+ end
469
+ }
470
+ if (@databaseType == GEOIP_COUNTRY_EDITION ||
471
+ @databaseType == GEOIP_PROXY_EDITION ||
472
+ @databaseType == GEOIP_NETSPEED_EDITION)
473
+ @databaseSegments = [ COUNTRY_BEGIN ]
474
+ end
475
+ end
476
+
477
+ # Search the GeoIP database for the specified host, returning country info
478
+ #
479
+ # +hostname+ is a String holding the host's DNS name or numeric IP address.
480
+ # Return an array of seven elements:
481
+ # * The host or IP address string as requested
482
+ # * The IP address string after looking up the host
483
+ # * The GeoIP country-ID as an integer
484
+ # * The ISO3166-1 two-character country code
485
+ # * The ISO3166-2 three-character country code
486
+ # * The ISO3166 English-language name of the country
487
+ # * The two-character continent code
488
+ #
489
+ def country(hostname)
490
+ if (@databaseType == GEOIP_CITY_EDITION_REV0 ||
491
+ @databaseType == GEOIP_CITY_EDITION_REV1)
492
+ return city(hostname)
493
+ end
494
+
495
+ ip = hostname
496
+ if ip.kind_of?(String) && ip !~ /^[0-9.]*$/
497
+ # Lookup IP address, we were given a name
498
+ ip = IPSocket.getaddress(hostname)
499
+ end
500
+
501
+ # Convert numeric IP address to an integer
502
+ ipnum = iptonum(ip)
503
+ if (@databaseType != GEOIP_COUNTRY_EDITION &&
504
+ @databaseType != GEOIP_PROXY_EDITION &&
505
+ @databaseType != GEOIP_NETSPEED_EDITION)
506
+ throw "Invalid GeoIP database type, can't look up Country by IP"
507
+ end
508
+ code = seek_record(ipnum) - COUNTRY_BEGIN;
509
+ [ hostname, # Requested hostname
510
+ ip, # Ip address as dotted quad
511
+ code, # GeoIP's country code
512
+ CountryCode[code], # ISO3166-1 code
513
+ CountryCode3[code], # ISO3166-2 code
514
+ CountryName[code], # Country name, per IS03166
515
+ CountryContinent[code] ] # Continent code.
516
+ end
517
+
518
+ # Search the GeoIP database for the specified host, returning city info
519
+ #
520
+ # +hostname+ is a String holding the host's DNS name or numeric IP address
521
+ # Return an array of twelve or fourteen elements:
522
+ # * All elements from the country query
523
+ # * The region (state or territory) name
524
+ # * The city name
525
+ # * The postal code (zipcode)
526
+ # * The latitude
527
+ # * The longitude
528
+ # * The dma_code and area_code, if available (REV1 City database)
529
+ private
530
+
531
+ def read_city(pos, hostname = '', ip = '')
532
+ record = ""
533
+ @mutex.synchronize {
534
+ @file.seek(pos + (2*@record_length-1) * @databaseSegments[0])
535
+ return nil unless record = @file.read(FULL_RECORD_LENGTH)
536
+ }
537
+
538
+ # The country code is the first byte:
539
+ code = record[0]
540
+ record = record[1..-1]
541
+ @iter_pos += 1 unless @iter_pos.nil?
542
+
543
+ spl = record.split("\x00", 4)
544
+ # Get the region:
545
+ region = spl[0]
546
+ @iter_pos += (region.size + 1) unless @iter_pos.nil?
547
+
548
+ # Get the city:
549
+ city = spl[1]
550
+ @iter_pos += (city.size + 1) unless @iter_pos.nil?
551
+
552
+ # Get the postal code:
553
+ postal_code = spl[2]
554
+ @iter_pos += (postal_code.size + 1) unless @iter_pos.nil?
555
+
556
+ record = spl[3]
557
+ # Get the latitude/longitude:
558
+ if(record && record[0,3]) then
559
+ latitude = le_to_ui(record[0,3].unpack('C*')) / 10000.0 - 180
560
+ record = record[3..-1]
561
+ @iter_pos += 3 unless @iter_pos.nil?
562
+ else
563
+ latitude = ''
564
+ end
565
+ if(record && record[0,3]) then
566
+ longitude = le_to_ui(record[0,3].unpack('C*')) / 10000.0 - 180
567
+ record = record[3..-1]
568
+ @iter_pos += 3 unless @iter_pos.nil?
569
+ else
570
+ longitude = ''
571
+ end
572
+
573
+ us_area_codes = []
574
+ if (record &&
575
+ record[0,3] &&
576
+ @databaseType == GEOIP_CITY_EDITION_REV1 &&
577
+ CountryCode[code] == "US") # UNTESTED
578
+ dmaarea_combo = le_to_ui(record[0,3].unpack('C*'))
579
+ dma_code = dmaarea_combo / 1000;
580
+ area_code = dmaarea_combo % 1000;
581
+ us_area_codes = [ dma_code, area_code ]
582
+ @iter_pos += 3 unless @iter_pos.nil?
583
+ end
584
+
585
+ [ hostname, # Requested hostname
586
+ ip, # Ip address as dotted quad
587
+ CountryCode[code], # ISO3166-1 code
588
+ CountryCode3[code], # ISO3166-2 code
589
+ CountryName[code], # Country name, per IS03166
590
+ CountryContinent[code], # Continent code.
591
+ region, # Region name
592
+ city, # City name
593
+ postal_code, # Postal code
594
+ latitude,
595
+ longitude,
596
+ ] + us_area_codes
597
+ end
598
+
599
+ public
600
+
601
+ # Search the GeoIP database for the specified host, returning city info.
602
+ #
603
+ # +hostname+ is a String holding the host's DNS name or numeric IP address.
604
+ # Return an array of twelve or fourteen elements:
605
+ # * The host or IP address string as requested
606
+ # * The IP address string after looking up the host
607
+ # * The GeoIP country-ID as an integer
608
+ # * The ISO3166-1 two-character country code
609
+ # * The ISO3166-2 three-character country code
610
+ # * The ISO3166 English-language name of the country
611
+ # * The two-character continent code
612
+ # * The region name
613
+ # * The city name
614
+ # * The postal code
615
+ # * The latitude
616
+ # * The longitude
617
+ # * The USA dma_code and area_code, if available (REV1 City database)
618
+ #
619
+ def city(hostname)
620
+ ip = hostname
621
+ if ip.kind_of?(String) && ip !~ /^[0-9.]*$/
622
+ # Lookup IP address, we were given a name
623
+ ip = IPSocket.getaddress(hostname)
624
+ end
625
+
626
+ # Convert numeric IP address to an integer
627
+ ipnum = iptonum(ip)
628
+ if (@databaseType != GEOIP_CITY_EDITION_REV0 &&
629
+ @databaseType != GEOIP_CITY_EDITION_REV1)
630
+ throw "Invalid GeoIP database type, can't look up City by IP"
631
+ end
632
+ pos = seek_record(ipnum);
633
+ read_city(pos, hostname, ip)
634
+ end
635
+
636
+ # Search a ISP GeoIP database for the specified host, returning the ISP
637
+ #
638
+ # +hostname+ is a String holding the host's DNS name or numeric IP address.
639
+ # Return the ISP name
640
+ #
641
+ def isp(hostname)
642
+ ip = hostname
643
+ if ip.kind_of?(String) && ip !~ /^[0-9.]*$/
644
+ # Lookup IP address, we were given a name
645
+ ip = IPSocket.getaddress(hostname)
646
+ end
647
+
648
+ # Convert numeric IP address to an integer
649
+ ipnum = iptonum(ip)
650
+ if @databaseType != GEOIP_ISP_EDITION
651
+ throw "Invalid GeoIP database type, can't look up Organization/ISP by IP"
652
+ end
653
+ pos = seek_record(ipnum);
654
+ record = ""
655
+ @mutex.synchronize {
656
+ @file.seek(pos + (2*@record_length-1) * @databaseSegments[0])
657
+ record = @file.read(MAX_ORG_RECORD_LENGTH)
658
+ }
659
+ record = record.sub(/\000.*/, '')
660
+ record
661
+ end
662
+
663
+ # Search a ASN GeoIP database for the specified host, returning the AS number + description
664
+ #
665
+ # +hostname+ is a String holding the host's DNS name or numeric IP address.
666
+ # Return the AS number + description
667
+ #
668
+ # Source:
669
+ # http://geolite.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz
670
+ #
671
+ def asn(hostname)
672
+ ip = hostname
673
+ if ip.kind_of?(String) && ip !~ /^[0-9.]*$/
674
+ # Lookup IP address, we were given a name
675
+ ip = IPSocket.getaddress(hostname)
676
+ end
677
+
678
+ # Convert numeric IP address to an integer
679
+ ipnum = iptonum(ip)
680
+ if (@databaseType != GEOIP_ASNUM_EDITION)
681
+ throw "Invalid GeoIP database type, can't look up ASN by IP"
682
+ end
683
+ pos = seek_record(ipnum);
684
+ record = ""
685
+ @mutex.synchronize {
686
+ @file.seek(pos + (2*@record_length-1) * @databaseSegments[0])
687
+ record = @file.read(MAX_ASN_RECORD_LENGTH)
688
+ }
689
+ record = record.sub(/\000.*/, '')
690
+
691
+ if record =~ /^(AS\d+)\s(.*)$/
692
+ # AS####, Description
693
+ return [$1, $2]
694
+ end
695
+ end
696
+
697
+ # Search a ISP GeoIP database for the specified host, returning the organization
698
+ #
699
+ # +hostname+ is a String holding the host's DNS name or numeric IP address.
700
+ # Return the organization associated with it
701
+ #
702
+ alias_method(:organization, :isp) # Untested, according to Maxmind docs this should work
703
+
704
+ # Iterate through a GeoIP city database
705
+ def each
706
+ if (@databaseType != GEOIP_CITY_EDITION_REV0 &&
707
+ @databaseType != GEOIP_CITY_EDITION_REV1)
708
+ throw "Invalid GeoIP database type, can't iterate thru non-City database"
709
+ end
710
+
711
+ @iter_pos = @databaseSegments[0] + 1
712
+ num = 0
713
+ until((rec = read_city(@iter_pos)).nil?)
714
+ yield(rec)
715
+ print "#{num}: #{@iter_pos}\n" if((num += 1) % 1000 == 0)
716
+ end
717
+ @iter_pos = nil
718
+ self
719
+ end
720
+
721
+ private
722
+
723
+ def iptonum(ip) # Convert numeric IP address to integer
724
+ if ip.kind_of?(String) &&
725
+ ip =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/
726
+ ip = be_to_ui(Regexp.last_match().to_a.slice(1..4))
727
+ end
728
+ ip
729
+ end
730
+
731
+ def seek_record(ipnum)
732
+ # Binary search in the file.
733
+ # Records are pairs of little-endian integers, each of @record_length.
734
+ offset = 0
735
+ mask = 0x80000000
736
+ 31.downto(0) { |depth|
737
+ buf = @mutex.synchronize {
738
+ @file.seek(@record_length * 2 * offset);
739
+ @file.read(@record_length * 2);
740
+ }
741
+ buf.slice!(0...@record_length) if ((ipnum & mask) != 0)
742
+ offset = le_to_ui(buf[0...@record_length].unpack("C*"))
743
+ return offset if (offset >= @databaseSegments[0])
744
+ mask >>= 1
745
+ }
746
+ end
747
+
748
+ # Convert a big-endian array of numeric bytes to unsigned int
749
+ def be_to_ui(s)
750
+ s.inject(0) { |m, o|
751
+ (m << 8) + o.to_i
752
+ }
753
+ end
754
+
755
+ # Same for little-endian
756
+ def le_to_ui(s)
757
+ be_to_ui(s.reverse)
758
+ end
759
+ end
760
+
761
+ if $0 == __FILE__
762
+ data = '/usr/share/GeoIP/GeoIP.dat'
763
+ data = ARGV.shift if ARGV[0] =~ /\.dat\Z/
764
+ g = GeoIP.new data
765
+
766
+ req = ([GeoIP::GEOIP_CITY_EDITION_REV1, GeoIP::GEOIP_CITY_EDITION_REV0].include?(g.databaseType)) ? :city : :country
767
+ ARGV.each { |a|
768
+ p g.send(req, a)
769
+ }
770
+ end
771
+