sqreen 1.13.4-java → 1.14.0.beta3-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
- SHA1:
3
- metadata.gz: 2d9abe0c770383b57205c2eb661c33ed286ed18a
4
- data.tar.gz: d802f183cc3fe90149eb1e1ccce5faba70be653d
2
+ SHA256:
3
+ metadata.gz: c4e8efa6908940531662b925941a3bc59413670b80a48c9e758932abd7647b5e
4
+ data.tar.gz: bd56cf6968cd82cc05bee7294b3de887064203b352abcc84e15c0945792fd6e0
5
5
  SHA512:
6
- metadata.gz: c4f72bc138efcde980f98924266e8dd861a46aadf2221a0e17a8520c6066bfa921c8cfdf5407143b8c8a20fa0340ced7dd02a784ea62fc86fd3079902b9de909
7
- data.tar.gz: cbb3d0a5123068c0e23739d4261b731917d4ce77c9c8ceac854610b9df39b994bfe69ca23a23148a2346f7644fa4452cea468ae8737d1ff27213afe991f625bd
6
+ metadata.gz: e6ca675f7878bf9484867e90313b0a058599413b7f29c4f1d502dc103d70d6f90a1a084d0c8c547d74a3a80019b0fd2770d1aca7c2f9668fad90d2da6cf29ff1
7
+ data.tar.gz: 11c11c046eb0f92b3c4a15c6b95c2d33d73aba144ec9dd43f11693849e63c9e94c5e8aeae2139e7b8d1e6e274da6d674000a0efd66f4560a36f536bdce0b1c55
@@ -0,0 +1,25 @@
1
+ require 'execjs'
2
+
3
+ module Sqreen
4
+ module Js
5
+ class ExecjsAdapter < JsServiceAdapter
6
+ def preprocess(_rule_name, code)
7
+ ExecJsRunnable.new(ExecJS.compile(code))
8
+ end
9
+
10
+ def variant_name
11
+ ExecJS.runtime.name + ' (ExecJS)'
12
+ end
13
+ end
14
+
15
+ class ExecJsRunnable < ExecutableJs
16
+ def initialize(compiled)
17
+ @compiled = compiled
18
+ end
19
+
20
+ def run_js_cb(cbname, _budget, arguments)
21
+ @compiled.call(cbname, *arguments)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,87 @@
1
+ require 'sqreen/exception'
2
+
3
+ module Sqreen
4
+ module Js
5
+ # start public interface
6
+
7
+ class JsService
8
+ include Singleton
9
+
10
+ def initialize
11
+ detect_adapter
12
+ end
13
+
14
+ # @return [Sqreen::Js::ExecutableJs]
15
+ def prepare(rule_name, code)
16
+ raise 'Not online' unless online?
17
+ @adapter.preprocess(rule_name, code)
18
+ end
19
+
20
+ def online?
21
+ @online
22
+ end
23
+
24
+ def variant
25
+ @adapter.variant_name
26
+ end
27
+
28
+ private
29
+
30
+ def detect_adapter
31
+ @online = try_sq_mini_racer || try_rhino
32
+
33
+ Sqreen.log.info "JS engine online: #{variant}" if @online
34
+ end
35
+
36
+ def try_sq_mini_racer
37
+ gem = Gem.loaded_specs['sq_mini_racer']
38
+ unless gem
39
+ Sqreen.log.info "sq_mini_racer gem not detected"
40
+ return false
41
+ end
42
+
43
+ require 'sqreen/mini_racer'
44
+ require 'sqreen/js/mini_racer_adapter'
45
+ @adapter = MiniRacerAdapter.new(true)
46
+ rescue LoadError => e
47
+ Sqreen.log.warn "Failed loading sq_mini_racer: #{e}"
48
+ false
49
+ end
50
+
51
+ def try_rhino
52
+ gem = Gem.loaded_specs['therubyrhino']
53
+ unless gem
54
+ Sqreen.log.info "therubyrhino gem not detected"
55
+ return false
56
+ end
57
+
58
+ require 'rhino'
59
+ require 'sqreen/js/execjs_adapter'
60
+ @adapter = ExecjsAdapter.new
61
+ rescue LoadError => e
62
+ Sqreen.log.warn "Failed loading rhino: #{e}"
63
+ false
64
+ end
65
+ end
66
+
67
+ CallContext = Struct.new(:inst, :args, :rv)
68
+
69
+ class ExecutableJs
70
+ def run_js_cb(_cb_name, _budget, _arguments)
71
+ raise Sqreen::NotImplementedYet
72
+ end
73
+ end
74
+
75
+ # end public interface
76
+
77
+ class JsServiceAdapter
78
+ def preprocess(code)
79
+ raise Sqreen::NotImplementedYet
80
+ end
81
+
82
+ def variant_name
83
+ 'unspecified'
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,125 @@
1
+ require 'weakref'
2
+
3
+ module Sqreen
4
+ module Js
5
+ GC_MINI_RACER = 10_000
6
+
7
+ class MiniRacerAdapter < JsServiceAdapter
8
+ def initialize(vendored = false)
9
+ @vendored = vendored
10
+ self.class.static_init
11
+ end
12
+
13
+ def preprocess(_rule_name, code)
14
+ MiniRacerExecutableJs.new(code, @vendored)
15
+ end
16
+
17
+ def variant_name
18
+ @vendored ? 'sq_mini_racer' : 'mini_racer'
19
+ end
20
+
21
+ def self.static_init
22
+ return if @done_static_init
23
+ Sqreen::MiniRacer::Platform.set_flags! :noconcurrent_recompilation
24
+ @done_static_init = true
25
+ end
26
+ end
27
+
28
+ # Auxiliary classes
29
+
30
+ class MiniRacerExecutableJs < ExecutableJs
31
+ @@ctx_defined = false
32
+
33
+ def initialize(source, vendored)
34
+ @module = vendored ? Sqreen::MiniRacer : MiniRacer
35
+ @source = source
36
+ @recycle_runtime_every = GC_MINI_RACER
37
+ @snapshot = @module::Snapshot.new(source)
38
+ @runtimes = []
39
+ @tl_key = "SQREEN_MINI_RACER_CONTEXT_#{object_id}".freeze
40
+ unless @@ctx_defined
41
+ self.class.define_sqreen_context(@module)
42
+ @@ctx_defined = true
43
+ end
44
+ end
45
+
46
+ def run_js_cb(cb_name, budget, arguments)
47
+ mini_racer_context = Thread.current[@tl_key]
48
+ dead_runtime = !mini_racer_context ||
49
+ !mini_racer_context[:r] || !mini_racer_context[:r].weakref_alive?
50
+ if !dead_runtime && mini_racer_context[:c] >= @recycle_runtime_every
51
+ dispose_runtime(mini_racer_context[:r])
52
+ dead_runtime = true
53
+ end
54
+ if dead_runtime
55
+ new_runtime = SqreenContext.new(:snapshot => @snapshot)
56
+ push_runtime new_runtime
57
+ mini_racer_context = {
58
+ :c => 0,
59
+ :r => WeakCtx.new(new_runtime),
60
+ }
61
+ Thread.current[@tl_key] = mini_racer_context
62
+ end
63
+
64
+ mini_racer_context[:c] += 1
65
+ begin
66
+ mini_racer_context[:r].eval_unsafe(
67
+ "#{cb_name}.apply(this, #{::JSON.generate(arguments)})", nil, budget)
68
+ rescue @module::ScriptTerminatedError
69
+ nil
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def push_runtime(runtime)
76
+ @runtimes.delete_if do |th, runt, _thid|
77
+ del = th.nil? || !th.weakref_alive? || !th.alive?
78
+ runt.dispose if del
79
+ del
80
+ end
81
+ @runtimes.push [WeakRef.new(Thread.current), runtime, Thread.current.object_id]
82
+ end
83
+
84
+ def dispose_runtime(runtime)
85
+ @runtimes.delete_if { |_th, _runt, thid| thid == Thread.current.object_id }
86
+ runtime.dispose
87
+ end
88
+
89
+ class << self
90
+ def define_sqreen_context(modoole)
91
+ # Context specialized for Sqreen usage
92
+ Sqreen::Js.const_set 'SqreenContext', Class.new(modoole.const_get('Context'))
93
+ SqreenContext.class_eval do
94
+ def eval_unsafe(str, filename = nil, timeoutv = nil)
95
+ # Beware, timeout could be kept in the context
96
+ # if perf cap is removed after having been activated
97
+ # As it's unused by execjscb we are not cleaning it
98
+ return super(str, filename) if timeoutv.nil?
99
+ return if timeoutv <= 0.0
100
+ timeoutv *= 1000 # Timeout are currently expressed in seconds
101
+ @timeout = timeoutv
102
+ @eval_thread = Thread.current
103
+ timeout do
104
+ super(str, filename)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+
113
+ # Weak ref to a context
114
+ # enables us to skip a method missing call
115
+ class WeakCtx < WeakRef
116
+ def initialize(*args)
117
+ super(*args)
118
+ end
119
+
120
+ def eval_unsafe(str, filename, timeoutv)
121
+ __getobj__.eval_unsafe(str, filename, timeoutv)
122
+ end
123
+ end
124
+ end
125
+ end
@@ -75,6 +75,17 @@ module Sqreen
75
75
 
