IPinfo 1.0.1 → 2.5.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.
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'resolv'
4
+ require 'ipaddr'
5
+
6
+ class IPinfo::IpAddressMatcher
7
+ attr_accessor :required_address
8
+
9
+ @@bogon_list = [
10
+ "0.0.0.0/8",
11
+ "10.0.0.0/8",
12
+ "100.64.0.0/10",
13
+ "127.0.0.0/8",
14
+ "169.254.0.0/16",
15
+ "172.16.0.0/12",
16
+ "192.0.0.0/24",
17
+ "192.0.2.0/24",
18
+ "192.168.0.0/16",
19
+ "198.18.0.0/15",
20
+ "198.51.100.0/24",
21
+ "203.0.113.0/24",
22
+ "224.0.0.0/4",
23
+ "240.0.0.0/4",
24
+ "255.255.255.255/32",
25
+ "::/128",
26
+ "::1/128",
27
+ "::ffff:0:0/96",
28
+ "::/96",
29
+ "100::/64",
30
+ "2001:10::/28",
31
+ "2001:db8::/32",
32
+ "fc00::/7",
33
+ "fe80::/10",
34
+ "fec0::/10",
35
+ "ff00::/8",
36
+ "2002::/24",
37
+ "2002:a00::/24",
38
+ "2002:7f00::/24",
39
+ "2002:a9fe::/32",
40
+ "2002:ac10::/28",
41
+ "2002:c000::/40",
42
+ "2002:c000:200::/40",
43
+ "2002:c0a8::/32",
44
+ "2002:c612::/31",
45
+ "2002:c633:6400::/40",
46
+ "2002:cb00:7100::/40",
47
+ "2002:e000::/20",
48
+ "2002:f000::/20",
49
+ "2002:ffff:ffff::/48",
50
+ "2001::/40",
51
+ "2001:0:a00::/40",
52
+ "2001:0:7f00::/40",
53
+ "2001:0:a9fe::/48",
54
+ "2001:0:ac10::/44",
55
+ "2001:0:c000::/56",
56
+ "2001:0:c000:200::/56",
57
+ "2001:0:c0a8::/48",
58
+ "2001:0:c612::/47",
59
+ "2001:0:c633:6400::/56",
60
+ "2001:0:cb00:7100::/56",
61
+ "2001:0:e000::/36",
62
+ "2001:0:f000::/36",
63
+ "2001:0:ffff:ffff::/64"
64
+ ]
65
+
66
+ def initialize(ip)
67
+ if !ip.index('/').nil? and ip.index('/') > 0
68
+ addr_mask = ip.split('/')
69
+ ip = addr_mask[0]
70
+ end
71
+
72
+ @required_address = parseAddress(ip)
73
+ end
74
+
75
+ def parseAddress(ip)
76
+ begin
77
+ Resolv.getaddress ip
78
+ rescue Resolv::ResolvError => e
79
+ pp e
80
+ end
81
+ end
82
+
83
+ def matches
84
+ for bogon in @@bogon_list do
85
+ if bogon.include? required_address
86
+ return true
87
+ end
88
+ end
89
+
90
+ false
91
+ end
92
+ end
data/lib/ipinfo/mod.rb CHANGED
@@ -2,3 +2,6 @@
2
2
 
3
3
  module IPinfo
4
4
  end
5
+
6
+ module IPinfoLite
7
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IPinfo
4
- VERSION = '1.0.1'
4
+ VERSION = '2.5.0'
5
5
  end
data/lib/ipinfo.rb CHANGED
@@ -8,14 +8,18 @@ require 'ipinfo/errors'
8
8
  require 'ipinfo/response'
9
9
  require 'ipinfo/version'
10
10
  require 'json'
11
+ require_relative 'ipinfo/ipAddressMatcher'
12
+ require_relative 'ipinfo/countriesData'
11
13
 
12
14
  module IPinfo
15
+ include CountriesData
13
16
  DEFAULT_CACHE_MAXSIZE = 4096
14
17
  DEFAULT_CACHE_TTL = 60 * 60 * 24
15
- DEFAULT_COUNTRY_FILE = File.join(File.dirname(__FILE__),
16
- 'ipinfo/countries.json')
17
18
  RATE_LIMIT_MESSAGE = 'To increase your limits, please review our ' \
18
19
  'paid plans at https://ipinfo.io/pricing'
20
+ # Base URL to get country flag image link.
21
+ # "PK" -> "https://cdn.ipinfo.io/static/images/countries-flags/PK.svg"
22
+ COUNTRY_FLAGS_URL = "https://cdn.ipinfo.io/static/images/countries-flags/"
19
23
 
