sqreen 1.13.4-java → 1.14.0.beta3-java

Sign up to get free protection for your applications and to get access to all the features.
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