76
76
  cb_class = nil
77
77
  js = hash_rule[Attrs::CALLBACKS]
78
+
79
+ if js && !Sqreen::Js::JsService.instance.online?
80
+ unless @@issue_nojs_warn
81
+ @@issue_nojs_warn = true
82
+ Sqreen.log.warn('No JavaScript engine is available. ' \
83
+ 'JavaScript callbacks will be ignored')
84
+ end
85
+ Sqreen.log.info("Ignoring JS callback #{rule_name}")
86
+ return nil
87
+ end
88
+
78
89
  cb_class = ExecJSCB if js
79
90
 
80
91
  if cbname && Rules.const_defined?(cbname)
@@ -1,22 +1,8 @@
1
1
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
- if defined?(::JRUBY_VERSION)
5
- require 'rhino'
6
- SQREEN_MINI_RACER = false
7
- else
8
- begin
9
- require 'mini_racer'
10
- SQREEN_MINI_RACER = true
11
- GC_MINI_RACER = 10_000
12
- rescue LoadError
13
- require 'therubyracer'
14
- SQREEN_MINI_RACER = false
15
- end
16
- end
17
4
 
18
- require 'weakref'
19
- require 'execjs'
5
+ require 'sqreen/js/js_service'
20
6
 
21
7
  require 'sqreen/rule_attributes'
