sqreen 1.15.0 → 1.15.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59d80d2fe6c9c0bd343380d867b4c1b7f878a42890e3cbe5ab005e4e32507abc
4
- data.tar.gz: 1e7c0ee1ef948048fb020b3b64377a1476f2b000aaf22df683b5aec38658895e
3
+ metadata.gz: 87e7db04c8d69640cf2da93b2dc235f5b1a95c8f2847db137c3047eb9f87ac1a
4
+ data.tar.gz: 690b3963a003fced7eb7e699576a488b006dc63589e372c67b0f808d9b2aafd6
5
5
  SHA512:
6
- metadata.gz: 8b26544a382b1ef480be8bd92593187584efa7c0b63a05d366f4d53083d6d2f3e71acb168c216985d649281b6004f5bb31ed068b688062531a77cfe0ead4d0e0
7
- data.tar.gz: 6331863e48ce18d812ed294b9d1a6fef01a1a805ca2dbab1a20bd76d5837b983bcd022572d751f1e006a68e34a5cf0db2b32a4fc4e7eaf598bf6cd9f7c269ee2
6
+ metadata.gz: e70942a29b5a702ebd3e023d72ad49a58234510c798e0fbf88dfc99388ddbbb24bc0c25a41f3103df92884d1b9804355e2d724868372f9dc3197bcad134a9fd4
7
+ data.tar.gz: 5719e61c1bae72c9dbf4257ccce892cf8135b7a887e69dce1320c5955ddae9b575a992ab42a83e28887daaa09187705686579a6cabc53c555d5931f90a812b11
@@ -2,6 +2,7 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
4
  require 'ipaddr'
5
+ require 'sqreen/trie'
5
6
  require 'sqreen/log'
6
7
  require 'sqreen/exception'
7
8
  require 'sqreen/sdk'
@@ -24,21 +25,17 @@ module Sqreen
24
25
  class Repository
25
26
  include Singleton
26
27
 
27
- def initialize
28
- @actions = {} # indexed by subclass
29
- @actions.default_proc = proc { |h, k| h[k] = [] }
28
+ def add(params, action)
29
+ action.class.index(params || {}, action)
30
30
  end
31
31
 
32
- def <<(action)
33
- @actions[action.class] << action
34
- end
35
-
36
- def [](action_class)
37
- @actions[action_class]
32
+ def get(action_class, key)
33
+ action_class = Base.get_type_class(action_class) unless action_class.class == Class
34
+ action_class.actions_matching key
38
35
  end
39
36
 
40
37
  def clear
41
- @actions.clear
38
+ Base.known_subclasses.each(&:clear)
42
39
  end
43
40
  end
44
41
 
@@ -68,6 +65,8 @@ module Sqreen
68
65
  end
69
66
 
70
67
  # Base class for actions
68
+ # subclasses must also implement some methods in their singleton classes
69
+ # (actions_matching, index and clear)
71
70
  class Base
72
71
  attr_reader :id, :expiry, :send_response
73
72
 
@@ -124,10 +123,27 @@ module Sqreen
124
123
  @@subclasses[name]
125
124
  end
126
125
 
126
+ def known_subclasses
127
+ @@subclasses.values
128
+ end
129
+
127
130
  def known_types
128
131
  @@subclasses.keys
129
132
  end
130
133
 
134
+ # all actions matching, possibly already expired
135
+ def actions_matching(_key)
136
+ raise 'implement in singletons of subclasses'
137
+ end
138
+
139
+ def index(_params, _action)
140
+ raise 'implement in singletons of subclasses'
141
+ end
142
+
143
+ def clear
144
+ raise 'implement in singletons of subclasses'
145
+ end
146
+
131
147
  def inherited(subclass)
132
148
  class << subclass
133
149
  public :new
@@ -143,43 +159,74 @@ module Sqreen
143
159
  end
144
160
  end
145
161
 
