sqreen 1.5.0-java → 1.6.0-java

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
  SHA1:
3
- metadata.gz: 7dabc8645b6eaca59937e5742c95229b6a4016b4
4
- data.tar.gz: 0a9b782a69bd8b807d4bfc6077bad91b9e9b537f
3
+ metadata.gz: b6b96afe645e834947aa8a4ba899a0c94d9b8037
4
+ data.tar.gz: b09d59bff4ba050f2d3409c2896e7df6ba26c5b7
5
5
  SHA512:
6
- metadata.gz: 7d2f9dd053786379e7b8a2834bb4e73499c7a4d74ab1835710ea4341196b428b71759d905543e108eede149b1ea7ba2fb47ce8741a63424f019a43113f7708dd
7
- data.tar.gz: df8b05e55f77b2a34bdbcc89c5dbe0586fa476597c4784a20818418880bba75d43c3ee6176125f7594fccf9a26a9e3e8c1ff53b4947d63438757360267c3696f
6
+ metadata.gz: bf2acd08c3047ba024c7479859118cd5b4eeef937a5562184bb83c329bc60d266ccf35d6da7318e59d8b3fc79a0d33ec9fd0d888a37f3afc70306507cde7a700
7
+ data.tar.gz: b22575e52f4df22aea73f12bd1b719238fbcd7062c659f75d70c69c0c65a98112efca7c887b81847a74be27c76cc02d4afbe110ecb90be2c245eca90a1524efc
@@ -0,0 +1,2 @@
1
+ <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Sqreen has detected an attack.</title> <style>html,body,div,span,h1,a{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1}svg,h1,p{display:block}svg{margin:0 auto 4vh}h1{font-family:sans-serif;font-weight:300;font-size:34px;color:#384886;line-height:normal}p{font-size:18px;line-height:normal;color:#b8bccc;font-family:sans-serif;font-weight:300}a{color:#b8bccc}.flex{text-align:center}</style></head><body> <div class="flex"> <svg xmlns="http://www.w3.org/2000/svg" width="230" height="250" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <path id="b" d="M176.55 22.93v-.03c-.17-1.13-.8-2.1-1.9-2.77C170.93 18.07 136.8.03 88.98 0 40.25 0 4.8 19.28 3.28 20.14c-.98.6-1.63 1.6-1.77 2.72-14.8 123.1 84.7 176.2 85.7 176.7.6.3 1.2.44 1.8.44.6 0 1.2-.15 1.7-.42 1-.52 100.4-55.03 85.9-176.65z"/> <filter id="a" width="151.7%" height="146%" x="-25.8%" y="-16%" filterUnits="objectBoundingBox"> <feOffset dy="14" in="SourceAlpha" result="shadowOffsetOuter1"/> <feGaussianBlur stdDeviation="13" in="shadowOffsetOuter1" result="shadowBlurOuter1"/> <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowBlurOuter1"/> </filter> </defs> <g fill="none" fill-rule="evenodd"> <g fill-rule="nonzero" transform="translate(26 12)"> <use fill="#000" filter="url(#a)" xlink:href="#b"/> <use fill="#FFF" fill-rule="evenodd" xlink:href="#b"/> </g> <path fill="#374886" d="M153.85 75.8s0 .68-.35 1.13c-.46.57-8.52 10.74-12.32 15.6-3.8 4.98-11.52 2.26-11.52 2.26s2.54-1.5 4.15-3.4c4.3-5.3 12.5-15.7 12.5-15.7s-8.2-10.2-12.4-15.6c-1.5-2-4.1-3.4-4.1-3.4s7.7-2.6 11.5 2.2c3.8 4.9 11.9 15.1 12.2 15.7.5.4.5 1.1.5 1.1m-78 .1s0 .6.4 1.1c.4.4 8.5 10.7 12.2 15.6 3.7 4.8 11.5 2.2 11.5 2.2s-2.5-1.5-4.1-3.4c-4.3-5.2-12.5-15.5-12.5-15.5l12.4-15.7c1.5-2 4.2-3.4 4.2-3.4s-7.7-2.6-11.5 2.2C84.8 63.7 76.9 74 76.5 74.6c-.32.55-.44 1.12-.44 1.12M115 62.82c-7.03 0-12.9 5.78-12.9 12.9s5.75 12.9 12.9 12.9c7.13 0 12.9-5.8 12.9-12.9s-5.9-12.9-12.9-12.9m-33.38 58.6c3.46 0 5.4-1.77 5.4-4.1 0-5.15-7.4-3.56-7.4-5.47 0-.8.78-1.3 1.97-1.3 1.5 0 2.9.6 3.7 1.5l1.3-2.3c-1.3-1-2.9-1.8-5.1-1.8-3.3 0-5.1 1.9-5.1 4 0 5 7.5 3.3 7.5 5.4 0 .8-.6 1.4-2.1 1.4s-3.4-.9-4.3-1.8l-1.4 2.4c1.4 1.2 3.4 2 5.6 2zm13.53-3c-1.8 0-3.1-1.5-3.1-3.7s1.3-3.7 3.2-3.7c1.1 0 2.3.6 2.8 1.44v4.5c-.5.8-1.7 1.46-2.8 1.46zm-1 3c1.5 0 2.9-.64 3.9-1.96v6.5h3.3v-17.63H98v1.6c-.96-1.23-2.33-1.92-3.86-1.92-3.2 0-5.52 2.5-5.52 6.7 0 4.3 2.33 6.7 5.53 6.7zm13.7-.32v-8.42c.6-.8 2-1.4 3.1-1.4.4 0 .7.1.9.1v-3.3c-1.5 0-3.1 1-3.9 2.1v-1.7h-3.3v12.8h3.3zm11.9.33c2 0 3.9-.6 5.2-1.77l-1.4-2.2c-.8.8-2.26 1.2-3.32 1.2-2.1 0-3.4-1.4-3.6-3h9.3v-.7c0-4.2-2.53-7.1-6.25-7.1-3.8 0-6.45 3-6.45 6.7 0 4.05 2.8 6.7 6.6 6.7zm2.9-7.9h-6.1c.2-1.27 1.07-2.83 3.1-2.83 2.18 0 3 1.6 3.08 2.87zm11.4 7.9c2 0 3.9-.6 5.2-1.77l-1.42-2.2c-.8.8-2.3 1.2-3.34 1.2-2.2 0-3.4-1.4-3.6-3h9.2v-.7c0-4.2-2.6-7.1-6.3-7.1-3.8 0-6.4 3-6.4 6.7 0 4.05 2.82 6.7 6.62 6.7zm2.9-7.9h-6.1c.17-1.27 1.05-2.83 3.1-2.83 2.2 0 3 1.6 3.1 2.87zm17.3 7.58v-9c0-2.5-1.3-4-4.03-4-2.08 0-3.6 1-4.4 2v-1.64h-3.3v12.76h3.3v-8.6c.53-.74 1.53-1.5 2.82-1.5 1.4 0 2.3.63 2.3 2.4v7.7h3.2z"/> </g> </svg> <h1>Uh Oh! Sqreen has detected an attack.</h1> <p>If you are the application owner, check the Sqreen <a href="https://my.sqreen.io/">dashboard</a> for more information.</p></div></body></html>
2
+
@@ -2,195 +2,285 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
4
  require 'strscan'
5
+ require 'sqreen/exception'
6
+ require 'set'
5
7
 
6
- # the value located at the given binding
7
- class BindingAccessor
8
- PathElem = Struct.new(:kind, :value)
9
- attr_reader :path, :expression
10
-
11
- # Expression to be accessed
12
- # @param expression [String] expression to read
13
- # @param convert [Boolean] wheter to convert objects to simpler types (Array, Hash, String...)
14
- def initialize(expression, convert = false)
15
- @expression = expression
16
- @path = []
17
- @convert = convert
18
- parse(expression)
19
- end
8
+ module Sqreen
9
+ # the value located at the given binding
10
+ class BindingAccessor
11
+ PathElem = Struct.new(:kind, :value)
12
+ attr_reader :path, :expression, :final_transform
20
13
 
21
- # Resolve this expression for the give binding
22
- def resolve(binding, framework = nil, instance = nil, arguments = nil, cbdata = nil, last_return = nil)
23
- env = [framework, instance, arguments, cbdata, last_return]
24
- value = nil
25
- @path.each do |component|
26
- value = resolve_component(value, component, binding, env)
14
+ # Expression to be accessed
15
+ # @param expression [String] expression to read
16
+ # @param convert [Boolean] wheter to convert objects to
17
+ # simpler types (Array, Hash, String...)
18
+ def initialize(expression, convert = false)
19
+ @final_transform = nil
20
+ @expression = expression
21
+ @path = []
22
+ @convert = convert
23
+ parse(expression)
24
+ end
25
+
26
+ # Access data from the expression
27
+ def access(binding, framework = nil, instance = nil, arguments = nil, cbdata = nil, last_return = nil)
28
+ env = [framework, instance, arguments, cbdata, last_return]
29
+ value = nil
30
+ @path.each do |component|
31
+ value = resolve_component(value, component, binding, env)
32
+ end
33
+ value
34
+ end
35
+
36
+ # access and transform expression for the given binding
37
+ def resolve(*args)
38
+ value = access(*args)
39
+ value = transform(value) if @final_transform
40
+ return convert(value) if @convert
41
+ value
27
42
  end
28
- return convert(value) if @convert
29
- value
30
- end
31
43
 
32
- protected
44
+ protected
33
45
 
34
- STRING_KIND = 'string'.freeze
35
- SYMBOL_KIND = 'symbol'.freeze
36
- INTEGER_KIND = 'integer'.freeze
37
- LOCAL_VAR_KIND = 'local-variable'.freeze
38
- INSTANCE_VAR_KIND = 'instance-variable'.freeze
39
- CLASS_VAR_KIND = 'class-variable'.freeze
40
- GLOBAL_VAR_KIND = 'global-variable'.freeze
41
- CONSTANT_KIND = 'constant'.freeze
42
- METHOD_KIND = 'method'.freeze
43
- INDEX_KIND = 'index'.freeze
44
- SQREEN_VAR_KIND = 'sqreen-variable'.freeze
46
+ STRING_KIND = 'string'.freeze
47
+ SYMBOL_KIND = 'symbol'.freeze
48
+ INTEGER_KIND = 'integer'.freeze
49
+ LOCAL_VAR_KIND = 'local-variable'.freeze
50
+ INSTANCE_VAR_KIND = 'instance-variable'.freeze
51
+ CLASS_VAR_KIND = 'class-variable'.freeze
52
+ GLOBAL_VAR_KIND = 'global-variable'.freeze
53
+ CONSTANT_KIND = 'constant'.freeze
54
+ METHOD_KIND = 'method'.freeze
55
+ INDEX_KIND = 'index'.freeze
56
+ SQREEN_VAR_KIND = 'sqreen-variable'.freeze
45
57
 
46
- if binding.respond_to?(:local_variable_get)
47
- def get_local(name, bind)
48
- bind.local_variable_get(name)
58
+ if binding.respond_to?(:local_variable_get)
59
+ def get_local(name, bind)
60
+ bind.local_variable_get(name)
61
+ end
62
+ else
63
+ def get_local(name, bind)
64
+ eval(name, bind)
65
+ end
49
66
  end
50
- else
51
- def get_local(name, bind)
52
- eval(name, bind)
67
+
68
+ def resolve_component(current_value, component, binding, env)
69
+ case component[:kind]
70
+ when STRING_KIND, SYMBOL_KIND, INTEGER_KIND
71
+ component[:value]
72
+ when LOCAL_VAR_KIND
73
+ get_local(component[:value], binding)
74
+ when INSTANCE_VAR_KIND
75
+ current_value.instance_variable_get("@#{component[:value]}")
76
+ when CLASS_VAR_KIND
77
+ current_value.class.class_variable_get("@@#{component[:value]}")
78
+ when GLOBAL_VAR_KIND
79
+ instance_eval("$#{component[:value]}")
80
+ when CONSTANT_KIND
81
+ if current_value
82
+ current_value.const_get(component[:value].to_s)
83
+ else
84
+ Object.const_get(component[:value].to_s)
85
+ end
86
+ when METHOD_KIND
87
+ current_value.send(component[:value])
88
+ when INDEX_KIND
89
+ current_value[component[:value]]
90
+ when SQREEN_VAR_KIND
91
+ resolve_sqreen_variable(component[:value], *env)
92
+ else
93
+ raise "Do not know how to handle this component #{component.inspect}"
94
+ end
53
95
  end
54
- end
55
96
 
56
- def resolve_component(current_value, component, binding, env)
57
- case component[:kind]
58
- when STRING_KIND, SYMBOL_KIND, INTEGER_KIND
59
- component[:value]
60
- when LOCAL_VAR_KIND
61
- get_local(component[:value], binding)
62
- when INSTANCE_VAR_KIND
63
- current_value.instance_variable_get("@#{component[:value]}")
64
- when CLASS_VAR_KIND
65
- current_value.class.class_variable_get("@@#{component[:value]}")
66
- when GLOBAL_VAR_KIND
67
- instance_eval("$#{component[:value]}")
68
- when CONSTANT_KIND
69
- if current_value
70
- current_value.const_get(component[:value].to_s)
97
+ def resolve_sqreen_variable(what, framework, instance, args, cbdata, rv)
98
+ case what
99
+ when 'data'
100
+ cbdata
101
+ when 'rv'
102
+ rv
103
+ when 'args'
104
+ args
105
+ when 'inst'
106
+ instance
71
107
  else
72
- Object.const_get(component[:value].to_s)
108
+ framework.send(what)
73
109
  end
74
- when METHOD_KIND
75
- current_value.send(component[:value])
76
- when INDEX_KIND
77
- current_value[component[:value]]
78
- when SQREEN_VAR_KIND
79
- resolve_sqreen_variable(component[:value], *env)
80
- else
81
- raise "Do not know how to handle this component #{component.inspect}"
82
110
  end
83
- end
84
111
 
85
- def resolve_sqreen_variable(what, framework, instance, args, cbdata, rv)
86
- case what
87
- when 'data'
88
- cbdata
89
- when 'rv'
90
- rv
91
- when 'args'
92
- args
93
- when 'inst'
94
- instance
95
- else
96
- framework.send(what)
112
+ def parse(expression)
113
+ expression = extract_transform(expression)
114
+ @scan = StringScanner.new(expression)
115
+ until @scan.eos?
116
+ pos = @scan.pos
117
+ scalar = scan_scalar
118
+ if scalar
119
+ @path.push scalar
120
+ return
121
+ end
122
+ if @path.empty?
123
+ scan_push_variable
124
+ else
125
+ scan_push_method
126
+ end
127
+ scan_push_indexes
128
+ scan_push_more_constant if @scan.scan(/\./).nil?
129
+ raise Sqreen::Exception, error_state('Scan stuck') if @scan.pos == pos
130
+ end
131
+ ensure
132
+ @scan = nil
133
+ end
134
+
135
+ def extract_transform(expression)
136
+ parts = expression.split('|')
137
+ self.final_transform = parts.pop if parts.size > 1
138
+ parts.join('|').rstrip
97
139
  end
98
- end
99
140
 
100
- def parse(expression)
101
- @scan = StringScanner.new(expression)
102
- until @scan.eos?
103
- pos = @scan.pos
104
- scalar = scan_scalar
105
- if scalar
106
- @path.push scalar
107
- return
141
+ def final_transform=(transform)
142
+ transform.strip!
143
+ unless KNOWN_TRANSFORMS.include?(transform)
144
+ raise Sqreen::Exception, "Invalid transform #{transform}"
108
145
  end
109
- if @path.empty?
110
- scan_push_variable
111
- else
112
- scan_push_method
146
+ @final_transform = transform
147
+ end
148
+
149
+ def scan_scalar
150
+ if @scan.scan(/\d+/)
151
+ PathElem.new(INTEGER_KIND, @scan[0].to_i)
152
+ elsif @scan.scan(/:(\w+)/)
153
+ PathElem.new(SYMBOL_KIND, @scan[1].to_sym)
154
+ elsif @scan.scan(/'((?:\\.|[^\\'])*)'/)
155
+ PathElem.new(STRING_KIND, @scan[1])
113
156
  end