22
8
  require 'sqreen/rule_callback'
@@ -25,64 +11,30 @@ require 'sqreen/binding_accessor'
25
11
  require 'sqreen/events/remote_exception'
26
12
 
27
13
  module Sqreen
28
- if SQREEN_MINI_RACER
29
- # Context specialized for Sqreen usage
30
- class SqreenContext < MiniRacer::Context
31
- def eval_unsafe(str, filename = nil, timeoutv = nil)
32
- # Beware, timeout could be kept in the context
33
- # if perf cap is removed after having been activated
34
- # As it's unused by execjscb we are not cleaning it
35
- return super(str, filename) if timeoutv.nil?
36
- return if timeoutv <= 0.0
37
- timeoutv *= 1000 # Timeout are currently expressed in seconds
38
- @timeout = timeoutv
39
- @eval_thread = Thread.current
40
- timeout do
41
- super(str, filename)
42
- end
43
- end
44
- end
45
-
46
- # Weak ref to a context
47
- # enables us to skip a method missing call
48
- class WeakCtx < WeakRef
49
- def initialize(*args)
50
- super(*args)
51
- end
52
-
53
- def eval_unsafe(str, filename, timeoutv)
54
- __getobj__.eval_unsafe(str, filename, timeoutv)
55
- end
56
- end
57
- end
58
14
  module Rules
59
15
  # Exec js callbacks
60
16
  class ExecJSCB < RuleCB
61
- attr_accessor :restrict_max_depth
62
- attr_reader :runtimes
63
- attr_accessor :recycle_runtime_every
17
+
18
+ class << self
19
+ # @return [Sqreen::Js::JsService]
20
+ def js_service
21
+ Sqreen::Js::JsService.instance
22
+ end
23
+ end
64
24
 
65
25
  def initialize(klass, method, rule_hash)
66
26
  super(klass, method, rule_hash)
67
27
  callbacks = @rule[Attrs::CALLBACKS]
68
28
  @conditions = @rule.fetch(Attrs::CONDITIONS, {})
69
29
 
70
- if callbacks['pre'].nil? &&
71
- callbacks['post'].nil? &&
72
- callbacks['failing'].nil?
73
- raise(Sqreen::Exception, 'no JS CB provided')
74
- end
75
-
76
30
  build_runnable(callbacks)
77
- @restrict_max_depth = 20
78
- unless SQREEN_MINI_RACER
79
- @compiled = ExecJS.compile(@source)
80
- return
31
+
32
+ unless pre? || post? || failing?
33
+ raise Sqreen::Exception, 'no JS CB provided'
81
34
  end
82
- @recycle_runtime_every = GC_MINI_RACER
83
- @snapshot = MiniRacer::Snapshot.new(@source)
84
- @runtimes = []
85
- @key = "SQREEN_MINI_RACER_CONTEXT_#{object_id}".freeze
35
+
36
+ @executable = ExecJSCB.js_service.prepare(rule_name, @source)
37
+ @argument_filter = ArgumentFilter.new(rule_hash)
86
38
  end
87
39
 
88
40
  def pre?
@@ -100,63 +52,22 @@ module Sqreen
100
52
  def pre(inst, args, budget = nil, &_block)
101
53
  return unless pre?
102
54
 
103
- call_callback('pre', inst, budget, args)
55
+ call_callback('pre', budget, inst, @cb_bas['pre'], args)
104
56
  end
105
57
 
106
58
  def post(rv, inst, args, budget = nil, &_block)
107
59
  return unless post?
108
60
 
109
- call_callback('post', inst, budget, args, rv)
61
+ call_callback('post', budget, inst, @cb_bas['post'], args, rv)
110
62
  end
111
63
 
112
64
  def failing(rv, inst, args, budget = nil, &_block)
113
65
  return unless failing?
114
66
 
115
- call_callback('failing', inst, budget, args, rv)
116
- end
117
-
118
- def self.hash_val_included(needed, haystack, min_length = 8, max_depth = 20)
119
- new_obj = {}
120
- insert = []
121
- to_do = haystack.map { |k, v| [new_obj, k, v, 0] }
122
- until to_do.empty?
123
- where, key, value, deepness = to_do.pop
124
- safe_key = key.is_a?(Integer) ? key : key.to_s
125
- if value.is_a?(Hash) && deepness < max_depth
126
- val = {}
127
- insert << [where, safe_key, val]
128
- to_do += value.map { |k, v| [val, k, v, deepness + 1] }
129
- elsif value.is_a?(Array) && deepness < max_depth
130
- val = []
131
- insert << [where, safe_key, val]
132
- i = -1
133
- to_do += value.map { |v| [val, i += 1, v, deepness + 1] }
134
- elsif deepness >= max_depth # if we are after max_depth don't try to filter
135
- insert << [where, safe_key, value]
136
- else
137
- v = value.to_s
138
- if v.size >= min_length && ConditionEvaluator.str_include?(needed.to_s, v)
139
- case where
140
- when Array
141
- where << value
142
- else
143
- where[safe_key] = value
144
- end
145
- end
146
- end
147
- end
148
- insert.reverse.each do |wh, ikey, ival|
149
- case wh
150
- when Array
151
- wh << ival unless ival.respond_to?(:empty?) && ival.empty?
152
- else
153
- wh[ikey] = ival unless ival.respond_to?(:empty?) && ival.empty?
154
- end
155
- end
156
- new_obj
67
+ call_callback('failing', budget, inst, @cb_bas['failing'], args, rv)
157
68
  end
