qqwry 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in qqwry.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Changli Gao
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # QQWry
2
+
3
+ A Ruby interface for QQWry IP database.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'qqwry'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install qqwry
18
+
19
+ ## Usage
20
+
21
+ At First, you need to get the latest QQWry IP database from http://www.cz88.net
22
+
23
+ ````ruby
24
+ require 'rubygems'
25
+ require 'bundler/setup'
26
+ require 'qqwry'
27
+
28
+ db = QQWry::Database.new('QQWry.Dat')
29
+ r = db.query('8.8.8.8')
30
+ puts "#{r.country} #{r.area}"
31
+ ````
32
+
33
+ ## Contributing
34
+
35
+ 1. Fork it
36
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
37
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
38
+ 4. Push to the branch (`git push origin my-new-feature`)
39
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ t.test_files = FileList['test/**/*.rb']
8
+ end
9
+
10
+ task :default => :build
data/bin/qqwry ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+ require 'qqwry'
6
+ require 'optparse'
7
+ require 'csv'
8
+
9
+ db_path = 'QQWry.Dat'
10
+
11
+ opt_parser = OptionParser.new do |opts|
12
+ opts.banner = "Usage: #$0 [options] (dump FN.csv)|(query IP)|version"
13
+
14
+ opts.separator('Version: ' + QQWry::VERSION)
15
+ opts.separator('Options:')
16
+
17
+ opts.on('-d', '--database PATH',
18
+ 'Use the database at PATH instead of QQWry.Dat') do |d|
19
+ db_path = d
20
+ end
21
+
22
+ opts.on('-h', '--help', 'Show this message') do |h|
23
+ puts opts
24
+ exit
25
+ end
26
+ end
27
+ opt_parser.parse!(ARGV)
28
+
29
+ db = QQWry::Database.new(db_path)
30
+ case ARGV[0]
31
+ when 'dump'
32
+ if ARGV.length != 2
33
+ puts opt_parser
34
+ exit(1)
35
+ end
36
+ CSV.open(ARGV[1], 'w') do |writer|
37
+ db.each do |r|
38
+ writer << [r.ip_str_start, r.ip_str_end, r.country, r.area]
39
+ end
40
+ end
41
+ when 'query'
42
+ if ARGV.length != 2
43
+ puts opt_parser
44
+ exit(1)
45
+ end
46
+ r = db.query(ARGV[1])
47
+ puts "country: #{r.country}"
48
+ puts "area: #{r.area}"
49
+ when 'version'
50
+ if ARGV.length != 1
51
+ puts opt_parser
52
+ exit(1)
53
+ end
54
+ puts db.version
55
+ else
56
+ puts opt_parser
57
+ exit(1)
58
+ end
data/lib/qqwry.rb ADDED
@@ -0,0 +1,4 @@
1
+
2
+ require 'qqwry/database'
3
+ require 'qqwry/record'
4
+ require 'qqwry/version'
@@ -0,0 +1,76 @@
1
+
2
+ require 'qqwry/record'
3
+
4
+ module QQWry
5
+ class Header < BinData::Record
6
+ SIZE = 8
7
+ endian :little
8
+ uint32 :index_offset_start
9
+ uint32 :index_offset_end
10
+ end
11
+
12
+ class Index < BinData::Record
13
+ SIZE = 7
14
+ endian :little
15
+ uint32 :ip_start
16
+ uint24 :record_offset
17
+ end
18
+
19
+ class Database
20
+ def initialize(file = 'QQWrt.Dat')
21
+ if file.respond_to?(:to_str)
22
+ @file = File.new(file.to_str)
23
+ else
24
+ @file = file
25
+ @file.seek(0)
26
+ end
27
+ @header = QQWry::Header.read(@file)
28
+ end
29
+
30
+ def each
31
+ offset = @header.index_offset_start
32
+ while offset <= @header.index_offset_end
33
+ @file.seek(offset)
34
+ index = QQWry::Index.read(@file)
35
+ record = QQWry::Record.new(index.ip_start, @file, index.record_offset)
36
+ yield record
37
+ offset += QQWry::Index::SIZE
38
+ end
39
+ end
40
+
41
+ def version
42
+ # The last record saves the version info
43
+ @file.seek(@header.index_offset_end)
44
+ index = QQWry::Index.read(@file)
45
+ r = QQWry::Record.new(0, @file, index.record_offset)
46
+ r.country + r.area
47
+ end
48
+
49
+ def query(ip)
50
+ if ip.respond_to?(:to_str)
51
+ ip = ip.to_str.split('.').collect{|i| i.to_i}.pack('C4').unpack('N')[0]
52
+ end
53
+ raise ArgumentError, "invalid IP" unless (0...(1 << 32)).include?(ip)
54
+ @file.seek(QQWry::Header::SIZE)
55
+ low = 0
56
+ # the end record is the version info
57
+ start = @header.index_offset_start
58
+ high = (@header.index_offset_end - start) / QQWry::Index::SIZE
59
+ while low <= high
60
+ middle = (low + high) / 2
61
+ @file.seek(start + middle * QQWry::Index::SIZE)
62
+ index = QQWry::Index.read(@file)
63
+ if ip > index.ip_start
64
+ low = middle + 1
65
+ elsif ip < index.ip_start
66
+ high = middle - 1
67
+ else
68
+ return QQWry::Record.new(index.ip_start, @file, index.record_offset)
69
+ end
70
+ end
71
+ @file.seek(start + high * QQWry::Index::SIZE)
72
+ index = QQWry::Index.read(@file)
73
+ QQWry::Record.new(index.ip_start, @file, index.record_offset)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,78 @@
1
+
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+ require 'bindata'
5
+ require 'iconv'
6
+
7
+ module QQWry
8
+ class Record
9
+ attr_reader :ip_start, :ip_end, :country, :area
10
+
11
+ def initialize(ip_start, file, offset)
12
+ @ip_start = ip_start
13
+ file.seek(offset)
14
+ @ip_end = BinData::Uint32le.read(file).to_i
15
+ case BinData::Uint8.read(file).to_i
16
+ when 0x1
17
+ offset = BinData::Uint24le.read(file).to_i
18
+ file.seek(offset)
19
+ case BinData::Uint8.read(file).to_i
20
+ when 0x2
21
+ file.seek(BinData::Uint24le.read(file).to_i)
22
+ @country = BinData::Stringz.read(file).to_s
23
+ file.seek(offset + 4)
24
+ else
25
+ file.seek(-1, IO::SEEK_CUR)
26
+ @country = BinData::Stringz.read(file).to_s
27
+ end
28
+ parse_area(file)
29
+ when 0x2
30
+ offset = BinData::Uint24le.read(file).to_i
31
+ parse_area(file)
32
+ file.seek(offset)
33
+ @country = BinData::Stringz.read(file).to_s
34
+ else
35
+ file.seek(-1, IO::SEEK_CUR)
36
+ @country = BinData::Stringz.read(file).to_s
37
+ parse_area(file)
38
+ end
39
+ if @country
40
+ @country = Iconv.conv('UTF-8', 'GBK', @country)
41
+ @country = nil if @country=~ /\s*CZ88\.NET\s*/
42
+ end
43
+ end
44
+
45
+ def ip_str_start
46
+ ip_str(@ip_start)
47
+ end
48
+
49
+ def ip_str_end
50
+ ip_str(@ip_end)
51
+ end
52
+
53
+ private
54
+
55
+ def ip_str(ip)
56
+ [ip].pack('N').unpack('C4').join('.')
57
+ end
58
+
59
+ def parse_area(file)
60
+ case BinData::Uint8.read(file).to_i
61
+ when 0x1, 0x2
62
+ offset = BinData::Uint24le.read(file).to_i
63
+ if offset > 0
64
+ file.seek(offset)
65
+ @area = BinData::Stringz.read(file).to_s
66
+ end
67
+ else
68
+ file.seek(-1, IO::SEEK_CUR)
69
+ @area = BinData::Stringz.read(file).to_s
70
+ end
71
+ if @area
72
+ @area = Iconv.conv('UTF-8', 'GBK', @area)
73
+ # CZ88.NET is an advertisement
74
+ @area = nil if @area =~ /\s*CZ88\.NET\s*/
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,4 @@
1
+
2
+ module QQWry
3
+ VERSION = '0.0.1'
4
+ end
data/qqwry.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'qqwry/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "qqwry"
8
+ gem.version = QQWry::VERSION
9
+ gem.authors = ["Changli Gao"]
10
+ gem.email = ["xiaosuo@gmail.com"]
11
+ gem.description = %q{A Ruby interface for QQWry IP database}
12
+ gem.summary = %q{A Ruby interface for QQWry IP database}
13
+ gem.homepage = 'https://github.com/xiaosuo/qqwry'
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+ gem.license = 'MIT'
19
+ gem.add_dependency 'bindata'
20
+ gem.add_development_dependency 'rake'
21
+ gem.add_development_dependency 'bundler'
22
+ gem.add_development_dependency 'bindata'
23
+ end
@@ -0,0 +1,82 @@
1
+
2
+ require 'test/unit'
3
+ require 'qqwry'
4
+
5
+ class TC_DataBase < Test::Unit::TestCase
6
+ # one record
7
+ def test_one_record
8
+ s = "\x12\x00\x00\x00\x12\x00\x00\x00" + # header
9
+ "\xff\xff\xff\xffc1\x00a1\x00" + # record
10
+ "\x00\x00\x00\x00\x08\x00\x00" # index
11
+ db = QQWry::Database.new(StringIO.new(s))
12
+ assert_equal('c1a1', db.version)
13
+ records = []
14
+ db.each{|r| records << r}
15
+ assert_equal(1, records.length)
16
+ assert_equal('c1', records[0].country)
17
+ assert_equal('a1', records[0].area)
18
+ ['0.0.0.0', 0x01020304, '255.255.255.255'].each do |ip|
19
+ r = db.query(ip)
20
+ assert_equal('c1', r.country)
21
+ assert_equal('a1', r.area)
22
+ end
23
+ assert_raise(ArgumentError){db.query(-1)}
24
+ assert_raise(ArgumentError){db.query(1 << 32)}
25
+ end
26
+
27
+ # two records
28
+ def test_two_record
29
+ s = "\x1c\x00\x00\x00\x23\x00\x00\x00" + # header
30
+ "\xff\xff\x00\x00c1\x00a1\x00" + # record 1
31
+ "\xff\xff\xff\xffc2\x00a2\x00" + # record 2
32
+ "\x00\x00\x00\x00\x08\x00\x00" + # index 1
33
+ "\x00\x00\x01\x00\x12\x00\x00" # index 2
34
+ db = QQWry::Database.new(StringIO.new(s))
35
+ assert_equal('c2a2', db.version)
36
+ records = []
37
+ db.each{|r| records << r}
38
+ assert_equal(2, records.length)
39
+ r = [{:c => 'c1', :a => 'a1'}, {:c => 'c2', :a => 'a2'}]
40
+ (0...2).each do |i|
41
+ assert_equal(r[i][:c], records[i].country)
42
+ assert_equal(r[i][:a], records[i].area)
43
+ end
44
+ s = {'0.0.0.0' => r[0], '0.0.254.254' => r[0], '0.0.255.255' => r[0],
45
+ '0.1.0.0' => r[1], '0.1.2.3' => r[1], '255.255.255.255' => r[1]}
46
+ s.each do |ip, r|
47
+ rec = db.query(ip)
48
+ assert_equal(r[:c], rec.country)
49
+ assert_equal(r[:a], rec.area)
50
+ end
51
+ end
52
+
53
+ # three records
54
+ def test_three_record
55
+ s = "\x26\x00\x00\x00\x34\x00\x00\x00" + # header
56
+ "\xff\xff\x00\x00c1\x00a1\x00" + # record 1
57
+ "\xff\xff\xff\x00c2\x00a2\x00" + # record 2
58
+ "\xff\xff\xff\xffc3\x00a3\x00" + # record 3
59
+ "\x00\x00\x00\x00\x08\x00\x00" + # index 1
60
+ "\x00\x00\x01\x00\x12\x00\x00" + # index 2
61
+ "\x00\x00\x00\x01\x1c\x00\x00" # index 3
62
+ db = QQWry::Database.new(StringIO.new(s))
63
+ assert_equal('c3a3', db.version)
64
+ records = []
65
+ db.each{|r| records << r}
66
+ assert_equal(3, records.length)
67
+ r = [{:c => 'c1', :a => 'a1'}, {:c => 'c2', :a => 'a2'},
68
+ {:c => 'c3', :a => 'a3'}]
69
+ (0...3).each do |i|
70
+ assert_equal(r[i][:c], records[i].country)
71
+ assert_equal(r[i][:a], records[i].area)
72
+ end
73
+ s = {'0.0.0.0' => r[0], '0.0.254.254' => r[0], '0.0.255.255' => r[0],
74
+ '0.1.0.0' => r[1], '0.1.8.254' => r[1], '0.255.255.255' => r[1],
75
+ '1.0.0.0' => r[2], '1.2.3.4' => r[2], '255.255.255.255' => r[2]}
76
+ s.each do |ip, r|
77
+ rec = db.query(ip)
78
+ assert_equal(r[:c], rec.country)
79
+ assert_equal(r[:a], rec.area)
80
+ end
81
+ end
82
+ end
data/test/tc_record.rb ADDED
@@ -0,0 +1,59 @@
1
+
2
+ require 'test/unit'
3
+ require 'qqwry'
4
+
5
+ class TC_Record < Test::Unit::TestCase
6
+ def teardown
7
+ io = StringIO.new(@s)
8
+ r = QQWry::Record.new(0x01020300, io, 3)
9
+ assert_equal(0x01020300, r.ip_start)
10
+ assert_equal(0x01020304, r.ip_end)
11
+ assert_equal('1.2.3.0', r.ip_str_start)
12
+ assert_equal('1.2.3.4', r.ip_str_end)
13
+ if @nil_country
14
+ assert_equal(nil, r.country)
15
+ else
16
+ assert_equal('country', r.country)
17
+ end
18
+ if @nil_area
19
+ assert_equal(nil, r.area)
20
+ else
21
+ assert_equal('area', r.area)
22
+ end
23
+ end
24
+
25
+ def test_simple
26
+ @s = "pad\x04\x03\x02\x01country\x00area\x00"
27
+ end
28
+
29
+ def test_mode1
30
+ @s = "pad\x04\x03\x02\x01\x01\x0b\x00\x00country\x00area\x00"
31
+ end
32
+
33
+ def test_mode2
34
+ @s = "pad\x04\x03\x02\x01\x02\x10\x00\x00area\x00country\x00"
35
+ end
36
+
37
+ def test_mix1
38
+ @s = "pad\x04\x03\x02\x01\x01\x0b\x00\x00\x02\x14\x00\x00area\x00country\x00"
39
+ end
40
+
41
+ def test_mix2
42
+ @s = "pad\x04\x03\x02\x01\x01\x0b\x00\x00\x02\x13\x00\x00\x01\x1b\x00\x00country\x00area\x00"
43
+ end
44
+
45
+ def test_mix2_2
46
+ @s = "pad\x04\x03\x02\x01\x01\x0b\x00\x00\x02\x13\x00\x00\x02\x1b\x00\x00country\x00area\x00"
47
+ end
48
+
49
+ def test_zero_offset
50
+ @s = "pad\x04\x03\x02\x01\x01\x0b\x00\x00\x02\x13\x00\x00\x02\x00\x00\x00country\x00area\x00"
51
+ @nil_area = true
52
+ end
53
+
54
+ def test_adv
55
+ @s = "pad\x04\x03\x02\x01\x01\x0b\x00\x00\x02\x13\x00\x00\x02\x1c\x00\x00CZ88.NET\x00 CZ88.NET \x00"
56
+ @nil_country = true
57
+ @nil_area = true
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qqwry
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Changli Gao
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-12-17 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bindata
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rake
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: bundler
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :development
61
+ version_requirements: *id003
62
+ - !ruby/object:Gem::Dependency
63
+ name: bindata
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ type: :development
75
+ version_requirements: *id004
76
+ description: A Ruby interface for QQWry IP database
77
+ email:
78
+ - xiaosuo@gmail.com
79
+ executables:
80
+ - qqwry
81
+ extensions: []
82
+
83
+ extra_rdoc_files: []
84
+
85
+ files:
86
+ - .gitignore
87
+ - Gemfile
88
+ - LICENSE.txt
89
+ - README.md
90
+ - Rakefile
91
+ - bin/qqwry
92
+ - lib/qqwry.rb
93
+ - lib/qqwry/database.rb
94
+ - lib/qqwry/record.rb
95
+ - lib/qqwry/version.rb
96
+ - qqwry.gemspec
97
+ - test/tc_database.rb
98
+ - test/tc_record.rb
99
+ homepage: https://github.com/xiaosuo/qqwry
100
+ licenses:
101
+ - MIT
102
+ post_install_message:
103
+ rdoc_options: []
104
+
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ hash: 3
122
+ segments:
123
+ - 0
124
+ version: "0"
125
+ requirements: []
126
+
127
+ rubyforge_project:
128
+ rubygems_version: 1.8.24
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: A Ruby interface for QQWry IP database
132
+ test_files:
133
+ - test/tc_database.rb
134
+ - test/tc_record.rb