sqreen 1.18.3.beta1 → 1.18.3.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -5
  3. data/lib/sqreen/actions.rb +11 -337
  4. data/lib/sqreen/actions/base.rb +110 -0
  5. data/lib/sqreen/actions/block_ip.rb +32 -0
  6. data/lib/sqreen/actions/block_user.rb +44 -0
  7. data/lib/sqreen/actions/ip_range_indexed_action_class.rb +36 -0
  8. data/lib/sqreen/actions/ip_ranges_index.rb +36 -0
  9. data/lib/sqreen/actions/redirect_ip.rb +40 -0
  10. data/lib/sqreen/actions/redirect_user.rb +45 -0
  11. data/lib/sqreen/actions/repository.rb +24 -0
  12. data/lib/sqreen/actions/unknown_action_type.rb +16 -0
  13. data/lib/sqreen/actions/user_action_class.rb +41 -0
  14. data/lib/sqreen/agent.rb +4 -1
  15. data/lib/sqreen/attack_blocked.rb +17 -0
  16. data/lib/sqreen/binding_accessor.rb +9 -102
  17. data/lib/sqreen/binding_accessor/path_elem.rb +8 -0
  18. data/lib/sqreen/binding_accessor/transforms.rb +107 -0
  19. data/lib/sqreen/capped_queue.rb +2 -0
  20. data/lib/sqreen/{callbacks.rb → cb.rb} +1 -53
  21. data/lib/sqreen/{callback_tree.rb → cb_tree.rb} +2 -2
  22. data/lib/sqreen/condition_evaluator.rb +22 -5
  23. data/lib/sqreen/configuration.rb +3 -0
  24. data/lib/sqreen/default_cb.rb +20 -0
  25. data/lib/sqreen/deferred_logger.rb +63 -0
  26. data/lib/sqreen/deliveries.rb +10 -0
  27. data/lib/sqreen/deliveries/batch.rb +7 -1
  28. data/lib/sqreen/deliveries/simple.rb +5 -0
  29. data/lib/sqreen/dependency/rails.rb +4 -0
  30. data/lib/sqreen/dependency/sinatra.rb +4 -0
  31. data/lib/sqreen/error_handling_middleware.rb +30 -0
  32. data/lib/sqreen/event.rb +2 -0
  33. data/lib/sqreen/events/attack.rb +2 -0
  34. data/lib/sqreen/events/request_record.rb +11 -56
  35. data/lib/sqreen/exception.rb +9 -40
  36. data/lib/sqreen/formatter_with_tid.rb +45 -0
  37. data/lib/sqreen/framework_cb.rb +28 -0
  38. data/lib/sqreen/frameworks.rb +7 -0
  39. data/lib/sqreen/frameworks/generic.rb +5 -1
  40. data/lib/sqreen/frameworks/rails.rb +2 -0
  41. data/lib/sqreen/frameworks/request_recorder.rb +3 -0
  42. data/lib/sqreen/frameworks/sinatra.rb +2 -0
  43. data/lib/sqreen/frameworks/sqreen_test.rb +2 -0
  44. data/lib/sqreen/instrumentation.rb +5 -5
  45. data/lib/sqreen/invalid_signature_exception.rb +8 -0
  46. data/lib/{sqreen-alt.rb → sqreen/js.rb} +6 -1
  47. data/lib/sqreen/js/call_context.rb +10 -0
  48. data/lib/sqreen/js/context_pool.rb +60 -0
  49. data/lib/sqreen/js/exec_js_runnable.rb +20 -0
  50. data/lib/sqreen/js/execjs_adapter.rb +6 -47
  51. data/lib/sqreen/js/executable_js.rb +12 -0
  52. data/lib/sqreen/js/js_service.rb +2 -22
  53. data/lib/sqreen/js/js_service_adapter.rb +18 -0
  54. data/lib/sqreen/js/mini_racer_adapter.rb +6 -180
  55. data/lib/sqreen/js/mini_racer_executable_js.rb +142 -0
  56. data/lib/sqreen/js/thread_local_exec_js_runnable.rb +47 -0
  57. data/lib/sqreen/log.rb +8 -188
  58. data/lib/sqreen/logger.rb +83 -0
  59. data/lib/sqreen/metrics_store.rb +3 -11
  60. data/lib/sqreen/metrics_store/already_registered_metric.rb +11 -0
  61. data/lib/sqreen/metrics_store/unknown_metric.rb +11 -0
  62. data/lib/sqreen/metrics_store/unregistered_metric.rb +11 -0
  63. data/lib/sqreen/middleware.rb +0 -44
  64. data/lib/sqreen/mono_time.rb +2 -0
  65. data/lib/sqreen/node.rb +44 -0
  66. data/lib/sqreen/not_implemented_yet.rb +8 -0
  67. data/lib/sqreen/null_logger.rb +24 -0
  68. data/lib/sqreen/payload_creator.rb +2 -19
  69. data/lib/sqreen/payload_creator/header_section.rb +28 -0
  70. data/lib/sqreen/prefix.rb +33 -0
  71. data/lib/sqreen/rails_middleware.rb +14 -0
  72. data/lib/sqreen/remote_command.rb +1 -8
  73. data/lib/sqreen/remote_command/failure_output.rb +11 -0
  74. data/lib/sqreen/rules.rb +32 -2
  75. data/lib/sqreen/{rule_attributes.rb → rules/attrs.rb} +0 -0
  76. data/lib/sqreen/{rules_callbacks/sdk_auth_track.rb → rules/auth_track_cb.rb} +2 -2
  77. data/lib/sqreen/{rules_callbacks/binding_accessor_matcher.rb → rules/binding_accessor_matcher_cb.rb} +4 -8
  78. data/lib/sqreen/{rules_callbacks → rules}/binding_accessor_metrics.rb +1 -1
  79. data/lib/sqreen/{rules_callbacks/blacklist_ips.rb → rules/blacklist_ips_cb.rb} +3 -2
  80. data/lib/sqreen/{rules_callbacks → rules}/count_http_codes.rb +2 -2
  81. data/lib/sqreen/{rules_callbacks/crawler_user_agent_matches.rb → rules/crawler_user_agent_matches_cb.rb} +1 -1
  82. data/lib/sqreen/{rules_callbacks/crawler_user_agent_matches_metrics.rb → rules/crawler_user_agent_matches_metrics_cb.rb} +1 -1
  83. data/lib/sqreen/{rules_callbacks/custom_error.rb → rules/custom_error_cb.rb} +1 -1
  84. data/lib/sqreen/{rules_callbacks/devise_auth_track.rb → rules/devise_auth_track_cb.rb} +2 -2
  85. data/lib/sqreen/{rules_callbacks/devise_signup_track.rb → rules/devise_signup_track_cb.rb} +2 -2
  86. data/lib/sqreen/{rules_callbacks/execjs.rb → rules/execjs_cb.rb} +49 -50
  87. data/lib/sqreen/{rules_callbacks/headers_insert.rb → rules/headers_insert_cb.rb} +1 -1
  88. data/lib/sqreen/{rules_callbacks → rules}/matcher_rule.rb +2 -2
  89. data/lib/sqreen/{rules_callbacks/not_found.rb → rules/not_found_cb.rb} +2 -2
  90. data/lib/sqreen/{rules_callbacks/rails_parameters.rb → rules/rails_parameters_cb.rb} +1 -1
  91. data/lib/sqreen/{rules_callbacks → rules}/record_request_context.rb +1 -1
  92. data/lib/sqreen/{rules_callbacks/regexp_rule.rb → rules/regexp_rule_cb.rb} +1 -1
  93. data/lib/sqreen/{rule_callback.rb → rules/rule_cb.rb} +2 -2
  94. data/lib/sqreen/{rules_callbacks → rules}/run_req_start_actions.rb +4 -2
  95. data/lib/sqreen/{rules_callbacks → rules}/run_user_actions.rb +1 -1
  96. data/lib/sqreen/{rules_callbacks/shell_env.rb → rules/shell_env_cb.rb} +1 -1
  97. data/lib/sqreen/{rules_callbacks/sdk_signup_track.rb → rules/signup_track_cb.rb} +2 -2
  98. data/lib/sqreen/{rules_callbacks → rules}/update_request_context.rb +1 -1
  99. data/lib/sqreen/{rules_callbacks/url_matches.rb → rules/url_matches_cb.rb} +1 -1
  100. data/lib/sqreen/{rules_callbacks/user_agent_matches.rb → rules/user_agent_matches_cb.rb} +1 -1
  101. data/lib/sqreen/{rules_callbacks/waf.rb → rules/waf_cb.rb} +7 -3
  102. data/lib/sqreen/{rules_callbacks/reflected_xss.rb → rules/xss_cb.rb} +10 -7
  103. data/lib/sqreen/run_when_called_cb.rb +21 -0
  104. data/lib/sqreen/sensitive_data_redactor.rb +111 -0
  105. data/lib/sqreen/signature_verifier.rb +20 -0
  106. data/lib/sqreen/sinatra_middleware.rb +14 -0
  107. data/lib/sqreen/{rules_signature.rb → sqreen_signed_verifier.rb} +5 -17
  108. data/lib/sqreen/token_invalid_exception.rb +8 -0
  109. data/lib/sqreen/token_not_found_exception.rb +9 -0
  110. data/lib/sqreen/trie.rb +3 -64
  111. data/lib/sqreen/unauthorized.rb +8 -0
  112. data/lib/sqreen/util.rb +2 -0
  113. data/lib/sqreen/util/capped_array.rb +30 -0
  114. data/lib/sqreen/util/capped_hash.rb +36 -0
  115. data/lib/sqreen/util/capped_string.rb +22 -0
  116. data/lib/sqreen/util/capper.rb +57 -0
  117. data/lib/sqreen/version.rb +1 -1
  118. data/lib/sqreen/waf_error.rb +18 -0
  119. metadata +85 -36
  120. data/lib/sqreen/rules_callbacks.rb +0 -36
  121. data/lib/sqreen/rules_callbacks/inspect_rule.rb +0 -25
