sqreen 1.18.3.beta1 → 1.18.3.beta2

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.
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