sqreen-alt 1.10.0

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.
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