sqreen 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: bc6cce74b4d4613315018a182cc2d908cef00eb0
|
4
|
+
data.tar.gz: 3aee24c4aff6693fd17f9333408a1b012f468ae5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 156f389b4753efe02dab840026988ab5981417a79f95a9d540dc3941ce66761354feafed02b07cb4adab50bb10fce0a5bff0162f8f7b5f5195a25a6c15aa0828
|
7
|
+
data.tar.gz: 737febf536e98c7fd1753c0e4d55f843c2546fe3ae010e167dfbe83b0351628146f3ca0b27f7b24cb2800ffdd37f9ec993ab3b6b1eb59b4dae46f1b8a71cb835
|
@@ -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
|