@@ -0,0 +1,32 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
+
4
+ require 'sqreen/exception'
5
+ require 'sqreen/actions/base'
6
+ require 'sqreen/actions/ip_range_indexed_action_class'
7
+
8
+ module Sqreen
9
+ module Actions
10
+ # Block a list of IP address ranges. Standard "raise" behavior.
11
+ class BlockIp < Base
12
+ extend IpRangeIndexedActionClass
13
+
14
+ self.type_name = 'block_ip'
15
+
16
+ def initialize(id, opts, _params = {})
17
+ # no need to store the ranges for this action, the index filter the class
18
+ super(id, opts)
19
+ end
20
+
21
+ def do_run(client_ip)
22
+ e = Sqreen::AttackBlocked.new("Blocked client's IP #{client_ip} " \
23
+ "(action: #{id}). No action is required")
24
+ { :status => :raise, :exception => e, :skip_rem_cbs => true }
25
+ end
26
+
27
+ def event_properties(client_ip)
28
+ { 'ip_address' => client_ip }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
+
4
+ require 'sqreen/log'
5
+ require 'sqreen/exception'
6
+ require 'sqreen/actions/base'
7
+ require 'sqreen/actions/user_action_class'
8
+
9
+ module Sqreen
10
+ module Actions
11
+ # Blocks a user at the point Sqreen::identify()
12
+ # or Sqreen::auth_track() are called
13
+ class BlockUser < Base
14
+ extend UserActionClass
15
+
16
+ self.type_name = 'block_user'
17
+
18
+ def initialize(id, opts, _params = {})
19
+ super(id, opts)
20
+ end
21
+
22
+ def do_run(identity_params)
23
+ Sqreen.log.info(
24
+ "Will raise due to user being blocked by action #{id}. " \
25
+ "Blocked user identity: #{identity_params}"
26
+ )
27
+
28
+ e = Sqreen::AttackBlocked.new(
29
+ "Blocked user with identity #{identity_params} " \
30
+ 'due to automatic security response. No action is required'
31
+ )
32
+
33
+ {
34
+ :status => :raise,
35
+ :exception => e,
36
+ }
37
+ end
38
+
39
+ def event_properties(identity_params)
40
+ { 'user' => identity_params }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
+
4
+ require 'sqreen/actions/ip_ranges_index'
5
+
6
+ module Sqreen
7
+ module Actions
8
+ module IpRangeIndexedActionClass
9
+ include IpRangesIndex
10
+
11
+ def actions_matching(client_ip)
12
+ matching_actions client_ip
13
+ end
14
+
15
+ def index(params, action)
16
+ ranges = parse_ip_ranges params
17
+
18
+ ranges.each do |r|
19
+ add_prefix r, action
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ # returns array of prefixes in string form
26
+ def parse_ip_ranges(params)
27
+ ranges = params['ip_cidr']
28
+ unless ranges && ranges.is_a?(Array) && !ranges.empty?
29
+ raise 'no non-empty ip_cidr array present'
30
+ end
31
+
32
+ ranges
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
+
4
+ require 'ipaddr'
5
+ require 'sqreen/trie'
6
+ require 'sqreen/prefix'
7
+
8
+ module Sqreen
9
+ module Actions
10
+ module IpRangesIndex
11
+ def add_prefix(prefix_str, data)
12
+ @trie_v4 ||= Sqreen::Trie.new
13
+ @trie_v6 ||= Sqreen::Trie.new(nil, nil, Socket::AF_INET6)
14
+ prefix = Sqreen::Prefix.from_str(prefix_str, data)
15
+ trie = prefix.family == Socket::AF_INET6 ? @trie_v6 : @trie_v4
16
+ trie.insert prefix
17
+ end
18
+
19
+ def matching_actions(client_ip)
20
+ parsed_ip = IPAddr.new(client_ip.gsub(/%[^%\/]+/, ''))
21
+ trie = parsed_ip.family == Socket::AF_INET6 ? @trie_v6 : @trie_v4
22
+ return [] unless trie
23
+ found = trie.search_matching(parsed_ip.to_i, parsed_ip.family)
24
+ return [] unless found.size > 0
25
+
26
+ Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
27
+ found.map(&:data)
28
+ end
29
+
30
+ def clear
31
+ @trie_v4 = Sqreen::Trie.new
32
+ @trie_v6 = Sqreen::Trie.new(nil, nil, Socket::AF_INET6)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
+
4
+ require 'sqreen/log'
5
+ require 'sqreen/exception'
6
+ require 'sqreen/actions/base'
7
+
8
+ module Sqreen
9
+ module Actions
10
+ # Block a list of IP address ranges by forcefully redirecting the user
11
+ # to a specific URL.
12
+ class RedirectIp < Base
13
+ extend IpRangeIndexedActionClass
14
+
15
+ self.type_name = 'redirect_ip'
16
+
17
+ attr_reader :redirect_url
18
+
19
+ def initialize(id, opts, params = {})
20
+ super(id, opts)
21
+ @redirect_url = params['url']
22
+ raise "no url provided for action #{id}" unless @redirect_url
23
+ end
24
+
25
+ def do_run(client_ip)
26
+ Sqreen.log.info "Will request redirect for client with IP #{client_ip} " \
27
+ "(action: #{id})."
28
+ {
29
+ :status => :skip,
30
+ :new_return_value => [303, { 'Location' => @redirect_url }, ['']],
31
+ :skip_rem_cbs => true,
32
+ }
33
+ end
34
+
35
+ def event_properties(client_ip)
36
+ { 'ip_address' => client_ip, 'url' => @redirect_url }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
+
4
+ require 'sqreen/log'
5
+ require 'sqreen/exception'
6
+ require 'sqreen/actions/base'
7
+ require 'sqreen/actions/user_action_class'
8
+
9
+ module Sqreen
10
+ module Actions
11
+ # Redirects a user at the point Sqreen::identify()
12
+ # or Sqreen::auth_track() are called
13
+ class RedirectUser < Base
14
+ extend UserActionClass
15
+
16
+ self.type_name = 'redirect_user'
17
+
18
+ def initialize(id, opts, params = {})
19
+ super(id, opts)
20
+ @redirect_url = params['url']
21
+ raise "no url provided for action #{id}" unless @redirect_url
22
+ end
23
+
24
+ def do_run(identity_params)
25
+ Sqreen.log.info 'Will request redirect for user with identity ' \
26
+ "#{identity_params} (action: #{id})."
27
+
28
+ e = Sqreen::AttackBlocked.new(
29
+ "Redirected user with identity #{identity_params} " \
30
+ 'due to automatic security response. No action is required'
31
+ )
32
+ e.redirect_url = @redirect_url
33
+
34
+ {
35
+ :status => :raise,
36
+ :exception => e,
37
+ }
38
+ end
39
+
40
+ def event_properties(identity_params)
41
+ { 'user' => identity_params }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
+
4
+ module Sqreen
5
+ module Actions
6
+ # Where the currently loaded actions are stored. Singleton
7
+ class Repository
8
+ include Singleton
9
+
10
+ def add(params, action)
11
+ action.class.index(params || {}, action)
12
+ end
13
+
14
+ def get(action_class, key)
15
+ action_class = Base.get_type_class(action_class) unless action_class.class == Class
16
+ action_class.actions_matching key
17
+ end
18
+
19
+ def clear
20
+ Base.known_subclasses.each(&:clear)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
+
4
+ module Sqreen
5
+ # Implements actions (behavior taken in response to agent signals)
6
+ module Actions
7
+ # Exception for when an unknown action type is gotten from the server
8
+ class UnknownActionType < ::Sqreen::Exception
9
+ attr_reader :action_type
10
+ def initialize(action_type)
11
+ super("no such action type: #{action_type}. Must be one of #{Base.known_types}")
12
+ @action_type = action_type
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
+
4
+ require 'sqreen/exception'
5
+
6
+ module Sqreen
7
+ module Actions
8
+ module UserActionClass
9
+ def actions_matching(identity_params)
10
+ return [] unless @idx
11
+ key = stringify_keys(identity_params)
12
+ actions = @idx[key]
13
+ actions || []
14
+ end
15
+
16
+ def index(params, action)
17
+ @idx ||= {}
18
+ users = params['users']
19
+ raise ::Sqreen::Exception, 'nil "users" param for block_user action' if users.nil?
20
+ raise ::Sqreen::Exception, '"users" param must be an array' unless users.is_a? Array
21
+
22
+ users.each do |u|
23
+ @idx[u] ||= []
24
+ @idx[u] << action
25
+ end
26
+ end
27
+
28
+ def clear
29
+ @idx = {}
30
+ end
31
+
32
+ private
33
+
34
+ def stringify_keys(hash)
35
+ Hash[
36
+ hash.map { |k, v| [k.to_s, v] }
37
+ ]
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/sqreen/agent.rb CHANGED
@@ -5,10 +5,13 @@ require 'sqreen/version'
5
5
  require 'sqreen/instrumentation'