114
- scan_push_indexes
115
- scan_push_more_constant if @scan.scan(/\./).nil?
116
- raise error_state('Scan stuck') if @scan.pos == pos
117
157
  end
118
- ensure
119
- @scan = nil
120
- end
121
158
 
122
- def scan_scalar
123
- if @scan.scan(/\d+/)
124
- return PathElem.new(INTEGER_KIND, @scan[0].to_i)
125
- elsif @scan.scan(/:(\w+)/)
126
- return PathElem.new(SYMBOL_KIND, @scan[1].to_sym)
127
- elsif @scan.scan(/'((?:\\.|[^\\'])*)'/)
128
- return PathElem.new(STRING_KIND, @scan[1])
159
+ RUBY_IDENTIFIER_CHAR = if ''.respond_to? :encoding
160
+ '[\w\u0080-\u{10ffff}]'
161
+ else
162
+ '[\w\x80-\xFF]'
163
+ end
164
+
165
+ def scan_push_constant
166
+ return unless @scan.scan(/([A-Z]#{RUBY_IDENTIFIER_CHAR}+)/)
167
+ @path << PathElem.new(CONSTANT_KIND, @scan[1])
129
168
  end
130
- end
131
169
 
132
- RUBY_IDENTIFIER_CHAR = if ''.respond_to? :encoding
133
- '[\w\u0080-\u{10ffff}]'
134
- else
135
- '[\w\x80-\xFF]'
136
- end
170
+ def scan_push_more_constant
171
+ while @scan.scan(/::/)
172
+ unless scan_push_constant
173
+ raise Sqreen::Exception, error_state('No more constant')
174
+ end
175
+ end
176
+ end
137
177
 
138
- def scan_push_constant
139
- return unless @scan.scan(/([A-Z]#{RUBY_IDENTIFIER_CHAR}+)/)
140
- @path << PathElem.new(CONSTANT_KIND, @scan[1])
141
- end
178
+ def scan_push_variable
179
+ if @scan.scan(/\$(#{RUBY_IDENTIFIER_CHAR}+)/)
180
+ @path << PathElem.new(GLOBAL_VAR_KIND, @scan[1])
181
+ elsif @scan.scan(/@@(#{RUBY_IDENTIFIER_CHAR}+)/)
182
+ @path << PathElem.new(CLASS_VAR_KIND, @scan[1])
183
+ elsif @scan.scan(/@(#{RUBY_IDENTIFIER_CHAR}+)/)
184
+ @path << PathElem.new(INSTANCE_VAR_KIND, @scan[1])
185
+ elsif @scan.scan(/#\.(\w+)/)
186
+ @path << PathElem.new(SQREEN_VAR_KIND, @scan[1])
187
+ elsif scan_push_constant
188
+ nil
189
+ elsif @scan.scan(/(#{RUBY_IDENTIFIER_CHAR}+)/u)
190
+ @path << PathElem.new(LOCAL_VAR_KIND, @scan[1])
191
+ end
192
+ end
142
193
 
143
- def scan_push_more_constant
144
- while @scan.scan(/::/)
145
- raise error_state('No more constant') unless scan_push_constant
194
+ def scan_push_method
195
+ if @scan.scan(/@@(#{RUBY_IDENTIFIER_CHAR}+)/)
196
+ @path << PathElem.new(CLASS_VAR_KIND, @scan[1])
197
+ elsif @scan.scan(/@(#{RUBY_IDENTIFIER_CHAR}+)/)
198
+ @path << PathElem.new(INSTANCE_VAR_KIND, @scan[1])
199
+ elsif @scan.scan(/(#{RUBY_IDENTIFIER_CHAR}+)/)
200
+ @path << PathElem.new(METHOD_KIND, @scan[1])
201
+ end
146
202
  end
147
- end
148
203
 
149
- def scan_push_variable
150
- if @scan.scan(/\$(#{RUBY_IDENTIFIER_CHAR}+)/)
151
- return @path << PathElem.new(GLOBAL_VAR_KIND, @scan[1])
152
- elsif @scan.scan(/@@(#{RUBY_IDENTIFIER_CHAR}+)/)
153
- return @path << PathElem.new(CLASS_VAR_KIND, @scan[1])
154
- elsif @scan.scan(/@(#{RUBY_IDENTIFIER_CHAR}+)/)
155
- return @path << PathElem.new(INSTANCE_VAR_KIND, @scan[1])
156
- elsif @scan.scan(/#\.(\w+)/)
157
- return @path << PathElem.new(SQREEN_VAR_KIND, @scan[1])
158
- elsif scan_push_constant
159
- return
160
- elsif @scan.scan(/(#{RUBY_IDENTIFIER_CHAR}+)/u)
161
- return @path << PathElem.new(LOCAL_VAR_KIND, @scan[1])
204
+ def scan_push_indexes
205
+ while @scan.scan(/\[/)
206
+ scalar = scan_scalar
207
+ raise Sqreen::Exception, error_state('Invalid index') unless scalar
208
+ unless @scan.scan(/\]/)
209
+ raise Sqreen::Exception, error_state('Unfinished index')
210
+ end
211
+ @path << PathElem.new(INDEX_KIND, scalar[:value])
212
+ end
162
213
  end
163
- end
164
214
 
165
- def scan_push_method
166
- if @scan.scan(/@@(#{RUBY_IDENTIFIER_CHAR}+)/)
167
- return @path << PathElem.new(CLASS_VAR_KIND, @scan[1])
168
- elsif @scan.scan(/@(#{RUBY_IDENTIFIER_CHAR}+)/)
169
- return @path << PathElem.new(INSTANCE_VAR_KIND, @scan[1])
170
- elsif @scan.scan(/(#{RUBY_IDENTIFIER_CHAR}+)/)
171
- return @path << PathElem.new(METHOD_KIND, @scan[1])
215
+ def error_state(msg)
216
+ "#{msg} at #{@scan.pos} after #{@scan.string[0...@scan.pos]} (#{@scan.string})"
172
217
  end
173
- end
174
218
 
175
- def scan_push_indexes
176
- while @scan.scan(/\[/)
177
- scalar = scan_scalar
178
- raise error_state('Invalid index') unless scalar
179
- raise error_state('Unfinished index') unless @scan.scan(/\]/)
180
- @path << PathElem.new(INDEX_KIND, scalar[:value])
219
+ def convert(value)
220
+ case value
221
+ when ::Exception
222
+ { 'message' => value.message, 'backtrace' => value.backtrace }
223
+ else
224
+ value
225
+ end
181
226
  end
182
- end
183
227
 
184
- def error_state(msg)
185
- "#{msg} at #{@scan.pos} after #{@scan.string[0...@scan.pos]} (#{@scan.string})"
186
- end
228
+ # Available final transformations
229
+ module Transforms
230
+ def flat_keys(value, max_iter = 1000)
231
+ return nil if value.nil?
232
+ seen = Set.new
233
+ look_into = [value]
234
+ keys = []
235
+ idx = 0
236
+ until look_into.empty? || max_iter <= idx
237
+ idx += 1
238
+ val = look_into.pop
239
+ next unless seen.add?(val.object_id)
240
+ case val
241
+ when Hash
242
+ keys.concat(val.keys)
243
+ look_into.concat(val.values)
244
+ when Array
245
+ look_into.concat(val)
246
+ else
247
+ val.each { |v| look_into << v } if val.respond_to?(:each)
248
+ end
249
+ end
250
+ keys
251
+ end
187
252
 
188
- def convert(value)
189
- case value
190
- when Exception
191
- return { 'message' => value.message, 'backtrace' => value.backtrace }
192
- else
193
- return value
253
+ def flat_values(value, max_iter = 1000)
254
+ return nil if value.nil?
255
+ seen = Set.new
256
+ look_into = [value]
257
+ values = []
258
+ idx = 0
259
+ until look_into.empty? || max_iter <= idx
260
+ idx += 1
261
+ val = look_into.shift
262
+ next unless seen.add?(val.object_id)
263
+ case val
264
+ when Hash
265
+ look_into.concat(val.values)
266
+ when Array
267
+ look_into.concat(val)
268
+ else
269
+ if val.respond_to?(:each)
270
+ val.each { |v| look_into << v }
271
+ else
272
+ values << val
273
+ end
274
+ end
275
+ end
276
+ values
277
+ end
278
+ end
279
+ include Transforms
280
+ KNOWN_TRANSFORMS = Transforms.public_instance_methods.map(&:to_s)
281
+
282
+ def transform(value)
283
+ send(@final_transform, value) if @final_transform
194
284
  end
195
285
  end
196
286
  end