sqreen-alt 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +22 -0
  3. data/README.md +77 -0
  4. data/Rakefile +20 -0
  5. data/lib/sqreen-alt.rb +1 -0
  6. data/lib/sqreen.rb +68 -0
  7. data/lib/sqreen/attack_detected.html +2 -0
  8. data/lib/sqreen/binding_accessor.rb +288 -0
  9. data/lib/sqreen/ca.crt +72 -0
  10. data/lib/sqreen/call_countable.rb +67 -0
  11. data/lib/sqreen/callback_tree.rb +78 -0
  12. data/lib/sqreen/callbacks.rb +100 -0
  13. data/lib/sqreen/capped_queue.rb +23 -0
  14. data/lib/sqreen/condition_evaluator.rb +235 -0
  15. data/lib/sqreen/conditionable.rb +50 -0
  16. data/lib/sqreen/configuration.rb +168 -0
  17. data/lib/sqreen/context.rb +26 -0
  18. data/lib/sqreen/deliveries/batch.rb +84 -0
  19. data/lib/sqreen/deliveries/simple.rb +39 -0
  20. data/lib/sqreen/event.rb +16 -0
  21. data/lib/sqreen/events/attack.rb +61 -0
  22. data/lib/sqreen/events/remote_exception.rb +54 -0
  23. data/lib/sqreen/events/request_record.rb +62 -0
  24. data/lib/sqreen/exception.rb +34 -0
  25. data/lib/sqreen/frameworks.rb +40 -0
  26. data/lib/sqreen/frameworks/generic.rb +446 -0
  27. data/lib/sqreen/frameworks/rails.rb +148 -0
  28. data/lib/sqreen/frameworks/rails3.rb +36 -0
  29. data/lib/sqreen/frameworks/request_recorder.rb +69 -0
  30. data/lib/sqreen/frameworks/sinatra.rb +57 -0
  31. data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
  32. data/lib/sqreen/instrumentation.rb +542 -0
  33. data/lib/sqreen/log.rb +119 -0
  34. data/lib/sqreen/metrics.rb +6 -0
  35. data/lib/sqreen/metrics/average.rb +39 -0
  36. data/lib/sqreen/metrics/base.rb +45 -0
  37. data/lib/sqreen/metrics/collect.rb +22 -0
  38. data/lib/sqreen/metrics/sum.rb +20 -0
  39. data/lib/sqreen/metrics_store.rb +96 -0
  40. data/lib/sqreen/middleware.rb +34 -0
  41. data/lib/sqreen/payload_creator.rb +137 -0
  42. data/lib/sqreen/performance_notifications.rb +86 -0
  43. data/lib/sqreen/performance_notifications/log.rb +36 -0
  44. data/lib/sqreen/performance_notifications/metrics.rb +36 -0
  45. data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
  46. data/lib/sqreen/remote_command.rb +93 -0
  47. data/lib/sqreen/rule_attributes.rb +26 -0
  48. data/lib/sqreen/rule_callback.rb +108 -0
  49. data/lib/sqreen/rules.rb +126 -0
  50. data/lib/sqreen/rules_callbacks.rb +29 -0
  51. data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +77 -0
  52. data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
  53. data/lib/sqreen/rules_callbacks/blacklist_ips.rb +44 -0
  54. data/lib/sqreen/rules_callbacks/count_http_codes.rb +40 -0
  55. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
  56. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +24 -0
  57. data/lib/sqreen/rules_callbacks/custom_error.rb +64 -0
  58. data/lib/sqreen/rules_callbacks/execjs.rb +241 -0
  59. data/lib/sqreen/rules_callbacks/headers_insert.rb +22 -0
  60. data/lib/sqreen/rules_callbacks/inspect_rule.rb +25 -0
  61. data/lib/sqreen/rules_callbacks/matcher_rule.rb +138 -0
  62. data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
  63. data/lib/sqreen/rules_callbacks/record_request_context.rb +39 -0
  64. data/lib/sqreen/rules_callbacks/reflected_xss.rb +254 -0
  65. data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
  66. data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
  67. data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
  68. data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
  69. data/lib/sqreen/rules_signature.rb +151 -0
  70. data/lib/sqreen/runner.rb +365 -0
  71. data/lib/sqreen/runtime_infos.rb +138 -0
  72. data/lib/sqreen/safe_json.rb +60 -0
  73. data/lib/sqreen/sdk.rb +22 -0
  74. data/lib/sqreen/serializer.rb +46 -0
  75. data/lib/sqreen/session.rb +317 -0
  76. data/lib/sqreen/shared_storage.rb +31 -0
  77. data/lib/sqreen/stats.rb +18 -0
  78. data/lib/sqreen/version.rb +5 -0
  79. metadata +148 -0
