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 +4 -4
- data/lib/sqreen/attack_detected.html +2 -0
- data/lib/sqreen/binding_accessor.rb +247 -157
- data/lib/sqreen/condition_evaluator.rb +191 -189
- data/lib/sqreen/frameworks/generic.rb +6 -0
- data/lib/sqreen/frameworks/rails.rb +1 -0
- data/lib/sqreen/frameworks/sinatra.rb +18 -0
- data/lib/sqreen/middleware.rb +10 -0
- data/lib/sqreen/rules_callbacks.rb +3 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +75 -0
- data/lib/sqreen/rules_callbacks/custom_error.rb +57 -0
- data/lib/sqreen/rules_callbacks/execjs.rb +4 -7
- data/lib/sqreen/rules_callbacks/matcher_rule.rb +26 -20
- data/lib/sqreen/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6b96afe645e834947aa8a4ba899a0c94d9b8037
|
4
|
+
data.tar.gz: b09d59bff4ba050f2d3409c2896e7df6ba26c5b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
44
|
+
protected
|
33
45
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
51
|
-
def
|
52
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
166
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|