sqreen 1.15.0 → 1.15.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.
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: []