158
69
 
159
- protected
70
+ private
160
71
 
161
72
  def record_and_continue?(ret)
162
73
  case ret
@@ -165,10 +76,10 @@ module Sqreen
165
76
  when Hash
166
77
  ret.keys.each do |k|
167
78
  ret[(begin
168
- k.to_sym
169
- rescue StandardError
170
- k
171
- end)] = ret[k] end
79
+ k.to_sym
80
+ rescue StandardError
81
+ k
82
+ end)] = ret[k] end
172
83
  record_event(ret[:record]) unless ret[:record].nil?
173
84
  unless ret['observations'].nil?
174
85
  ret['observations'].each do |obs|
@@ -182,134 +93,163 @@ module Sqreen
182
93
  end
183
94
  end
184
95
 
185
- def push_runtime(runtime)
186
- @runtimes.delete_if do |th, runt, _thid|
187
- del = th.nil? || !th.weakref_alive? || !th.alive?
188
- runt.dispose if del
189
- del
96
+ def call_callback(cb_name, budget, inst, cb_ba_args, args, rv = nil)
97
+ arguments = cb_ba_args.map do |ba|
98
+ ba.resolve(binding, framework, inst, args, @data, rv)
190
99
  end
191
- @runtimes.push [WeakRef.new(Thread.current), runtime, Thread.current.object_id]
192
- end
100
+ arguments = @argument_filter.filter(cb_name, arguments)
193
101
 
194
- def dispose_runtime(runtime)
195
- @runtimes.delete_if { |_th, _runt, thid| thid == Thread.current.object_id }
196
- runtime.dispose
197
- end
102
+ ret = @executable.run_js_cb(cb_name, budget, arguments)
198
103
 
199
- def call_callback(name, inst, budget, args, rv = nil)
200
- mini_racer_context = nil
201
- if SQREEN_MINI_RACER
202
- mini_racer_context = Thread.current[@key]
203
- dead_runtime = !mini_racer_context || !mini_racer_context[:r] || !mini_racer_context[:r].weakref_alive?
204
- if !dead_runtime && mini_racer_context[:c] >= @recycle_runtime_every
205
- dispose_runtime(mini_racer_context[:r])
206
- dead_runtime = true
207
- end
208
- if dead_runtime
209
- new_runtime = SqreenContext.new(:snapshot => @snapshot)
210
- push_runtime new_runtime
211
- mini_racer_context = {
212
- :c => 0,
213
- :r => WeakCtx.new(new_runtime),
214
- }
215
- Thread.current[@key] = mini_racer_context
216
- end
217
- end
218
- ret = nil
219
- args_override = nil
220
- arguments = nil
221
- while true
222
- arguments = (args_override || @argument_requirements[name]).map do |accessor|
223
- accessor.resolve(binding, framework, inst, args, @data, rv)
224
- end
225
- arguments = restrict(name, arguments) if @conditions.key?(name)
226
- Sqreen.log.debug { [name, arguments].inspect }
227
- if SQREEN_MINI_RACER
228
- mini_racer_context[:c] += 1
229
- begin
230
- ret = mini_racer_context[:r].eval_unsafe("#{name}.apply(this, #{::JSON.generate(arguments)})", nil, budget)
231
- rescue MiniRacer::ScriptTerminatedError
232
- ret = nil
233
- end
234
- else
235
- ret = @compiled.call(name, *arguments)
236
- end
237
- unless record_and_continue?(ret)
238
- return nil if ret.nil?
239
- return advise_action(ret[:status], ret)
240
- end
241
- name = ret[:call]
242
- rv = ret[:data]
243
- args_override = ret[:args]
244
- args_override = build_accessor(args_override) if args_override
104
+ unless record_and_continue?(ret)
105
+ return nil if ret.nil?
106
+ return advise_action(ret[:status], ret)
245
107
  end
108
+
109
+ name = ret[:call]
110
+ rv = ret[:data]
111
+ new_ba_args = if ret[:args]
112
+ self.class.build_accessors(ret[:args])
113
+ else
114
+ @cb_bas[name] || []
115
+ end
116
+
117
+ # XXX: budgets was not subtracted from
118
+ call_callback(name, budget, inst, new_ba_args, args, rv)
119
+
246
120
  rescue StandardError => e
247
- Sqreen.log.warn { "we catch a JScb exception: #{e.inspect}" }
121
+ Sqreen.log.warn { "Caught JS callback exception: #{e.inspect}" }
248
122
  Sqreen.log.debug e.backtrace
249
- record_exception(e, :cb => name, :args => arguments)
123
+ record_exception(e, :cb => cb_name, :args => arguments)
250
124
  nil
251
125
  end
252
126
 