20
24
  class << self
21
25
  def create(access_token = nil, settings = {})
@@ -30,43 +34,120 @@ class IPinfo::IPinfo
30
34
 
31
35
  def initialize(access_token = nil, settings = {})
32
36
  @access_token = access_token
33
- @httpc = prepare_http_client(settings.fetch('http_client', nil))
37
+ prepare_http_client(settings.fetch('http_client', nil))
34
38
 
35
39
  maxsize = settings.fetch('maxsize', DEFAULT_CACHE_MAXSIZE)
36
40
  ttl = settings.fetch('ttl', DEFAULT_CACHE_TTL)
37
41
  @cache = settings.fetch('cache', DefaultCache.new(ttl, maxsize))
38
- @countries = prepare_countries(settings.fetch('countries',
39
- DEFAULT_COUNTRY_FILE))
42
+ @countries = settings.fetch('countries', DEFAULT_COUNTRY_LIST)
43
+ @eu_countries = settings.fetch('eu_countries', DEFAULT_EU_COUNTRIES_LIST)
44
+ @countries_flags = settings.fetch('countries_flags', DEFAULT_COUNTRIES_FLAG_LIST)
45
+ @countries_currencies = settings.fetch('countries_currencies', DEFAULT_COUNTRIES_CURRENCIES_LIST)
46
+ @continents = settings.fetch('continents', DEFAULT_CONTINENT_LIST)
40
47
  end
41
48
 
42
49
  def details(ip_address = nil)
43
- details = request_details(ip_address)
44
- if details.key? :country
45
- details[:country_name] =
46
- @countries.fetch(details.fetch(:country), nil)
50
+ details_base(ip_address, :v4)
51
+ end
52
+
53
+ def details_v6(ip_address = nil)
54
+ details_base(ip_address, :v6)
55
+ end
56
+
57
+ def resproxy(ip_address)
58
+ cache_key_str = "resproxy:#{ip_address}"
59
+ res = @cache.get(cache_key(cache_key_str))
60
+ return Response.new(res) unless res.nil?
61
+
62
+ response = @httpc.get("/resproxy/#{CGI.escape(ip_address)}", :v4)
63
+
64
+ if response.status.eql?(429)
65
+ raise RateLimitError,
66
+ RATE_LIMIT_MESSAGE
47
67
  end
48
68
 
49
- if details.key? :ip
50
- details[:ip_address] =
51
- IPAddr.new(details.fetch(:ip))
69
+ details = JSON.parse(response.body, symbolize_names: true)
70
+ @cache.set(cache_key(cache_key_str), details)
71
+ Response.new(details)
72
+ end
73
+
74
+ def get_map_url(ips)
75
+ if !ips.kind_of?(Array)
76
+ return JSON.generate({:error => 'Invalid input. Array required!'})
77
+ end
78
+ if ips.length > 500000
79
+ return JSON.generate({:error => 'No more than 500,000 ips allowed!'})
52
80
  end
53
81
 
54
- if details.key? :loc
55
- loc = details.fetch(:loc).split(',')
56
- details[:latitude] = loc[0]
57
- details[:longitude] = loc[1]
82
+ json_ips = JSON.generate({:ips => ips})
83
+ res = @httpc.post('/tools/map', json_ips)
84
+
85
+ obj = JSON.parse(res.body)
86
+ obj['reportUrl']
87
+ end
88
+
89
+ def batch_requests(url_array, api_token)
90
+ result = Hash.new
91
+ lookup_ips = []
92
+
93
+ url_array.each { |url|
94
+ ip = @cache.get(cache_key(url))
95
+
96
+ unless ip.nil?
97
+ result.store(url, ip)
98
+ else
99
+ lookup_ips << url
100
+ end
101
+ }
102
+
103
+ if lookup_ips.empty?
104
+ return result
58
105
  end
59
106
 
60
- Response.new(details)
107
+ begin
108
+ lookup_ips.each_slice(1000){ |ips|
109
+ json_arr = JSON.generate(lookup_ips)
110
+ res = @httpc.post("/batch?token=#{api_token}", json_arr, 5)
111
+
112
+ raise StandardError, "Request Quota Exceeded" if res.status == 429
113
+
114
+ data = JSON.parse(res.body)
115
+ data.each { |key, val|
116
+ @cache.set(cache_key(key), val)
117
+ }
118
+
119
+ result.merge!(data)
120
+ }
121
+
122
+ rescue StandardError => e
123
+ return e.message
124
+ end
125
+
126
+ result
61
127
  end
62
128
 