146
- module IpRanges
147
- attr_reader :ranges
162
+ module IpRangesIndex
163
+ def add_prefix(prefix_str, data)
164
+ @trie_v4 ||= Sqreen::Trie.new
165
+ @trie_v6 ||= Sqreen::Trie.new(nil, nil, Socket::AF_INET6)
166
+ prefix = Sqreen::Prefix.from_str(prefix_str, data)
167
+ trie = prefix.family == Socket::AF_INET6 ? @trie_v6 : @trie_v4
168
+ trie.insert prefix
169
+ end
170
+
171
+ def matching_actions(client_ip)
172
+ parsed_ip = IPAddr.new(client_ip)
173
+ trie = parsed_ip.family == Socket::AF_INET6 ? @trie_v6 : @trie_v4
174
+ found = trie.search_matching(parsed_ip.to_i, parsed_ip.family)
175
+ return [] unless found.size > 0
176
+
177
+ Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
178
+ found.map(&:data)
179
+ end
180
+
181
+ def clear
182
+ @trie_v4 = Sqreen::Trie.new
183
+ @trie_v6 = Sqreen::Trie.new(nil, nil, Socket::AF_INET6)
184
+ end
185
+ end
186
+
187
+ module IpRangeIndexedActionClass
188
+ include IpRangesIndex
189
+
190
+ def actions_matching(client_ip)
191
+ matching_actions client_ip
192
+ end
193
+
194
+ def index(params, action)
195
+ ranges = parse_ip_ranges params
196
+
197
+ ranges.each do |r|
198
+ add_prefix r, action
199
+ end
200
+ end
201
+
202
+ private
148
203
 
204
+ # returns array of prefixes in string form
149
205
  def parse_ip_ranges(params)
150
206
  ranges = params['ip_cidr']
151
207
  unless ranges && ranges.is_a?(Array) && !ranges.empty?
152
208
  raise 'no non-empty ip_cidr array present'
153
209
  end
154
210
 
155
- @ranges = ranges.map &IPAddr.method(:new)
156
- end
157
-
158
- def matches_ip?(client_ip)
159
- parsed_ip = IPAddr.new client_ip
160
- found = ranges.find { |r| r.include? parsed_ip }
161
- return false unless found
162
-
163
- Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
164
- true
211
+ ranges
165
212
  end
166
213
  end
167
214
 
168
215
  # Block a list of IP address ranges. Standard "raise" behavior.
169
216
  class BlockIp < Base
170
- include IpRanges
217
+ extend IpRangeIndexedActionClass
218
+
171
219
  self.type_name = 'block_ip'
172
220
 
173
221
  def initialize(id, opts, params = {})
222
+ # no need to store the ranges for this action, the index filter the class
174
223
  super(id, opts)
175
- parse_ip_ranges params
176
224
  end
177
225
 
178
226
  def do_run(client_ip)
179
- return nil unless matches_ip? client_ip
180
227
  e = Sqreen::AttackBlocked.new("Blocked client's IP #{client_ip} " \
181
- "(action: #{id} covering range(s) #{ranges}). No action is required")
182
- { :status => :raise, :exception => e }
228
+ "(action: #{id}). No action is required")
229
+ { :status => :raise, :exception => e, :skip_rem_cbs => true }
183
230
  end
184
231
 
185
232
  def event_properties(client_ip)
@@ -190,7 +237,8 @@ module Sqreen
190
237
  # Block a list of IP address ranges by forcefully redirecting the user
191
238
  # to a specific URL.
192
239
  class RedirectIp < Base
193
- include IpRanges
240
+ extend IpRangeIndexedActionClass
241
+
194
242
  self.type_name = 'redirect_ip'
195
243
 
196
244
  attr_reader :redirect_url
@@ -199,16 +247,15 @@ module Sqreen
199
247
  super(id, opts)
200
248
  @redirect_url = params['url']
201
249
  raise "no url provided for action #{id}" unless @redirect_url
202
- parse_ip_ranges params
203
250
  end
204
251
 
205
252
  def do_run(client_ip)
206
- return nil unless matches_ip? client_ip
207
253
  Sqreen.log.info "Will request redirect for client with IP #{client_ip} " \
208
- "(action: #{id} covering range(s) #{ranges})."
254
+ "(action: #{id})."
209
255
  {
210
256
  :status => :skip,
211
257
  :new_return_value => [303, { 'Location' => @redirect_url }, ['']],
258
+ :skip_rem_cbs => true,
212
259
  }
213
260
  end
214
261
 