253
- def each_hash_val_include(condition, depth = 10)
254
- return if depth <= 0
255
- condition.each do |key, values|
256
- if key == ConditionEvaluator::HASH_INC_OPERATOR
257
- yield values
127
+
128
+ def self.build_accessors(reqs)
129
+ reqs.map do |req|
130
+ BindingAccessor.new(req, true)
131
+ end
132
+ end
133
+
134
+ def build_runnable(callbacks)
135
+ @cb_bas = {}
136
+ @source = ''
137
+ @js_pre = !callbacks['pre'].nil?
138
+ @js_post = !callbacks['post'].nil?
139
+ @js_failing = !callbacks['failing'].nil?
140
+ callbacks.each do |name, args_or_func|
141
+ @source << "this['#{name.tr("'", "\\'")}'] = "
142
+ if args_or_func.is_a?(Array)
143
+ args_or_func = args_or_func.dup
144
+ @source << args_or_func.pop
145
+ @cb_bas[name] = self.class.build_accessors(args_or_func)
258
146
  else
259
- values.map do |v|
260
- each_hash_val_include(v, depth - 1) { |vals| yield vals } if v.is_a?(Hash)
261
- end
147
+ @source << args_or_func
148
+ @cb_bas[name] = []
262
149
  end
150
+ @source << ";\n"
263
151
  end
264
152
  end
153
+ end
154
+
155
+ class ArgumentFilter
156
+ MAX_DEPTH = 2
157
+
158
+ def initialize(rule)
159
+ @conditions = rule.fetch(Attrs::CONDITIONS, {})
160
+ build_arg_requirements rule
161
+ end
265
162
 
266
- def restrict(cbname, arguments)
163
+ def filter(cbname, arguments)
267
164
  condition = @conditions[cbname]
268
- return arguments if condition.nil? || @argument_requirements[cbname].nil?
165
+ return arguments if condition.nil? || @ba_expressions[cbname].nil?
269
166
 
270
167
  each_hash_val_include(condition) do |needle, haystack, min_length|
271
168
  # We could actually run the binding accessor expression here.
272
- needed_idx = @argument_requirements[cbname].map(&:expression).index(needle)
169
+ needed_idx = @ba_expressions[cbname].index(needle)
273
170
  next unless needed_idx
274
171
 
275
- haystack_idx = @argument_requirements[cbname].map(&:expression).index(haystack)
172
+ haystack_idx = @ba_expressions[cbname].index(haystack)
276
173
  next unless haystack_idx
277
174
 