6
6
  require 'sqreen/session'
7
7
  require 'sqreen/runner'
8
- require 'sqreen/callbacks'
9
8
  require 'sqreen/log'
10
9
  require 'sqreen/exception'
11
10
  require 'sqreen/configuration'
11
+ require 'sqreen/cb'
12
+ require 'sqreen/default_cb'
13
+ require 'sqreen/run_when_called_cb'
14
+ require 'sqreen/framework_cb'
12
15
  require 'sqreen/events/attack'
13
16
  require 'sqreen/sdk'
14
17
  require 'sqreen/dependency/detector'
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
+
4
+ require 'sqreen/exception'
5
+
6
+ module Sqreen
7
+ # This exception name is particularly important since it is often seen by
8
+ # Sqreen users when watching their logs. It should not raise any concern to
9
+ # them.
10
+ class AttackBlocked < Sqreen::Exception
11
+ attr_accessor :redirect_url
12
+
13
+ def log_message(msg)
14
+ Sqreen.log.warn(msg)
15
+ end
16
+ end
17
+ end
@@ -3,12 +3,13 @@
3
3
 
4
4
  require 'strscan'
5
5
  require 'sqreen/exception'
6
- require 'set'
6
+
7
+ require 'sqreen/binding_accessor/path_elem'
8
+ require 'sqreen/binding_accessor/transforms'
7
9
 