@@ -222,15 +269,45 @@ module Sqreen
222
269
  class BlockUser < Base
223
270
  self.type_name = 'block_user'
224
271
 
272
+ class << self
273
+ def actions_matching(identity_params)
274
+ key = stringify_keys(identity_params)
275
+ actions = @idx[key]
276
+ actions || []
277
+ end
278
+
279
+ def index(params, action)
280
+ @idx ||= {}
281
+ users = params['users']
282
+ raise ::Sqreen::Exception, 'nil "users" param for block_user action' if users.nil?
283
+ raise ::Sqreen::Exception, '"users" param must be an array' unless users.is_a? Array
284
+
285
+ users.each do |u|
286
+ @idx[u] ||= []
287
+ @idx[u] << action
288
+ end
289
+ end
290
+
291
+ def clear
292
+ @idx = {}
293
+ end
294
+
295
+ private
296
+
297
+ def stringify_keys(hash)
298
+ Hash[
299
+ hash.map { |k, v| [k.to_s, v] }
300
+ ]
301
+ end
302
+ end
303
+
304
+ # BlockUser proper definition continues
305
+
225
306
  def initialize(id, opts, params = {})
226
307
  super(id, opts)
227
- @users = params['users']
228
- raise ::Sqreen::Exception, 'nil "users" param for block_user action' if @users.nil?
229
- raise ::Sqreen::Exception, '"users" param must be an array' unless @users.is_a? Array
230
308
  end
231
309
 
232
310
  def do_run(identity_params)
233
- return unless @users.include? stringify_keys(identity_params)
234
311
  Sqreen.log.info(
235
312
  "Will raise due to user being blocked by action #{id}. " \
236
313
  "Blocked user identity: #{identity_params}"
@@ -250,14 +327,6 @@ module Sqreen
250
327
  def event_properties(identity_params)
251
328
  { 'user' => identity_params }
252
329
  end
253
-
254
- private
255
-
256
- def stringify_keys(hash)
257
- Hash[
258
- hash.map { |k, v| [k.to_s, v] }
259
- ]
260
- end
261
330
  end
262
331
  end
263
332
  end
@@ -25,15 +25,15 @@ module Sqreen
25
25
 
26
26
  if cb.pre?
27
27
  methods[0] ||= []
28
- methods[0] << cb
28
+ add_to_array(methods[0], cb)
29
29
  end
30
30
  if cb.post?
31
31
  methods[1] ||= []
32
- methods[1] << cb
32
+ add_to_array(methods[1], cb)
33
33
  end
34
34
  if cb.failing?
35
35
  methods[2] ||= []
36
- methods[2] << cb
36
+ add_to_array(methods[2], cb)
37
37
  end
38
38
  end
39
39
 
@@ -88,5 +88,18 @@ module Sqreen
88
88
  end
89
89
  end
90
90
  end
91
+
92
+ private
93
+
94
+ def add_to_array(arr, cb)
95
+ # find last element with prio equal or smaller; insert after
96
+ pos = arr.size
97
+ arr.reverse_each do |arr_cb|
98
+ break if arr_cb.priority <= cb.priority
99
+ pos -= 1
100
+ end
101
+
102
+ arr.insert(pos, cb)
103
+ end
91
104
  end
92
105
  end
@@ -36,6 +36,8 @@ module Sqreen
36
36
  #
37
37
  # CB can also declare that they are whitelisted and should not be run at the moment.
38
38
 
39
+ DEFAULT_PRIORITY = 100
40
+
39
41
  attr_reader :klass, :method
40
42
  attr_reader :overtimeable
41
43
 
@@ -55,6 +57,11 @@ module Sqreen
55
57
  false
56
58
  end
57
59
 
60
+ # the lower, the closer to the beginning of the list
61
+ def priority
62
+ DEFAULT_PRIORITY
63
+ end
64
+
58
65
  def pre?
59
66
  @has_pre
60
67
  end
@@ -95,6 +95,7 @@ module Sqreen
95
95
  res[:rule_name] = rule
96
96
  end
97
97
  returns << res
98
+ break if res.is_a?(Hash) && res[:skip_rem_cbs]
98
99
  rescue StandardError => e
99
100
  Sqreen.log.warn { "we catch an exception: #{e.inspect}" }
100
101
  Sqreen.log.debug e.backtrace
@@ -710,6 +711,7 @@ module Sqreen
710
711
  add_callback(rcb)
711
712
  end
712
713
 
714
+ # add hardcoded callbacks, observing priority
713
715
  hardcoded_callbacks(framework).each { |cb| add_callback(cb) }
714
716
 
715
717
  Sqreen.instrumentation_ready = true
@@ -85,9 +85,9 @@ module Sqreen
85
85
  ctx.add_code(@code_id, @code) unless ctx.has_code?(@code_id)
86
86
 
87
87
  begin
88
- json_args = "[#{arguments.map(&method(:fixup_bad_encoding)).map(&:to_json).join(',')}]"
88
+ json_args = convert_arguments(arguments)
89
89
  ctx.eval_unsafe(
90
- "sqreen_data['#{@code_id}']['#{cb_name}'].apply(this, #{json_args})", nil, budget)
90
+ "sqreen_data['#{@code_id}']['#{cb_name}'].apply(this, #{json_args})", nil, budget)
91
91
  rescue @module::ScriptTerminatedError