@@ -0,0 +1,29 @@
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
+ require 'sqreen/rules_callbacks/regexp_rule'
5
+ require 'sqreen/rules_callbacks/matcher_rule'
6
+
7
+ require 'sqreen/rules_callbacks/record_request_context'
8
+ require 'sqreen/rules_callbacks/rails_parameters'
9
+
10
+ require 'sqreen/rules_callbacks/headers_insert'
11
+ require 'sqreen/rules_callbacks/blacklist_ips'
12
+
13
+ require 'sqreen/rules_callbacks/inspect_rule'
14
+
15
+ require 'sqreen/rules_callbacks/shell_env'
16
+
17
+ require 'sqreen/rules_callbacks/url_matches'
18
+ require 'sqreen/rules_callbacks/user_agent_matches'
19
+ require 'sqreen/rules_callbacks/crawler_user_agent_matches'
20
+
21
+ require 'sqreen/rules_callbacks/reflected_xss'
22
+ require 'sqreen/rules_callbacks/execjs'
23
+
24
+ require 'sqreen/rules_callbacks/binding_accessor_metrics'
25
+ require 'sqreen/rules_callbacks/binding_accessor_matcher'
26
+ require 'sqreen/rules_callbacks/count_http_codes'
27
+ require 'sqreen/rules_callbacks/crawler_user_agent_matches_metrics'
28
+
29
+ require 'sqreen/rules_callbacks/custom_error'
@@ -0,0 +1,77 @@
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
+ require 'sqreen/rule_callback'
5
+ require 'sqreen/binding_accessor'
6
+ require 'sqreen/rules_callbacks/matcher_rule'
7
+
8
+ module Sqreen
9
+ module Rules
10
+ # Callback that match on a list or matcher+binding accessor
11
+ class BindingAccessorMatcherCB < RuleCB
12
+ attr_reader :rules
13
+
14
+ # matcher on one elem
15
+ class MatcherElem
16
+ def initialize(expr)
17
+ prepare([expr])
18
+ end
19
+ include Matcher
20
+ end
21
+
22
+ def initialize(klass, method, rule_hash)
23
+ super(klass, method, rule_hash)
24
+ @rules = []
25
+ if @data.empty? || @data['values'].nil? || @data['values'].empty?
26
+ msg = "no rules in data (had #{@data.keys})"
27
+ raise Sqreen::Exception, msg
28
+ end
29
+ prepare_rules(@data['values'])
30
+ end
31
+
32
+ def prepare_rules(rules)
33
+ accessors = Hash.new do |hash, key|
34
+ hash[key] = BindingAccessor.new(key, true)
35
+ end
36
+ @rules = rules.map do |r|
37
+ if r['binding_accessor'].empty?
38
+ raise Sqreen::Exception, "no accessors #{r['id']}"
39
+ end
40
+ [
41
+ r['id'],
42
+ r['binding_accessor'].map { |expression| accessors[expression] },
43
+ MatcherElem.new(r['matcher']),
44
+ r['matcher']['value'],
45
+ ]
46
+ end
47
+ end
48
+
49
+ def pre(inst, *args, &_block)
50
+ resol_cache = Hash.new do |hash, accessor|
51
+ hash[accessor] = accessor.resolve(binding, framework, inst, args)
52
+ end
53
+ @rules.each do |id, accessors, matcher, matcher_ref|
54
+ accessors.each do |accessor|
55
+ val = resol_cache[accessor]
56
+ val = [val] if val.is_a?(String)
57
+ next unless val.respond_to?(:each)
58
+ next if val.respond_to?(:seek)
59
+ val.each do |v|
60
+ next if !v.is_a?(String) || (!matcher.min_size.nil? && v.size < matcher.min_size)
61
+ next if matcher.match(v).nil?
62
+ infos = {
63
+ 'id' => id,
64
+ 'binding_accessor' => accessor.expression,
65
+ 'matcher' => matcher_ref,
66
+ 'found' => v,
67
+ }
68
+ record_event(infos)
69
+ return advise_action(:raise, :infos => infos)
70
+ end
71
+ end
72
+ end
73
+ nil
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,79 @@
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
+ require 'sqreen/rule_callback'
5
+ require 'sqreen/binding_accessor'
6
+ require 'sqreen/events/remote_exception'
7
+
8
+ module Sqreen
9
+ module Rules
10
+ # Publish metrics about data taken from the binding accessor
11
+ class BindingAccessorMetrics < RuleCB
12
+ # Exception thrown when no expression are present
13
+ class NoExpressions < Sqreen::Exception
14
+ def initialize(expr)
15
+ super("No valid expressions defined in #{expr.inspect}")
16
+ end
17
+ end
18
+
19
+ def initialize(klass, method, rule_hash)
20
+ super(klass, method, rule_hash)
21
+ @expr = {}
22
+ build_expressions(rule_hash[Attrs::CALLBACKS])
23
+ end
24
+
25
+ PRE_CB = 'pre'.freeze
26
+ POST_CB = 'post'.freeze
27
+ FAILING_CB = 'failing'.freeze
28
+
29
+ def pre?
30
+ @expr[PRE_CB]
31
+ end
32
+
33
+ def post?
34
+ @expr[POST_CB]
35
+ end
36
+
37
+ def failing?
38
+ @expr[FAILING_CB]
39
+ end
40
+
41
+ def pre(inst, *args, &_block)
42
+ return unless pre?
43
+
44
+ add_metrics(PRE_CB, inst, args)
45
+ end
46
+
47
+ def post(rv, inst, *args, &_block)
48
+ return unless post?
49
+
50
+ add_metrics(POST_CB, inst, args, rv)
51
+ end
52
+
53
+ def failing(exception, inst, *args, &_block)
54
+ return unless failing?
55
+
56
+ add_metrics(FAILING_CB, inst, args, exception)
57
+ end
58
+
59
+ protected
60
+
61
+ def add_metrics(name, inst, args, rv = nil)
62
+ category, key, value, = @expr[name].map do |accessor|
63
+ accessor.resolve(binding, framework, inst, args, @data, rv)
64
+ end
65
+ record_observation(category, key, value)
66
+ advise_action(nil)
67
+ end
68
+
69
+ def build_expressions(callbacks)
70
+ raise NoExpressions, callbacks if callbacks.nil? || callbacks.empty?
71
+ [PRE_CB, POST_CB, FAILING_CB].each do |c|
72
+ next if callbacks[c].nil? || callbacks[c].size < 3
73
+ @expr[c] = callbacks[c].map { |req| BindingAccessor.new(req, true) }
74
+ end
75
+ raise NoExpressions, callbacks if @expr.empty?
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,44 @@
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
+ require 'ipaddr'
5
+
6
+ require 'sqreen/rule_callback'
7
+
8
+ module Sqreen
9
+ module Rules
10
+ # Looks for a blacklisted ip and block
11
+ class BlacklistIPsCB < RuleCB
12
+ def initialize(klass, method, rule_hash)
13
+ super(klass, method, rule_hash)
14
+ @ips = Hash[@data['values'].map { |v| [v, IPAddr.new(v)] }]
15
+ raise ArgumentError.new("no ips given") if @ips.empty?
16
+ end
17
+
18
+ def pre(_inst, *_args, &_block)
19
+ return unless framework
20
+ ip = framework.client_ip
21
+ return unless ip
22
+ found = find_blacklisted_ip(ip)
23
+ return unless found
24
+ Sqreen.log.debug { "Found blacklisted IP #{ip} - found: #{found}" }
25
+ record_observation('blacklisted', found, 1)
26
+ advise_action(:raise)
27
+ end
28
+
29
+ protected
30
+
31
+ # Is this a blacklisted ip?
32
+ # return the ip blacklisted range that match ip
33
+ def find_blacklisted_ip(rip)
34
+ ret = (@ips || {}).find do |_, ip|
35
+ ip.include?(rip)
36
+ end
37
+ return nil unless ret
38
+ ret.first
39
+ rescue
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,40 @@
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
+ require 'sqreen/rule_attributes'
5
+ require 'sqreen/rule_callback'
6
+ require 'sqreen/safe_json'
7
+
8
+ module Sqreen
9
+ module Rules
10
+ # Save request context for handling further down
11
+ class CountHTTPCodes < RuleCB
12
+ METRIC_CATEGORY = 'http_code'.freeze
13
+ def post(rv, _inst, *_args, &_block)
14
+ return unless rv.is_a?(Array) && !rv.empty?
15
+ record_observation(METRIC_CATEGORY, rv[0], 1)
16
+ advise_action(nil)
17
+ end
18
+ end
19
+
20
+ # Count 1 for each things located by the binding accessor
21
+ class BindingAccessorCounter < RuleCB
22
+ def initialize(klass, method, rule_hash)
23
+ super(klass, method, rule_hash)
24
+ @accessors = @data['values'].map do |expr|
25
+ BindingAccessor.new(expr, true)
26
+ end
27
+ @metric_category = rule_hash[Attrs::METRICS].first['name']
28
+ end
29
+
30
+ def post(rv, inst, *args, &_block)
31
+ return unless rv.is_a?(Array) && !rv.empty?
32
+ key = @accessors.map do |accessor|
33
+ accessor.resolve(binding, framework, inst, args, @data, rv)
34
+ end
35
+ record_observation(@metric_category, SafeJSON.dump(key), 1)
36
+ advise_action(nil)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
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
+ require 'sqreen/rules_callbacks/matcher_rule'
5
+ require 'sqreen/frameworks'
6
+
7
+ module Sqreen
8
+ module Rules
9
+ # FIXME: Factor with UserAgentMatchesCB
10
+ # Look for crawlers
11
+ class CrawlerUserAgentMatchesCB < MatcherRuleCB
12
+ def pre(_inst, *_args, &_block)
13
+ ua = framework.client_user_agent
14
+ return unless ua
15
+ found = match(ua)
16
+ return unless found
17
+ Sqreen.log.debug { "Found UA #{ua} - found: #{found}" }
18
+ infos = { :found => found }
19
+ record_event(infos)
20
+ advise_action(nil)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
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
+ require 'sqreen/rules_callbacks/matcher_rule'
5
+ require 'sqreen/frameworks'
6
+
7
+ module Sqreen
8
+ module Rules
9
+ # Look for crawlers and post them in metrics
10
+ class CrawlerUserAgentMatchesMetricsCB < MatcherRuleCB
11
+ CRAWLER_CATEGORY = 'crawler'.freeze
12
+
13
+ def pre(_inst, *_args, &_block)
14
+ ua = framework.client_user_agent
15
+ return unless ua
16
+ found = match(ua)
17
+ return unless found
18
+ Sqreen.log.debug { "Found UA #{ua} - found: #{found}" }
19
+ record_observation(CRAWLER_CATEGORY, ua, 1)
20
+ advise_action(nil)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,64 @@
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
+ require 'sqreen/rule_callback'
5
+ require 'sqreen/exception'
6
+
7
+ module Sqreen
8
+ module Rules
9
+ # Display sqreen presence
10
+ class CustomErrorCB < RuleCB
11
+ attr_reader :status_code, :redirect_url
12
+ def initialize(klass, method, rule_hash)
13
+ @redirect_url = nil
14
+ @status_code = nil
15
+ super(klass, method, rule_hash)
16
+ if @data.nil? || @data['values'].empty?
17
+ raise Sqreen::Exception, 'No data'
18
+ end
19
+ configure_custom_error(@data['values'][0])
20
+ end
21
+
22
+ def configure_custom_error(custom_error)
23
+ case custom_error['type']
24
+ when 'custom_error_page' then
25
+ @status_code = custom_error['status_code'].to_i
26
+ when 'redirection' then
27
+ @redirect_url = custom_error['redirection_url']
28
+ @status_code = custom_error.fetch('status_code', 303).to_i
29
+ else
30
+ raise Sqreen::Exception, "No custom error #{custom_error['type']}"
31
+ end
32
+ end
33
+
34
+ def failing(except, _inst, *_args, &_block)
35
+ oexcept = nil
36
+ if except.respond_to?(:original_exception)
37
+ oexcept = except.original_exception
38
+ end
39
+ if !except.is_a?(Sqreen::AttackBlocked) &&
40
+ !oexcept.is_a?(Sqreen::AttackBlocked)
41
+ return advise_action(nil)
42
+ end
43
+ if @redirect_url
44
+ advise_action(:override, :new_return_value => respond_redirect)
45
+ else
46
+ advise_action(:override, :new_return_value => respond_page)
47
+ end
48
+ end
49
+
50
+ def respond_redirect
51
+ [@status_code, { 'Location' => @redirect_url }, ['']]
52
+ end
53
+
54
+ def respond_page
55
+ page = open(File.join(File.dirname(__FILE__), '../attack_detected.html'))
56
+ headers = {
57
+ 'Content-Type' => 'text/html',
58
+ 'Content-Length' => page.size.to_s,
59
+ }
60
+ [@status_code, headers, page]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,241 @@
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_V8_THREAD = false
7
+ else
8
+ begin
9
+ require 'mini_racer'
10
+ SQREEN_V8_THREAD = true
11
+ rescue LoadError
12
+ require 'therubyracer'
13
+ SQREEN_V8_THREAD = false
14
+ end
15
+ end
16
+
17
+ require 'execjs'
18
+
19
+ require 'sqreen/rule_attributes'
20
+ require 'sqreen/rule_callback'
21
+ require 'sqreen/condition_evaluator'
22
+ require 'sqreen/binding_accessor'
23
+ require 'sqreen/events/remote_exception'
24
+
25
+ module Sqreen
26
+ module Rules
27
+ # Exec js callbacks
28
+ class ExecJSCB < RuleCB
29
+ attr_accessor :restrict_max_depth
30
+ def initialize(klass, method, rule_hash)
31
+ super(klass, method, rule_hash)
32
+ callbacks = @rule[Attrs::CALLBACKS]
33
+ @conditions = @rule.fetch(Attrs::CONDITIONS, {})
34
+
35
+ if callbacks['pre'].nil? &&
36
+ callbacks['post'].nil? &&
37
+ callbacks['failing'].nil?
38
+ raise(Sqreen::Exception, 'no JS CB provided')
39
+ end
40
+
41
+ build_runnable(callbacks)
42
+ if !SQREEN_V8_THREAD
43
+ @compiled = ExecJS.compile(@source)
44
+ end
45
+ @restrict_max_depth = 20
46
+ end
47
+
48
+ def pre?
49
+ @js_pre
50
+ end
51
+
52
+ def post?
53
+ @js_post
54
+ end
55
+
56
+ def failing?
57
+ @js_failing
58
+ end
59
+
60
+ def pre(inst, *args, &_block)
61
+ return unless pre?
62
+
63
+ call_callback('pre', inst, args)
64
+ end
65
+
66
+ def post(rv, inst, *args, &_block)
67
+ return unless post?
68
+
69
+ call_callback('post', inst, args, rv)
70
+ end
71
+
72
+ def failing(rv, inst, *args, &_block)
73
+ return unless failing?
74
+
75
+ call_callback('failing', inst, args, rv)
76
+ end
77
+
78
+ def self.hash_val_included(needed, haystack, min_length = 8, max_depth = 20)
79
+ new_obj = {}
80
+ insert = []
81
+ to_do = haystack.map { |k, v| [new_obj, k, v, 0] }
82
+ until to_do.empty?
83
+ where, key, value, deepness = to_do.pop
84
+ safe_key = key.kind_of?(Integer) ? key : key.to_s
85
+ if value.is_a?(Hash) && deepness < max_depth
86
+ val = {}
87
+ insert << [where, safe_key, val]
88
+ to_do += value.map { |k, v| [val, k, v, deepness + 1] }
89
+ elsif value.is_a?(Array) && deepness < max_depth
90
+ val = []
91
+ insert << [where, safe_key, val]
92
+ i = -1
93
+ to_do += value.map { |v| [val, i += 1, v, deepness + 1] }
94
+ elsif deepness >= max_depth # if we are after max_depth don't try to filter
95
+ insert << [where, safe_key, value]
96
+ else
97
+ v = value.to_s
98
+ if v.size >= min_length && ConditionEvaluator.str_include?(needed.to_s, v)
99
+ case where
100
+ when Array
101
+ where << value
102
+ else
103
+ where[safe_key] = value
104
+ end
105
+ end
106
+ end
107
+ end
108
+ insert.reverse.each do |wh, ikey, ival|
109
+ case wh
110
+ when Array
111
+ wh << ival unless ival.respond_to?(:empty?) && ival.empty?
112
+ else
113
+ wh[ikey] = ival unless ival.respond_to?(:empty?) && ival.empty?
114
+ end
115
+ end
116
+ new_obj
117
+ end
118
+
119
+ protected
120
+
121
+ def record_and_continue?(ret)
122
+ case ret
123
+ when NilClass
124
+ return false
125
+ when Hash
126
+ ret.keys.each do |k|
127
+ ret[(begin
128
+ k.to_sym
129
+ rescue
130
+ k
131
+ end)] = ret[k] end
132
+ record_event(ret[:record]) unless ret[:record].nil?
133
+ unless ret['observations'].nil?
134
+ ret['observations'].each do |obs|
135
+ obs[3] = Time.parse(obs[3]) if obs.size >= 3 && obs[3].is_a?(String)
136
+ record_observation(*obs)
137
+ end
138
+ end
139
+ return !ret[:call].nil?
140
+ else
141
+ raise Sqreen::Exception, "Invalid return type #{ret.inspect}"
142
+ end
143
+ end
144
+
145
+ def call_callback(name, inst, args, rv = nil)
146
+ if SQREEN_V8_THREAD
147
+ Thread.current["SQREEN_EXECJS_SOURCE#{self.object_id}"] ||= ExecJS.compile(@source)
148
+ end
149
+ ret = nil
150
+ args_override = nil
151
+ arguments = nil
152
+ loop do
153
+ arguments = (args_override || @argument_requirements[name]).map do |accessor|
154
+ accessor.resolve(binding, framework, inst, args, @data, rv)
155
+ end
156
+ arguments = restrict(name, arguments) if @conditions.key?(name)
157
+ Sqreen.log.debug { [name, arguments].inspect }
158
+ if SQREEN_V8_THREAD
159
+ ret = Thread.current["SQREEN_EXECJS_SOURCE#{self.object_id}"].call(name, *arguments)
160
+ else
161
+ ret = @compiled.call(name, *arguments)
162
+ end
163
+ unless record_and_continue?(ret)
164
+ return nil if ret.nil?
165
+ return advise_action(ret[:status], ret)
166
+ end
167
+ name = ret[:call]
168
+ rv = ret[:data]
169
+ args_override = ret[:args]
170
+ args_override = build_accessor(args_override) if args_override
171
+ end
172
+ rescue => e
173
+ Sqreen.log.warn "we catch a JScb exception: #{e.inspect}"
174
+ Sqreen.log.debug e.backtrace
175
+ record_exception(e, :cb => name, :args => arguments)
176
+ nil
177
+ end
178
+
179
+ def each_hash_val_include(condition, depth = 10)
180
+ return if depth <= 0
181
+ condition.each do |key, values|
182
+ if key == ConditionEvaluator::HASH_INC_OPERATOR
183
+ yield values
184
+ else
185
+ values.map do |v|
186
+ each_hash_val_include(v, depth - 1) { |vals| yield vals } if v.is_a?(Hash)
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ def restrict(cbname, arguments)
193
+ condition = @conditions[cbname]
194
+ return arguments if condition.nil? or @argument_requirements[cbname].nil?
195
+
196
+ each_hash_val_include(condition) do |needle, haystack, min_length|
197
+ # We could actually run the binding accessor expression here.
198
+ needed_idx = @argument_requirements[cbname].map(&:expression).index(needle)
199
+ next unless needed_idx
200
+
201
+ haystack_idx = @argument_requirements[cbname].map(&:expression).index(haystack)
202
+ next unless haystack_idx
203
+
204
+ arguments[haystack_idx] = ExecJSCB.hash_val_included(
205
+ arguments[needed_idx],
206
+ arguments[haystack_idx],
207
+ min_length.to_i,
208
+ @restrict_max_depth
209
+ )
210
+ end
211
+
212
+ arguments
213
+ end
214
+
215
+ def build_accessor(reqs)
216
+ reqs.map do |req|
217
+ BindingAccessor.new(req, true)
218
+ end
219
+ end
220
+
221
+ def build_runnable(callbacks)
222
+ @argument_requirements = {}
223
+ @source = ''
224
+ @js_pre = !callbacks['pre'].nil?
225
+ @js_post = !callbacks['post'].nil?
226
+ @js_failing = !callbacks['failing'].nil?
227
+ callbacks.each do |name, args_or_func|
228
+ @source << "var #{name} = "
229
+ if args_or_func.is_a?(Array)
230
+ @source << args_or_func.pop
231
+ @argument_requirements[name] = build_accessor(args_or_func)
232
+ else
233
+ @source << args_or_func
234
+ @argument_requirements[name] = []
235
+ end
236
+ @source << ";\n"
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end