63
129
  protected
64
130
 
65
- def request_details(ip_address = nil)
66
- res = @cache.get(ip_address)
131
+ def prepare_http_client(httpc = nil)
132
+ @httpc = Adapter.new(access_token, httpc || :net_http)
133
+ end
134
+
135
+ private
136
+
137
+ def request_details(ip_address = nil, host_type)
138
+ if isBogon(ip_address)
139
+ details = {}
140
+ details[:ip] = ip_address
141
+ details[:bogon] = true
142
+ details[:ip_address] = IPAddr.new(ip_address)
143
+
144
+ return details
145
+ end
146
+
147
+ res = @cache.get(cache_key(ip_address))
67
148
  return res unless res.nil?
68
149
 
69
- response = @httpc.get(escape_path(ip_address))
150
+ response = @httpc.get(escape_path(ip_address), host_type)
70
151
 
71
152
  if response.status.eql?(429)
72
153
  raise RateLimitError,
@@ -74,26 +155,54 @@ class IPinfo::IPinfo
74
155
  end
75
156
 
76
157
  details = JSON.parse(response.body, symbolize_names: true)
77
- @cache.set(ip_address, details)
158
+ @cache.set(cache_key(ip_address), details)
78
159
  details
79
160
  end
80
161
 
81
- def prepare_http_client(httpc = nil)
82
- @httpc = if httpc
83
- Adapter.new(access_token, httpc)
84
- else
85
- Adapter.new(access_token)
86
- end
87
- end
162
+ def details_base(ip_address, host_type)
163
+ details = request_details(ip_address, host_type)
164
+ if details.key? :country
165
+ details[:country_name] =
166
+ @countries.fetch(details.fetch(:country), nil)
167
+ details[:is_eu] =
168
+ @eu_countries.include?(details.fetch(:country))
169
+ details[:country_flag] =
170
+ @countries_flags.fetch(details.fetch(:country), nil)
171
+ details[:country_currency] =
172
+ @countries_currencies.fetch(details.fetch(:country), nil)
173
+ details[:continent] =
174
+ @continents.fetch(details.fetch(:country), nil)
175
+ details[:country_flag_url] = COUNTRY_FLAGS_URL + details.fetch(:country) + ".svg"
176
+ end
177
+
178
+ if details.key? :ip
179
+ details[:ip_address] =
180
+ IPAddr.new(details.fetch(:ip))
181
+ end
88
182
 
89
- def prepare_countries(filename)
90
- file = File.read(filename)
91
- JSON.parse(file)
183
+ if details.key? :loc
184
+ loc = details.fetch(:loc).split(',')
185
+ details[:latitude] = loc[0]
186
+ details[:longitude] = loc[1]
187
+ end
188
+
189
+ Response.new(details)
92
190
  end
93
191
 
94
- private
192
+ def isBogon(ip)
193
+ if ip.nil?
194
+ return false
195
+ end
196
+
197
+ matcher_object = IpAddressMatcher.new(ip)
198
+ matcher_object.matches
199
+ end
95
200
 
96
201
  def escape_path(ip)
97
202
  ip ? "/#{CGI.escape(ip)}" : '/'
98
203
  end