8
10
  module Sqreen
9
11
  # the value located at the given binding
10
12
  class BindingAccessor
11
- PathElem = Struct.new(:kind, :value)
12
13
  attr_reader :path, :expression, :final_transform, :transform_args
13
14
 
14
15
  # Expression to be accessed
@@ -67,6 +68,7 @@ module Sqreen
67
68
  CONSTANT_KIND = 'constant'.freeze
68
69
  METHOD_KIND = 'method'.freeze
69
70
  INDEX_KIND = 'index'.freeze
71
+ NIL_KIND = 'nil'.freeze
70
72
  SQREEN_VAR_KIND = 'sqreen-variable'.freeze
71
73
 
72
74
  if binding.respond_to?(:local_variable_get)
@@ -101,6 +103,8 @@ module Sqreen
101
103
  current_value.send(component[:value])
102
104
  when INDEX_KIND
103
105
  current_value[component[:value]]
106
+ when NIL_KIND
107
+ current_value
104
108
  when SQREEN_VAR_KIND
105
109
  resolve_sqreen_variable(component[:value], *env)
106
110
  else
@@ -176,6 +180,8 @@ module Sqreen
176
180
  PathElem.new(SYMBOL_KIND, @scan[1].to_sym)
177
181
  elsif @scan.scan(/'((?:\\.|[^\\'])*)'/)