92
92
  nil
93
93
  end
@@ -100,9 +100,31 @@ module Sqreen
100
100
 
101
101
  private
102
102
 
103
- def fixup_bad_encoding(arg)
104
- # NOTE: we don't fix encoding problems in deeper structures
103
+ def convert_arguments(args)
104
+ JSON.generate(args)
105
+ rescue JSON::GeneratorError, Encoding::UndefinedConversionError
106
+ fixed_args = fixup_bad_encoding(args)
107
+ JSON.generate(fixed_args)
108
+ end
109
+
110
+ def fixup_bad_encoding(arg, max_depth = 100)
111
+ return nil if max_depth <= 0
112
+
113
+ if arg.is_a?(Array)
114
+ return arg.map { |it| fixup_bad_encoding(it, max_depth - 1) }
115
+ end
116
+
117
+ if arg.is_a?(Hash)
118
+ return Hash[
119
+ arg.map do |k, v|
120
+ [fixup_bad_encoding(k, max_depth - 1),
121
+ fixup_bad_encoding(v, max_depth - 1)]
122
+ end
123
+ ]
124
+ end
125
+
105
126
  return arg unless arg.is_a?(String)
127
+
106
128
  unless arg.valid_encoding?
107
129
  return arg.dup.force_encoding(Encoding::ISO_8859_1)
108
130
  end
@@ -19,6 +19,7 @@ module Sqreen
19
19
  METRICS = 'metrics'.freeze
20
20
  CONDITIONS = 'conditions'.freeze
21
21
  CALL_COUNT_INTERVAL = 'call_count_interval'.freeze
22
+ PRIORITY = 'priority'.freeze
22
23
 
23
24
  freeze
24
25
  end
@@ -47,6 +47,10 @@ module Sqreen
47
47
  @rule[Attrs::RULESPACK_ID]
48
48
  end
49
49
 
50
+ def priority
51
+ @rule[Attrs::PRIORITY] || super
52
+ end
53
+
50
54
  # Recommend taking an action (optionnally adding more data/context)
51
55
  #
52
56
  # This will format the requested action and optionnally
@@ -23,7 +23,7 @@ module Sqreen
23
23
  return unless found
24
24
  Sqreen.log.debug { "Found blacklisted IP #{ip} - found: #{found}" }
25
25
  record_observation('blacklisted', found, 1)
26
- advise_action(:raise)
26
+ advise_action(:raise, :skip_rem_cbs => true)
27
27
  end
28
28
 
29
29
  protected
@@ -14,7 +14,7 @@ module Sqreen
14
14
  end
15
15
 
16
16
  def post(_retval, _inst, args, _budget = nil)
17
- actions = actions_repo[Sqreen::Actions::BlockUser]
17
+ actions = actions_repo.get('block_user', args[@auth_keys_idx])
18
18
 
19
19
  actions.each do |action|
20
20
  res = action.run args[@auth_keys_idx]
@@ -9,6 +9,8 @@ module Sqreen
9
9
  module Rules