204
+
205
+ def cache_key(ip)
206
+ "1:#{ip}"
207
+ end
99
208
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipinfo/adapter'
4
+ require 'ipinfo/cache/default_cache'
5
+ require 'ipinfo/errors'
6
+ require 'ipinfo/response'
7
+ require_relative 'ipinfo/ipAddressMatcher'
8
+ require_relative 'ipinfo/countriesData'
9
+ require 'ipaddr'
10
+ require 'cgi'
11
+
12
+ module IPinfoCore
13
+ include CountriesData
14
+ DEFAULT_CACHE_MAXSIZE = 4096
15
+ DEFAULT_CACHE_TTL = 60 * 60 * 24
16
+ RATE_LIMIT_MESSAGE = 'To increase your limits, please review our ' \
17
+ 'paid plans at https://ipinfo.io/pricing'
18
+ # Base URL to get country flag image link.
19
+ # "PK" -> "https://cdn.ipinfo.io/static/images/countries-flags/PK.svg"
20
+ COUNTRY_FLAGS_URL = 'https://cdn.ipinfo.io/static/images/countries-flags/'
21
+
22
+ class << self
23
+ def create(access_token = nil, settings = {})
24
+ IPinfo::IPinfoCore.new(access_token, settings)
25
+ end
26
+ end
27
+ end
28
+
29
+ class IPinfo::IPinfoCore
30
+ include IPinfoCore
31
+ attr_accessor :access_token, :countries, :httpc
32
+
33
+ def initialize(access_token = nil, settings = {})
34
+ @access_token = access_token
35
+ @httpc = IPinfo::AdapterCore.new(access_token, httpc || :net_http)
36
+
37
+ maxsize = settings.fetch('maxsize', DEFAULT_CACHE_MAXSIZE)
38
+ ttl = settings.fetch('ttl', DEFAULT_CACHE_TTL)
39
+ @cache = settings.fetch('cache', IPinfo::DefaultCache.new(ttl, maxsize))
40
+ @countries = settings.fetch('countries', DEFAULT_COUNTRY_LIST)
41
+ @eu_countries = settings.fetch('eu_countries', DEFAULT_EU_COUNTRIES_LIST)
42
+ @countries_flags = settings.fetch('countries_flags', DEFAULT_COUNTRIES_FLAG_LIST)
43
+ @countries_currencies = settings.fetch('countries_currencies', DEFAULT_COUNTRIES_CURRENCIES_LIST)
44
+ @continents = settings.fetch('continents', DEFAULT_CONTINENT_LIST)
45
+ end
46
+
47
+ def details(ip_address = nil)
48
+ details_base(ip_address)
49
+ end
50
+
51
+ def request_details(ip_address = nil)
52
+ if ip_address && isBogon(ip_address)
53
+ details = {}
54
+ details[:ip] = ip_address
55
+ details[:bogon] = true
56
+ details[:ip_address] = IPAddr.new(ip_address)
57
+ return details
58
+ end
59
+
60
+ res = @cache.get(cache_key(ip_address))
61
+ return res unless res.nil?
62
+
63
+ response = @httpc.get(escape_path(ip_address))
64
+
65
+ if response.status.eql?(429)
66
+ raise RateLimitError,
67
+ RATE_LIMIT_MESSAGE
68
+ end
69
+
70
+ details = JSON.parse(response.body, symbolize_names: true)
71
+ @cache.set(cache_key(ip_address), details)
72
+ details
73
+ end
74
+
75
+ def details_base(ip_address)
76
+ details = request_details(ip_address)
77
+
78
+ # Core response has nested geo object
79
+ if details.key?(:geo) && details[:geo].is_a?(Hash) && details[:geo].key?(:country_code)
80
+ country_code = details[:geo][:country_code]
81
+ details[:geo][:country_name] = @countries.fetch(country_code, nil)
82
+ details[:geo][:is_eu] = @eu_countries.include?(country_code)
83
+ details[:geo][:country_flag] = @countries_flags.fetch(country_code, nil)
84
+ details[:geo][:country_currency] = @countries_currencies.fetch(country_code, nil)
85
+ details[:geo][:continent] = @continents.fetch(country_code, nil)
86
+ details[:geo][:country_flag_url] = "#{COUNTRY_FLAGS_URL}#{country_code}.svg"
87
+ end
88
+
89
+ # Handle top-level country_code if present (for certain edge cases)
90
+ if details.key?(:country_code)
91
+ country_code = details[:country_code]
92
+ details[:country_name] = @countries.fetch(country_code, nil)
93
+ details[:is_eu] = @eu_countries.include?(country_code)
94
+ details[:country_flag] = @countries_flags.fetch(country_code, nil)
95
+ details[:country_currency] = @countries_currencies.fetch(country_code, nil)
96
+ details[:continent] = @continents.fetch(country_code, nil)
97
+ details[:country_flag_url] = "#{COUNTRY_FLAGS_URL}#{country_code}.svg"
98
+ end
99
+
100
+ if details.key? :ip
101
+ details[:ip_address] =
102
+ IPAddr.new(details.fetch(:ip))
103
+ end
104
+
105
+ IPinfo::Response.new(details)
106
+ end
107
+
108
+ def isBogon(ip)
109
+ if ip.nil?
110
+ return false
111
+ end
112
+
113
+ matcher_object = IPinfo::IpAddressMatcher.new(ip)
114
+ matcher_object.matches
115
+ end
116
+
117
+ def escape_path(ip)
118
+ ip ? "/#{CGI.escape(ip)}" : '/'
119
+ end
120
+
121
+ def cache_key(ip)
122
+ "1:#{ip}"
123
+ end
124
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipinfo/adapter'
4
+ require 'ipinfo/cache/default_cache'
5
+ require 'ipinfo/errors'
6
+ require 'ipinfo/response'
7
+ require_relative 'ipinfo/ipAddressMatcher'
8
+ require_relative 'ipinfo/countriesData'
9
+ require 'ipaddr'
10
+ require 'cgi'
11
+
12
+ module IPinfoLite
13
+ include CountriesData
14
+ DEFAULT_CACHE_MAXSIZE = 4096
15
+ DEFAULT_CACHE_TTL = 60 * 60 * 24
16
+ RATE_LIMIT_MESSAGE = 'To increase your limits, please review our ' \
17
+ 'paid plans at https://ipinfo.io/pricing'
18
+ # Base URL to get country flag image link.
19
+ # "PK" -> "https://cdn.ipinfo.io/static/images/countries-flags/PK.svg"
20
+ COUNTRY_FLAGS_URL = 'https://cdn.ipinfo.io/static/images/countries-flags/'
21
+
22
+ class << self
23
+ def create(access_token = nil, settings = {})
24
+ IPinfo::IPinfoLite.new(access_token, settings)
25
+ end
26
+ end
27
+ end
28
+
29
+ class IPinfo::IPinfoLite
30
+ include IPinfoLite
31
+ attr_accessor :access_token, :countries, :httpc
32
+
33
+ def initialize(access_token = nil, settings = {})
34
+ @access_token = access_token
35
+ @httpc = IPinfo::AdapterLite.new(access_token, httpc || :net_http)
36
+
37
+ maxsize = settings.fetch('maxsize', DEFAULT_CACHE_MAXSIZE)
38
+ ttl = settings.fetch('ttl', DEFAULT_CACHE_TTL)
39
+ @cache = settings.fetch('cache', IPinfo::DefaultCache.new(ttl, maxsize))
40
+ @countries = settings.fetch('countries', DEFAULT_COUNTRY_LIST)
41
+ @eu_countries = settings.fetch('eu_countries', DEFAULT_EU_COUNTRIES_LIST)
42
+ @countries_flags = settings.fetch('countries_flags', DEFAULT_COUNTRIES_FLAG_LIST)
43
+ @countries_currencies = settings.fetch('countries_currencies', DEFAULT_COUNTRIES_CURRENCIES_LIST)
44
+ @continents = settings.fetch('continents', DEFAULT_CONTINENT_LIST)
45
+ end
46
+
47
+ def details(ip_address = nil)
48
+ details_base(ip_address)
49
+ end
50
+
51
+ def request_details(ip_address = nil)
52
+ if ip_address && ip_address != 'me' && isBogon(ip_address)
53
+ details = {}
54
+ details[:ip] = ip_address
55
+ details[:bogon] = true
56
+ details[:ip_address] = IPAddr.new(ip_address)
57
+ return details
58
+ end
59
+
60
+ res = @cache.get(cache_key(ip_address))
61
+ return res unless res.nil?
62
+
63
+ ip_address ||= 'me'
64
+ response = @httpc.get(escape_path(ip_address))
65
+
66
+ if response.status.eql?(429)
67
+ raise RateLimitError,
68
+ RATE_LIMIT_MESSAGE
69
+ end
70
+
71
+ details = JSON.parse(response.body, symbolize_names: true)
72
+ @cache.set(cache_key(ip_address), details)
73
+ details
74
+ end
75
+
76
+ def details_base(ip_address)
77
+ details = request_details(ip_address)
78
+ if details.key? :country_code
79
+ details[:country_name] =
80
+ @countries.fetch(details.fetch(:country_code), nil)
81
+ details[:is_eu] =
82
+ @eu_countries.include?(details.fetch(:country_code))
83
+ details[:country_flag] =
84
+ @countries_flags.fetch(details.fetch(:country_code), nil)
85
+ details[:country_currency] =
86
+ @countries_currencies.fetch(details.fetch(:country_code), nil)
87
+ details[:continent] =
88
+ @continents.fetch(details.fetch(:country_code), nil)
89
+ details[:country_flag_url] = "#{COUNTRY_FLAGS_URL}#{details.fetch(:country_code)}.svg"
90
+ end
91
+
92
+ if details.key? :ip
93
+ details[:ip_address] =
94
+ IPAddr.new(details.fetch(:ip))
95
+ end
96
+
97
+ IPinfo::Response.new(details)
98
+ end
99
+
100
+ def isBogon(ip)
101
+ if ip.nil?
102
+ return false
103
+ end
104
+
105
+ matcher_object = IPinfo::IpAddressMatcher.new(ip)
106
+ matcher_object.matches
107
+ end
108
+
109
+ def escape_path(ip)
110
+ ip ? "/#{CGI.escape(ip)}" : '/'
111
+ end
112
+
113
+ def cache_key(ip)
114
+ "1:#{ip}"
115
+ end
116
+ end