278
- arguments[haystack_idx] = ExecJSCB.hash_val_included(
279
- arguments[needed_idx],
280
- arguments[haystack_idx],
281
- min_length.to_i,
282
- @restrict_max_depth
175
+ arguments[haystack_idx] = ArgumentFilter.hash_val_included(
176
+ arguments[needed_idx],
177
+ arguments[haystack_idx],
178
+ min_length.to_i,
179
+ MAX_DEPTH
283
180
  )
284
181
  end
285
182
 
286
183
  arguments
287
184
  end
288
185
 
289
- def build_accessor(reqs)
290
- reqs.map do |req|
291
- BindingAccessor.new(req, true)
186
+ def build_arg_requirements(rule)
187
+ @ba_expressions = {}
188
+ callbacks = rule[Attrs::CALLBACKS]
189
+ callbacks.each do |name, args_or_func|
190
+ next unless args_or_func.is_a?(Array)
191
+ args_bas = args_or_func[0..-2] unless args_or_func.empty?
192
+ @ba_expressions[name] =
193
+ ExecJSCB.build_accessors(args_bas).map(&:expression)
292
194
  end
293
195
  end
294
196
 
295
- def build_runnable(callbacks)
296
- @argument_requirements = {}
297
- @source = ''
298
- @js_pre = !callbacks['pre'].nil?
299
- @js_post = !callbacks['post'].nil?
300
- @js_failing = !callbacks['failing'].nil?
301
- callbacks.each do |name, args_or_func|
302
- @source << "var #{name} = "
303
- if args_or_func.is_a?(Array)
304
- @source << args_or_func.pop
305
- @argument_requirements[name] = build_accessor(args_or_func)
197
+ private
198
+
199
+ def each_hash_val_include(condition, depth = 10)
200
+ return if depth <= 0
201
+ condition.each do |key, values|
202
+ if key == ConditionEvaluator::HASH_INC_OPERATOR
203
+ yield values
306
204
  else
307
- @source << args_or_func
308
- @argument_requirements[name] = []
205
+ values.map do |v|
206
+ each_hash_val_include(v, depth - 1) { |vals| yield vals } if v.is_a?(Hash)
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ def self.hash_val_included(needed, haystack, min_length = 8, max_depth = 20)
213
+ new_obj = {}
214
+ insert = []
215
+ to_do = haystack.map { |k, v| [new_obj, k, v, 0] }
216
+ until to_do.empty?
217
+ where, key, value, deepness = to_do.pop
218
+ safe_key = key.is_a?(Integer) ? key : key.to_s
219
+ if value.is_a?(Hash) && deepness < max_depth
220
+ val = {}
221
+ insert << [where, safe_key, val]
222
+ to_do += value.map { |k, v| [val, k, v, deepness + 1] }
223
+ elsif value.is_a?(Array) && deepness < max_depth
224
+ val = []
225
+ insert << [where, safe_key, val]
226
+ i = -1
227
+ to_do += value.map { |v| [val, i += 1, v, deepness + 1] }
228
+ elsif deepness >= max_depth # if we are after max_depth don't try to filter
229
+ insert << [where, safe_key, value]
230
+ else
231
+ v = value.to_s
232
+ if v.size >= min_length && ConditionEvaluator.str_include?(needed.to_s, v)
233
+ case where
234
+ when Array
235
+ where << value
236
+ else
237
+ where[safe_key] = value
238
+ end
239
+ end
309
240
  end
310
- @source << ";\n"
311
241
  end
242
+ insert.reverse.each do |wh, ikey, ival|
243
+ case wh
244
+ when Array
245
+ wh << ival unless ival.respond_to?(:empty?) && ival.empty?
246
+ else
247
+ wh[ikey] = ival unless ival.respond_to?(:empty?) && ival.empty?
248
+ end
249
+ end
250
+ new_obj
312
251
  end
313
252
  end
314
253
  end
315
254
  end
255
+
@@ -0,0 +1,315 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ if defined?(::JRUBY_VERSION)
5
+ require 'rhino'
6
+ SQREEN_MINI_RACER = false
7
+ else
8
+ begin
9
+ require 'mini_racer'
10
+ SQREEN_MINI_RACER = true
11
+ GC_MINI_RACER = 10_000
12
+ rescue LoadError
13
+ require 'therubyracer'
14
+ SQREEN_MINI_RACER = false
15
+ end
16
+ end
17
+
18
+ require 'weakref'
19
+ require 'execjs'
20
+
21
+ require 'sqreen/rule_attributes'
22
+ require 'sqreen/rule_callback'
23
+ require 'sqreen/condition_evaluator'
24
+ require 'sqreen/binding_accessor'
25
+ require 'sqreen/events/remote_exception'
26
+
27
+ module Sqreen
28
+ if SQREEN_MINI_RACER
29
+ # Context specialized for Sqreen usage
30
+ class SqreenContext < MiniRacer::Context
31
+ def eval_unsafe(str, filename = nil, timeoutv = nil)
32
+ # Beware, timeout could be kept in the context
33
+ # if perf cap is removed after having been activated
34
+ # As it's unused by execjscb we are not cleaning it
35
+ return super(str, filename) if timeoutv.nil?
36
+ return if timeoutv <= 0.0
37
+ timeoutv *= 1000 # Timeout are currently expressed in seconds
38
+ @timeout = timeoutv
39
+ @eval_thread = Thread.current
40
+ timeout do
41
+ super(str, filename)
42
+ end
43
+ end
44
+ end
45
+
46
+ # Weak ref to a context
47
+ # enables us to skip a method missing call
48
+ class WeakCtx < WeakRef
49
+ def initialize(*args)
50
+ super(*args)
51
+ end
52
+
53
+ def eval_unsafe(str, filename, timeoutv)
54
+ __getobj__.eval_unsafe(str, filename, timeoutv)
55
+ end
56
+ end
57
+ end
58
+ module Rules
59
+ # Exec js callbacks
60
+ class ExecJSCBOld < RuleCB
61
+ attr_accessor :restrict_max_depth
62
+ attr_reader :runtimes
63
+ attr_accessor :recycle_runtime_every
64
+
65
+ def initialize(klass, method, rule_hash)
66
+ super(klass, method, rule_hash)
67
+ callbacks = @rule[Attrs::CALLBACKS]
68
+ @conditions = @rule.fetch(Attrs::CONDITIONS, {})
69
+
70
+ if callbacks['pre'].nil? &&
71
+ callbacks['post'].nil? &&
72
+ callbacks['failing'].nil?
73
+ raise(Sqreen::Exception, 'no JS CB provided')
74
+ end
75
+
76
+ build_runnable(callbacks)
77
+ @restrict_max_depth = 20
78
+ unless SQREEN_MINI_RACER
79
+ @compiled = ExecJS.compile(@source)
80
+ return
81
+ end
82
+ @recycle_runtime_every = GC_MINI_RACER
83
+ @snapshot = MiniRacer::Snapshot.new(@source)
84
+ @runtimes = []
85
+ @key = "SQREEN_MINI_RACER_CONTEXT_#{object_id}".freeze
86
+ end
87
+
88
+ def pre?
89
+ @js_pre
90
+ end
91
+
92
+ def post?
93
+ @js_post
94
+ end
95
+
96
+ def failing?
97
+ @js_failing
98
+ end
99
+
100
+ def pre(inst, args, budget = nil, &_block)
101
+ return unless pre?
102
+
103
+ call_callback('pre', inst, budget, args)
104
+ end
105
+
106
+ def post(rv, inst, args, budget = nil, &_block)
107
+ return unless post?
108
+
109
+ call_callback('post', inst, budget, args, rv)
110
+ end
111
+
112
+ def failing(rv, inst, args, budget = nil, &_block)
113
+ return unless failing?
114
+
115
+ call_callback('failing', inst, budget, args, rv)
116
+ end
117
+
118
+ def self.hash_val_included(needed, haystack, min_length = 8, max_depth = 20)
119
+ new_obj = {}
120
+ insert = []
121
+ to_do = haystack.map { |k, v| [new_obj, k, v, 0] }
122
+ until to_do.empty?
123
+ where, key, value, deepness = to_do.pop
124
+ safe_key = key.is_a?(Integer) ? key : key.to_s
125
+ if value.is_a?(Hash) && deepness < max_depth
126
+ val = {}
127
+ insert << [where, safe_key, val]
128
+ to_do += value.map { |k, v| [val, k, v, deepness + 1] }
129
+ elsif value.is_a?(Array) && deepness < max_depth
130
+ val = []
131
+ insert << [where, safe_key, val]
132
+ i = -1
133
+ to_do += value.map { |v| [val, i += 1, v, deepness + 1] }
134
+ elsif deepness >= max_depth # if we are after max_depth don't try to filter
135
+ insert << [where, safe_key, value]
136
+ else
137
+ v = value.to_s
138
+ if v.size >= min_length && ConditionEvaluator.str_include?(needed.to_s, v)
139
+ case where
140
+ when Array
141
+ where << value
142
+ else
143
+ where[safe_key] = value
144
+ end
145
+ end
146
+ end
147
+ end
148
+ insert.reverse.each do |wh, ikey, ival|
149
+ case wh
150
+ when Array
151
+ wh << ival unless ival.respond_to?(:empty?) && ival.empty?
152
+ else
153
+ wh[ikey] = ival unless ival.respond_to?(:empty?) && ival.empty?
154
+ end
155
+ end
156
+ new_obj
157
+ end
158
+
159
+ protected
160
+
161
+ def record_and_continue?(ret)
162
+ case ret
163
+ when NilClass
164
+ false
165
+ when Hash
166
+ ret.keys.each do |k|
167
+ ret[(begin
168
+ k.to_sym
169
+ rescue StandardError
170
+ k
171
+ end)] = ret[k] end
172
+ record_event(ret[:record]) unless ret[:record].nil?
173
+ unless ret['observations'].nil?
174
+ ret['observations'].each do |obs|
175
+ obs[3] = Time.parse(obs[3]) if obs.size >= 3 && obs[3].is_a?(String)
176
+ record_observation(*obs)
177
+ end
178
+ end
179
+ !ret[:call].nil?
180
+ else
181
+ raise Sqreen::Exception, "Invalid return type #{ret.inspect}"
182
+ end
183
+ end
184
+
185
+ def push_runtime(runtime)
186
+ @runtimes.delete_if do |th, runt, _thid|
187
+ del = th.nil? || !th.weakref_alive? || !th.alive?
188
+ runt.dispose if del
189
+ del
190
+ end
191
+ @runtimes.push [WeakRef.new(Thread.current), runtime, Thread.current.object_id]
192
+ end
193
+
194
+ def dispose_runtime(runtime)
195
+ @runtimes.delete_if { |_th, _runt, thid| thid == Thread.current.object_id }
196
+ runtime.dispose
197
+ end
198
+
199
+ def call_callback(name, inst, budget, args, rv = nil)
200
+ mini_racer_context = nil
201
+ if SQREEN_MINI_RACER
202
+ mini_racer_context = Thread.current[@key]
203
+ dead_runtime = !mini_racer_context || !mini_racer_context[:r] || !mini_racer_context[:r].weakref_alive?
204
+ if !dead_runtime && mini_racer_context[:c] >= @recycle_runtime_every
205
+ dispose_runtime(mini_racer_context[:r])
206
+ dead_runtime = true
207
+ end
208
+ if dead_runtime
209
+ new_runtime = SqreenContext.new(:snapshot => @snapshot)
210
+ push_runtime new_runtime
211
+ mini_racer_context = {
212
+ :c => 0,
213
+ :r => WeakCtx.new(new_runtime),
214
+ }
215
+ Thread.current[@key] = mini_racer_context
216
+ end
217
+ end
218
+ ret = nil
219
+ args_override = nil
220
+ arguments = nil
221
+ while true
222
+ arguments = (args_override || @argument_requirements[name]).map do |accessor|
223
+ accessor.resolve(binding, framework, inst, args, @data, rv)
224
+ end
225
+ arguments = restrict(name, arguments) if @conditions.key?(name)
226
+ Sqreen.log.debug { [name, arguments].inspect }
227
+ if SQREEN_MINI_RACER
228
+ mini_racer_context[:c] += 1
229
+ begin
230
+ ret = mini_racer_context[:r].eval_unsafe("#{name}.apply(this, #{::JSON.generate(arguments)})", nil, budget)
231
+ rescue MiniRacer::ScriptTerminatedError
232
+ ret = nil
233
+ end
234
+ else
235
+ ret = @compiled.call(name, *arguments)
236
+ end
237
+ unless record_and_continue?(ret)
238
+ return nil if ret.nil?
239
+ return advise_action(ret[:status], ret)
240
+ end
241
+ name = ret[:call]
242
+ rv = ret[:data]
243
+ args_override = ret[:args]
244
+ args_override = build_accessor(args_override) if args_override
245
+ end
246
+ rescue StandardError => e
247
+ Sqreen.log.warn { "we catch a JScb exception: #{e.inspect}" }
248
+ Sqreen.log.debug e.backtrace
249
+ record_exception(e, :cb => name, :args => arguments)
250
+ nil
251
+ end
252
+
253
+ def each_hash_val_include(condition, depth = 10)
254
+ return if depth <= 0
255
+ condition.each do |key, values|
256
+ if key == ConditionEvaluator::HASH_INC_OPERATOR
257
+ yield values
258
+ else
259
+ values.map do |v|
260
+ each_hash_val_include(v, depth - 1) { |vals| yield vals } if v.is_a?(Hash)
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ def restrict(cbname, arguments)
267
+ condition = @conditions[cbname]
268
+ return arguments if condition.nil? || @argument_requirements[cbname].nil?
269
+
270
+ each_hash_val_include(condition) do |needle, haystack, min_length|
271
+ # We could actually run the binding accessor expression here.
272
+ needed_idx = @argument_requirements[cbname].map(&:expression).index(needle)
273
+ next unless needed_idx
274
+
275
+ haystack_idx = @argument_requirements[cbname].map(&:expression).index(haystack)
276
+ next unless haystack_idx
277
+
278
+ arguments[haystack_idx] = ExecJSCB.hash_val_included(
279
+ arguments[needed_idx],
280
+ arguments[haystack_idx],
281
+ min_length.to_i,
282
+ @restrict_max_depth
283
+ )
284
+ end
285
+
286
+ arguments
287
+ end
288
+
289
+ def build_accessor(reqs)
290
+ reqs.map do |req|
291
+ BindingAccessor.new(req, true)
292
+ end
293
+ end
294
+
295
+ def build_runnable(callbacks)
296
+ @argument_requirements = {}
297
+ @source = ''
298
+ @js_pre = !callbacks['pre'].nil?
299
+ @js_post = !callbacks['post'].nil?
300
+ @js_failing = !callbacks['failing'].nil?
301
+ callbacks.each do |name, args_or_func|
302
+ @source << "var #{name} = "
303
+ if args_or_func.is_a?(Array)
304
+ @source << args_or_func.pop
305
+ @argument_requirements[name] = build_accessor(args_or_func)
306
+ else
307
+ @source << args_or_func
308
+ @argument_requirements[name] = []
309
+ end
310
+ @source << ";\n"
311
+ end
312
+ end
313
+ end
314
+ end
315
+ end
@@ -122,7 +122,11 @@ module Sqreen
122
122
  if escape_html == false &&
123
123
  text.respond_to?(:include?) &&
124
124
  !text.include?('html_escape')
125
- args[0].replace("Sqreen.escape_haml((#{args[0]}))")
125
+ if text.respond_to? :text=
126
+ args[0].text = "Sqreen.escape_haml((#{args[0].text}))"
127
+ else
128
+ args[0].replace("Sqreen.escape_haml((#{args[0]}))")
129
+ end
126
130
  end
127
131
  nil
128
132
  end
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
  module Sqreen
4
- VERSION = '1.13.4'.freeze
4
+ VERSION = '1.14.0.beta3'.freeze
5
5
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqreen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.4
4
+ version: 1.14.0.beta3
5
5
  platform: java
6
6
  authors:
7
7
  - Sqreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-16 00:00:00.000000000 Z
11
+ date: 2018-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 0.3.0
19
- name: execjs
18
+ version: '0'
19
+ name: therubyrhino
20
20
  prerelease: false
21
21
  type: :runtime
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.3.0
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '0'
33
- name: therubyrhino
32
+ version: 0.3.0
33
+ name: execjs
34
34
  prerelease: false
35
35
  type: :runtime
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 0.3.0
41
41
  description: Sqreen is a SaaS based Application protection and monitoring platform
42
42
  that integrates directly into your Ruby applications. Learn more at https://sqreen.io.
43
43
  email: contact@sqreen.io
@@ -77,6 +77,9 @@ files:
77
77
  - lib/sqreen/frameworks/sinatra.rb
78
78
  - lib/sqreen/frameworks/sqreen_test.rb
79
79
  - lib/sqreen/instrumentation.rb
80
+ - lib/sqreen/js/execjs_adapter.rb
81
+ - lib/sqreen/js/js_service.rb
82
+ - lib/sqreen/js/mini_racer_adapter.rb
80
83
  - lib/sqreen/log.rb
81
84
  - lib/sqreen/metrics.rb
82
85
  - lib/sqreen/metrics/average.rb
@@ -106,6 +109,7 @@ files:
106
109
  - lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb
107
110
  - lib/sqreen/rules_callbacks/custom_error.rb
108
111
  - lib/sqreen/rules_callbacks/execjs.rb
112
+ - lib/sqreen/rules_callbacks/execjs_old.rb
109
113
  - lib/sqreen/rules_callbacks/headers_insert.rb
110
114
  - lib/sqreen/rules_callbacks/inspect_rule.rb
111
115
  - lib/sqreen/rules_callbacks/matcher_rule.rb
@@ -142,12 +146,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
146
  version: '0'
143
147
  required_rubygems_version: !ruby/object:Gem::Requirement
144
148
  requirements:
145
- - - ">="
149
+ - - ">"
146
150
  - !ruby/object:Gem::Version
147
- version: '0'
151
+ version: 1.3.1
148
152
  requirements: []
149
153
  rubyforge_project:
150
- rubygems_version: 2.6.14.1
154
+ rubygems_version: 2.7.7
151
155
  signing_key:
152
156
  specification_version: 4
153
157
  summary: Sqreen Ruby agent