10
10
  # Runs actions concerned with whether the request ought to be served
11
11
  class RunReqStartActions < FrameworkCB
12
+ PRIORITY = 95
13
+
12
14
  def initialize(framework)
13
15
  if defined?(Sqreen::Frameworks::SinatraFramework) &&
14
16
  framework.is_a?(Sqreen::Frameworks::SinatraFramework)
@@ -30,13 +32,17 @@ module Sqreen
30
32
  framework && !framework.whitelisted_match.nil?
31
33
  end
32
34
 
35
+ def priority
36
+ PRIORITY
37
+ end
38
+
33
39
  def pre(_inst, _args, _budget = nil, &_block)
34
40
  return unless framework
35
41
  ip = framework.client_ip
36
42
  return unless ip
37
43
 
38
- actions = actions_repo[Sqreen::Actions::BlockIp] +
39
- actions_repo[Sqreen::Actions::RedirectIp]
44
+ actions = actions_repo.get(Sqreen::Actions::BlockIp, ip) +
45
+ actions_repo.get(Sqreen::Actions::RedirectIp, ip)
40
46
 
41
47
  actions.each do |act|
42
48
  res = run_client_ip_action(act, ip)
@@ -445,9 +445,14 @@ module Sqreen
445
445
  def load_actions(hashes)
446
446
  unsupported = Set.new
447
447
 
448
+ repos = Sqreen::Actions::Repository.instance
449
+ repos.clear
450
+
448
451
  actions = hashes.map do |h|
449
452
  begin
450
- Sqreen::Actions.deserialize_action(h)
453
+ act = Sqreen::Actions.deserialize_action(h)
454
+ repos.add h['parameters'], act
455
+ act
451
456
  rescue Sqreen::Actions::UnknownActionType => e
452
457
  Sqreen.log.warn("Unsupported action type: #{e.action_type}")
453
458
  unsupported << e.action_type
@@ -458,11 +463,7 @@ module Sqreen
458
463
  end
459
464
 
460
465
  actions = actions.reject(&:nil?)
461
- Sqreen.log.debug("Will add #{actions.size} valid actions")
462
-
463
- repos = Sqreen::Actions::Repository.instance
464
- repos.clear
465
- actions.each { |action| repos << action }
466
+ Sqreen.log.debug("Added #{actions.size} valid actions")
466
467
 
467
468
  unsupported
468
469
  end