178
182
  PathElem.new(STRING_KIND, @scan[1])
183
+ elsif @scan.scan(/nil/)
184
+ PathElem.new(NIL_KIND, nil)
179
185
  end
180
186
  end
181
187
 
@@ -248,110 +254,11 @@ module Sqreen
248
254
  end
249
255
  end
250
256
 
251
- # Available final transformations
252
- module Transforms
253
- def flat_keys(value, max_iter = 1000)
254
- return nil if value.nil?
255
- seen = Set.new
256
- look_into = [value]
257
- keys = []
258
- idx = 0
259
- until look_into.empty? || max_iter <= idx
260
- idx += 1
261
- val = look_into.pop
262
- next unless seen.add?(val.object_id)
263
- case val
264
- when Hash
265
- keys.concat(val.keys)
266
- look_into.concat(val.values)
267
- when Array
268
- look_into.concat(val)
269
- else
270
- next if val.respond_to?(:seek)
271
- val.each { |v| look_into << v } if val.respond_to?(:each)
272
- end
273
- end
274
- keys
275
- end
276
-
277
- def flat_values(value, max_iter = 1000)
278
- return nil if value.nil?
279
- seen = Set.new
280
- look_into = [value]
281
- values = []
282
- idx = 0
283
- until look_into.empty? || max_iter <= idx
284
- idx += 1
285
- val = look_into.shift
286
- next unless seen.add?(val.object_id)
287
- case val
288
- when Hash
289
- look_into.concat(val.values)
290
- when Array
291
- look_into.concat(val)
292
- else
293
- next if val.respond_to?(:seek)
294
- if val.respond_to?(:each)
295
- val.each { |v| look_into << v }
296
- else
297
- values << val
298
- end
299
- end
300
- end
301
- values
302
- end
303
-
304
- def concat_keys_and_values(value, max_size = nil)
305
- return nil if value.nil?
306
- values = Set.new
307
- max_size = max_size.to_i if max_size
308
- res = ''
309
- descend(value) do |x|
310
- next unless values.add?(x)
311
- x = x.to_s
312
- return res if max_size && res.size + x.size + 1 > max_size
313
- res << "\n" unless res.empty?
314
- res << x
315
- end
316
- res
317
- end
318
-
319
- private
320
-
321
- def descend(value, max_iter = 1000)
322
- seen = Set.new
323
- look_into = [value]
324
- idx = 0
325
- until look_into.empty? || max_iter <= idx
326
- idx += 1
327
- val = look_into.pop
328
-
329
- case val
330
- when Hash
331
- next unless seen.add?(val.object_id)
332
- look_into.concat(val.keys)
333
- look_into.concat(val.values)
334
- when Array
335
- next unless seen.add?(val.object_id)
336
- look_into.concat(val)
337
- else
338
- next if val.respond_to?(:seek)
339
- if val.respond_to?(:each)
340
- next unless seen.add?(val.object_id)
341
- val.each { |v| look_into << v }
342
- else
343
- yield val
344
- end
345
- end
346
- end
347
- end
348
- end # end module Transforms
349
-
350
257
  include Transforms
351
258
  KNOWN_TRANSFORMS = Transforms.public_instance_methods.map(&:to_s)
352
259
 
353
260
  def transform(value)
354
261
  send(@final_transform, value, *@transform_args) if @final_transform
355
262
  end
356
- end # end class BindingAccessor
263
+ end
357
264
  end