@@ -0,0 +1,274 @@
1
+ require 'ipaddr'
2
+
3
+ module Sqreen
4
+ Trie = Struct.new(:head, :num_active_nodes, :family) do
5
+ attr_reader :max_num_bits
6
+
7
+ def initialize(*args)
8
+ super
9
+ self.family ||= Socket::AF_INET
10
+ self.num_active_nodes = 0
11
+ @max_num_bits = family == Socket::AF_INET ? 32 : 128
12
+ end
13
+
14
+ def insert(arg_prefix)
15
+ raise 'family mismatch' if arg_prefix.family != family
16
+
17
+ if head.nil?
18
+ node = Node.new(
19
+ arg_prefix.bitlen, # bit
20
+ arg_prefix,
21
+ nil, nil, nil, #l , r, parent
22
+ )
23
+ self.head = node
24
+ self.num_active_nodes += 1
25
+ return node
26
+ end
27
+
28
+ arg_addr = arg_prefix.address
29
+ arg_bitlen = arg_prefix.bitlen
30
+ xcur = self.head
31
+
32
+ # descend until we find the end of the tree or go past the
33
+ # bitlen of the prefix we're adding
34
+ while xcur.bit < arg_bitlen || xcur.empty?
35
+ if bit_set?(arg_addr, xcur.bit)
36
+ break if xcur.r.nil?
37
+ xcur = xcur.r
38
+ else
39
+ break if xcur.l.nil?
40
+ xcur = xcur.l
41
+ end
42
+ end
43
+
44
+ trie_addr = xcur.prefix.address
45
+
46
+ # find first bit that differs between addr and trie_addr
47
+ cmp_bit_end = [arg_bitlen, xcur.bit].min # after last to be compared
48
+
49
+ differ_bit = (0...cmp_bit_end).find do |i|
50
+ bit_set?(arg_addr, i) ^ bit_set?(trie_addr, i)
51
+ end
52
+ differ_bit = cmp_bit_end if differ_bit.nil?
53
+
54
+ # go up till we find the parent before the bits differ
55
+ xparent = xcur.parent
56
+ while !xparent.nil? && xparent.bit >= differ_bit
57
+ xcur = xparent
58
+ xparent = xcur.parent
59
+ end
60
+
61
+ # case 1: replace current node's prefix
62
+ if differ_bit == arg_bitlen && xcur.bit == arg_bitlen
63
+ xcur.prefix = arg_prefix
64
+ return xcur
65
+ end
66
+
67
+ xnew = Node.new(arg_prefix.bitlen, arg_prefix, nil, nil, nil)
68
+ self.num_active_nodes += 1
69
+
70
+ # case 2: append below found node
71
+ if xcur.bit == differ_bit
72
+ xnew.parent = xcur
73
+ if bit_set?(arg_addr, xcur.bit)
74
+ xcur.r = xnew
75
+ else
76
+ xcur.l = xnew
77
+ end
78
+
79
+ return xnew
80
+ end
81
+
82
+ # case 3: take place of found node
83
+ if arg_bitlen == differ_bit
84
+ if bit_set?(trie_addr, arg_bitlen)
85
+ xnew.r = xcur
86
+ else
87
+ xnew.l = xcur
88
+ end
89
+
90
+ xnew.parent = xcur.parent
91
+
92
+ if xcur.parent.nil?
93
+ self.head = xnew
94
+ elsif xcur.parent.r.equal?(xcur)
95
+ xcur.parent.r = xnew
96
+ else
97
+ xcur.parent.l = xnew
98
+ end
99
+
100
+ xcur.parent = xnew
101
+
102
+ return xnew
103
+ end
104
+
105
+ # case 4: need to add intermediate node to be parent of the
106
+ # both xnew and xcur
107
+
108
+ # last arg is the parent of the new node
109
+ xglue = Node.new(differ_bit, nil, nil, nil, xcur.parent)
110
+ self.num_active_nodes += 1
111
+
112
+ if bit_set?(arg_addr, differ_bit)
113
+ xglue.r = xnew
114
+ xglue.l = xcur
115
+ else
116
+ xglue.r = xcur
117
+ xglue.l = xnew
118
+ end
119
+
120
+ xnew.parent = xglue
121
+
122
+ if xcur.parent.nil?
123
+ self.head = xglue
124
+ elsif xcur.equal?(xcur.parent.r)
125
+ xcur.parent.r = xglue
126
+ else
127
+ xcur.parent.l = xglue
128
+ end
129
+
130
+ xcur.parent = xglue
131
+
132
+ xnew
133
+ end
134
+
135
+ # generate pdf with `dot -Tpdf <input> > foo.pdf`
136
+ def to_dot(header = true)
137
+ res = ''
138
+ res = "graph trie {\n" if header
139
+
140
+ next_name = 'a'
141
+ obj_label_map = Hash.new do |h, k|
142
+ h[k] = next_name
143
+ next_name = next_name.next
144
+ h[k]
145
+ end
146
+
147
+ head.walk(max_num_bits, true) do |node|
148
+ node_name = obj_label_map[node.object_id]
149
+
150
+ if node.empty?
151
+ label = "<glue at bit #{node.bit}>"
152
+ else
153
+ ip_addr = IPAddr.new(node.prefix.address, node.prefix.family)
154
+ label = ip_addr.to_s + '/' + node.prefix.bitlen.to_s
155
+ end
156
+ res += "#{node_name} [label=\"#{label}\"]\n"
157
+ res += "#{node_name} -- #{obj_label_map[node.l.object_id]} [label=\"l\"]\n" if node.l
158
+ res += "#{node_name} -- #{obj_label_map[node.r.object_id]} [label=\"r\"]\n" if node.r
159
+ end
160
+ res += "}\n" if header
161
+ res
162
+ end
163
+
164
+ def search_best(addr, family)
165
+ nodes = search_common(addr, family)
166
+ for i in (nodes.size - 1).downto(0)
167
+ xcur = nodes[i]
168
+ return node_to_ip_addr(xcur) if xcur.prefix.matches?(addr, family)
169
+ end
170
+
171
+ nil
172
+ end
173
+
174
+ def search_matching(addr, family)
175
+ nodes = search_common(addr, family)
176
+ nodes.select { |xcur| xcur.prefix.matches?(addr, family) }
177
+ .map { |xcur| node_to_ip_addr(xcur) }
178
+ .reverse
179
+ end
180
+
181
+ private
182
+
183
+ def node_to_ip_addr(node)
184
+ ret = IPAddr.new(node.prefix.address, node.prefix.family)
185
+ ret.singleton_class.send(:define_method, :data) { node.prefix.data }
186
+ ret
187
+ end
188
+
189
+ def bit_set?(addr, i)
190
+ addr[max_num_bits - i - 1] == 1
191
+ end
192
+
193
+ def search_common(addr, family)
194
+ raise 'family mismatch' unless family == self.family
195
+
196
+ xstack = []
197
+
198
+ xcur = head
199
+ while !xcur.nil?
200
+ unless xcur.empty?
201
+ xstack << xcur
202
+ end
203
+
204
+ xcur = bit_set?(addr, xcur.bit) ? xcur.r : xcur.l
205
+ end
206
+
207
+ xstack
208
+ end
209
+ end
210
+
211
+ Prefix = Struct.new(:family, :bitlen, :address, :data) do # addr is integer
212
+ def initialize(*args)
213
+ super
214
+ raise ArgumentError, 'no family given' unless family
215
+ raise ArgumentError, 'no bitlen given' unless bitlen
216
+ raise ArgumentError, 'no address given' unless address
217
+ end
218
+
219
+ def matches?(addr, family)
220
+ raise 'family mismatch' unless family == self.family
221
+ shift_amount = (family == Socket::AF_INET ? 32 : 128) - self.bitlen
222
+ (addr ^ self.address) >> shift_amount == 0
223
+ end
224
+ end
225
+
226
+ def Prefix.from_str(str, data = nil)
227
+ ip_addr = IPAddr.new(str)
228
+ if str =~ /\/(\d+)$/
229
+ bitlen = $~[1].to_i
230
+ else
231
+ bitlen = ip_addr.family == Socket::AF_INET6 ? 128 : 32
232
+ end
233
+ Prefix.new(ip_addr.family, bitlen, ip_addr.to_i, data)
234
+ end
235
+
236
+ # bit starts at 0 (most significant)
237
+ Node = Struct.new(:bit, :prefix, :l, :r, :parent) do
238
+ def initialize(*args)
239
+ super
240
+ raise ArgumentError, 'no bit given' if bit.nil?
241
+ end
242
+
243
+ def empty?
244
+ prefix.nil?
245
+ end
246
+
247
+ # cover the whole tree
248
+ def walk(max_bits, empty_nodes = false)
249
+ xstack = Array.new(max_bits + 1)
250
+ sidx = 0 # stack index
251
+ xhead = self
252
+ xcur = xhead
253
+ while !xcur.nil?
254
+ yield xcur unless xcur.empty? && !empty_nodes
255
+
256
+ if xcur.l
257
+ if xcur.r
258
+ xstack[sidx] = xcur.r
259
+ sidx += 1
260
+ end
261
+ xcur = xcur.l
262
+ elsif xcur.r
263
+ xcur = xcur.r
264
+ elsif sidx.nonzero?
265
+ sidx -= 1
266
+ xcur = xstack[sidx]
267
+ else
268
+ xcur = nil
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ end
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
  module Sqreen
4
- VERSION = '1.15.0'.freeze
4
+ VERSION = '1.15.1'.freeze
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqreen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.15.0
4
+ version: 1.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sqreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-24 00:00:00.000000000 Z
11
+ date: 2018-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sq_mini_racer
@@ -117,6 +117,7 @@ files:
117
117
  - lib/sqreen/session.rb
118
118
  - lib/sqreen/shared_storage.rb
119
119
  - lib/sqreen/shared_storage23.rb
120
+ - lib/sqreen/trie.rb
120
121
  - lib/sqreen/version.rb
121
122
  homepage: https://www.sqreen.io/
122
123
  licenses: []