honeybadger 2.0.6 → 2.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/lib/honeybadger/agent.rb +8 -2
- data/lib/honeybadger/agent/worker.rb +8 -2
- data/lib/honeybadger/cli/helpers.rb +1 -1
- data/lib/honeybadger/cli/main.rb +1 -2
- data/lib/honeybadger/config.rb +7 -1
- data/lib/honeybadger/config/yaml.rb +0 -3
- data/lib/honeybadger/logging.rb +1 -1
- data/lib/honeybadger/notice.rb +44 -36
- data/lib/honeybadger/plugin.rb +2 -2
- data/lib/honeybadger/util/http.rb +0 -5
- data/lib/honeybadger/util/sanitizer.rb +55 -25
- data/lib/honeybadger/version.rb +1 -1
- metadata +2 -3
- data/lib/honeybadger/util/request_sanitizer.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f7f7f0ed185071b638c1397c651de08defdd67f
|
4
|
+
data.tar.gz: dc426f168bc4fc433638826c744df12f2f11e137
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19f26d5077e250b8faee7309504bc93ae568d87f8fb5eed8274a4810181dcb865e6688190bfba68c43cc3ac5dbd405f6ddd9ebc98aacf4e736605386c2a6b943
|
7
|
+
data.tar.gz: 1227af632c510f6255b24766622a921a5f2487d71450453a700db852da47cf4b0991b27b11714b0c71227ce224bd9d55a7701c90596704fdbd81fe405b278c35
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
* Handle bad encodings in exception payloads.
|
2
|
+
|
3
|
+
*Joshua Wood*
|
4
|
+
|
5
|
+
* Include full backtrace when logging worker exceptions.
|
6
|
+
|
7
|
+
*Joshua Wood*
|
8
|
+
|
9
|
+
* Always send a test notice on install.
|
10
|
+
|
11
|
+
*Joshua Wood*
|
12
|
+
|
13
|
+
* Send the id of the current process with error reports.
|
14
|
+
|
15
|
+
*Joshua Wood*
|
16
|
+
|
1
17
|
* Don't sub partial project root in backtrace lines.
|
2
18
|
|
3
19
|
*Joshua Wood*
|
data/lib/honeybadger/agent.rb
CHANGED
@@ -282,7 +282,10 @@ module Honeybadger
|
|
282
282
|
def run
|
283
283
|
loop { work }
|
284
284
|
rescue Exception => e
|
285
|
-
error
|
285
|
+
error {
|
286
|
+
msg = "error in agent thread (shutting down) class=%s message=%s\n\t%s"
|
287
|
+
sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
|
288
|
+
}
|
286
289
|
ensure
|
287
290
|
d { sprintf('stopping agent') }
|
288
291
|
end
|
@@ -291,7 +294,10 @@ module Honeybadger
|
|
291
294
|
flush_metrics if metrics.flush?
|
292
295
|
flush_traces if traces.flush?
|
293
296
|
rescue StandardError => e
|
294
|
-
error
|
297
|
+
error {
|
298
|
+
msg = "error in agent thread class=%s message=%s\n\t%s"
|
299
|
+
sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
|
300
|
+
}
|
295
301
|
ensure
|
296
302
|
sleep(delay)
|
297
303
|
end
|
@@ -131,7 +131,10 @@ module Honeybadger
|
|
131
131
|
d { sprintf('stopping worker feature=%s', feature) }
|
132
132
|
end
|
133
133
|
rescue Exception => e
|
134
|
-
error
|
134
|
+
error {
|
135
|
+
msg = "error in worker thread (shutting down) feature=%s class=%s message=%s\n\t%s"
|
136
|
+
sprintf(msg, feature, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
|
137
|
+
}
|
135
138
|
ensure
|
136
139
|
release_marker
|
137
140
|
end
|
@@ -140,7 +143,10 @@ module Honeybadger
|
|
140
143
|
handle_response(notify_backend(msg))
|
141
144
|
sleep(throttle_interval)
|
142
145
|
rescue StandardError => e
|
143
|
-
error
|
146
|
+
error {
|
147
|
+
msg = "error in worker thread feature=%s class=%s message=%s\n\t%s"
|
148
|
+
sprintf(msg, feature, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
|
149
|
+
}
|
144
150
|
sleep(1)
|
145
151
|
end
|
146
152
|
|
@@ -125,7 +125,7 @@ module Honeybadger
|
|
125
125
|
|
126
126
|
def test_honeybadger
|
127
127
|
puts "Raising '#{test_exception_class.name}' to simulate application failure."
|
128
|
-
raise #{test_exception_class}.new, 'Testing honeybadger via "honeybadger
|
128
|
+
raise #{test_exception_class}.new, 'Testing honeybadger via "honeybadger test", it works.'
|
129
129
|
end
|
130
130
|
|
131
131
|
# Ensure we actually have an action to go to.
|
data/lib/honeybadger/cli/main.rb
CHANGED
@@ -127,7 +127,6 @@ module Honeybadger
|
|
127
127
|
|
128
128
|
if (path = config.config_path).exist?
|
129
129
|
say("You're already on Honeybadger, so you're all set.", :yellow)
|
130
|
-
skip_test = true if options[:test].nil? # Only if it wasn't specified.
|
131
130
|
else
|
132
131
|
say("Writing configuration to: #{path}", :yellow)
|
133
132
|
|
@@ -153,7 +152,7 @@ module Honeybadger
|
|
153
152
|
end
|
154
153
|
end
|
155
154
|
|
156
|
-
if
|
155
|
+
if options[:test].nil? || options[:test]
|
157
156
|
Honeybadger.start(config) unless load_rails_env(verbose: true)
|
158
157
|
say('Sending test notice', :yellow)
|
159
158
|
unless Agent.instance && send_test(false)
|
data/lib/honeybadger/config.rb
CHANGED
@@ -352,7 +352,13 @@ api_key: '#{self[:api_key]}'
|
|
352
352
|
end
|
353
353
|
end
|
354
354
|
rescue ConfigError => e
|
355
|
-
|
355
|
+
error("error while loading config from disk: #{e}")
|
356
|
+
nil
|
357
|
+
rescue StandardError => e
|
358
|
+
error {
|
359
|
+
msg = "error while loading config from disk class=%s message=%s\n\t%s"
|
360
|
+
sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
|
361
|
+
}
|
356
362
|
nil
|
357
363
|
end
|
358
364
|
|
@@ -19,9 +19,6 @@ module Honeybadger
|
|
19
19
|
yaml.merge!(yaml[env]) if yaml[env].kind_of?(Hash)
|
20
20
|
update(dotify_keys(yaml))
|
21
21
|
end
|
22
|
-
|
23
|
-
rescue StandardError => e
|
24
|
-
raise ConfigError, "An unknown error occured: #{e.class} -- #{e.message}\n\t#{e.backtrace.first}"
|
25
22
|
end
|
26
23
|
|
27
24
|
private
|
data/lib/honeybadger/logging.rb
CHANGED
data/lib/honeybadger/notice.rb
CHANGED
@@ -7,7 +7,6 @@ require 'honeybadger/version'
|
|
7
7
|
require 'honeybadger/backtrace'
|
8
8
|
require 'honeybadger/util/stats'
|
9
9
|
require 'honeybadger/util/sanitizer'
|
10
|
-
require 'honeybadger/util/request_sanitizer'
|
11
10
|
require 'honeybadger/rack/request_hash'
|
12
11
|
|
13
12
|
module Honeybadger
|
@@ -132,10 +131,12 @@ module Honeybadger
|
|
132
131
|
|
133
132
|
def initialize(config, opts = {})
|
134
133
|
@now = Time.now.utc
|
134
|
+
@pid = Process.pid
|
135
135
|
@id = SecureRandom.uuid
|
136
136
|
|
137
137
|
@opts = opts
|
138
138
|
@config = config
|
139
|
+
@sanitizer = Util::Sanitizer.new(filters: config.params_filters)
|
139
140
|
|
140
141
|
@exception = opts[:exception]
|
141
142
|
@error_class = exception_attribute(:error_class) {|exception| exception.class.name }
|
@@ -148,10 +149,9 @@ module Honeybadger
|
|
148
149
|
@source = extract_source_from_backtrace(@backtrace, config, opts)
|
149
150
|
@fingerprint = construct_fingerprint(opts)
|
150
151
|
|
151
|
-
@
|
152
|
-
|
153
|
-
@
|
154
|
-
@context = construct_context_hash(opts, @sanitizer)
|
152
|
+
@request = OpenStruct.new(construct_request_hash(config.request, opts, config.excluded_request_keys))
|
153
|
+
|
154
|
+
@context = construct_context_hash(opts)
|
155
155
|
|
156
156
|
@causes = unwrap_causes(opts[:exception])
|
157
157
|
|
@@ -160,7 +160,7 @@ module Honeybadger
|
|
160
160
|
|
161
161
|
@stats = Util::Stats.all
|
162
162
|
|
163
|
-
@local_variables = local_variables_from_exception(exception, config
|
163
|
+
@local_variables = local_variables_from_exception(exception, config)
|
164
164
|
|
165
165
|
@api_key = opts[:api_key] || config[:api_key]
|
166
166
|
|
@@ -172,34 +172,35 @@ module Honeybadger
|
|
172
172
|
# Returns Hash JSON representation of notice
|
173
173
|
def as_json(*args)
|
174
174
|
{
|
175
|
-
api_key: api_key,
|
175
|
+
api_key: s(api_key),
|
176
176
|
notifier: NOTIFIER,
|
177
177
|
error: {
|
178
178
|
token: id,
|
179
|
-
class: error_class,
|
180
|
-
message: error_message,
|
181
|
-
backtrace: backtrace,
|
182
|
-
source: source,
|
183
|
-
fingerprint: fingerprint,
|
184
|
-
tags: tags,
|
185
|
-
causes: causes
|
179
|
+
class: s(error_class),
|
180
|
+
message: s(error_message),
|
181
|
+
backtrace: s(backtrace),
|
182
|
+
source: s(source),
|
183
|
+
fingerprint: s(fingerprint),
|
184
|
+
tags: s(tags),
|
185
|
+
causes: s(causes)
|
186
186
|
},
|
187
187
|
request: {
|
188
|
-
url:
|
189
|
-
component: component,
|
190
|
-
action: action,
|
191
|
-
params: params,
|
192
|
-
session: session,
|
193
|
-
cgi_data: cgi_data,
|
194
|
-
context: context,
|
195
|
-
local_variables: local_variables
|
188
|
+
url: sanitized_url,
|
189
|
+
component: s(component),
|
190
|
+
action: s(action),
|
191
|
+
params: s(params),
|
192
|
+
session: s(session),
|
193
|
+
cgi_data: s(cgi_data),
|
194
|
+
context: s(context),
|
195
|
+
local_variables: s(local_variables)
|
196
196
|
},
|
197
197
|
server: {
|
198
|
-
project_root: config[:root],
|
199
|
-
environment_name: config[:env],
|
200
|
-
hostname: config[:hostname],
|
198
|
+
project_root: s(config[:root]),
|
199
|
+
environment_name: s(config[:env]),
|
200
|
+
hostname: s(config[:hostname]),
|
201
201
|
stats: stats,
|
202
|
-
time: now
|
202
|
+
time: now,
|
203
|
+
pid: pid
|
203
204
|
}
|
204
205
|
}
|
205
206
|
end
|
@@ -208,7 +209,7 @@ module Honeybadger
|
|
208
209
|
#
|
209
210
|
# Returns valid JSON representation of Notice
|
210
211
|
def to_json(*a)
|
211
|
-
|
212
|
+
::JSON.generate(as_json(*a))
|
212
213
|
end
|
213
214
|
|
214
215
|
# Public: Allows properties to be accessed using a hash-like syntax
|
@@ -236,7 +237,7 @@ module Honeybadger
|
|
236
237
|
|
237
238
|
private
|
238
239
|
|
239
|
-
attr_reader :config, :opts, :context, :stats, :api_key, :now, :causes
|
240
|
+
attr_reader :config, :opts, :context, :stats, :api_key, :now, :pid, :causes, :sanitizer
|
240
241
|
|
241
242
|
def ignore_by_origin?
|
242
243
|
opts[:origin] == :rake && !config[:'exceptions.rescue_rake']
|
@@ -328,7 +329,7 @@ module Honeybadger
|
|
328
329
|
].compact | BACKTRACE_FILTERS
|
329
330
|
end
|
330
331
|
|
331
|
-
def construct_request_hash(rack_request, opts,
|
332
|
+
def construct_request_hash(rack_request, opts, excluded_keys = [])
|
332
333
|
request = {}
|
333
334
|
request.merge!(Rack::RequestHash.new(rack_request)) if rack_request
|
334
335
|
|
@@ -342,14 +343,14 @@ module Honeybadger
|
|
342
343
|
|
343
344
|
request[:session] = request[:session][:data] if request[:session][:data]
|
344
345
|
|
345
|
-
|
346
|
+
request
|
346
347
|
end
|
347
348
|
|
348
|
-
def construct_context_hash(opts
|
349
|
+
def construct_context_hash(opts)
|
349
350
|
context = {}
|
350
351
|
context.merge!(Thread.current[:__honeybadger_context]) if Thread.current[:__honeybadger_context]
|
351
352
|
context.merge!(opts[:context]) if opts[:context]
|
352
|
-
context.empty? ? nil :
|
353
|
+
context.empty? ? nil : context
|
353
354
|
end
|
354
355
|
|
355
356
|
def extract_source_from_backtrace(backtrace, config, opts)
|
@@ -401,12 +402,21 @@ module Honeybadger
|
|
401
402
|
ret
|
402
403
|
end
|
403
404
|
|
405
|
+
def s(data)
|
406
|
+
sanitizer.sanitize(data)
|
407
|
+
end
|
408
|
+
|
409
|
+
def sanitized_url
|
410
|
+
return nil unless url
|
411
|
+
sanitizer.filter_url(s(url))
|
412
|
+
end
|
413
|
+
|
404
414
|
# Internal: Fetch local variables from first frame of backtrace.
|
405
415
|
#
|
406
416
|
# exception - The Exception containing the bindings stack.
|
407
417
|
#
|
408
418
|
# Returns a Hash of local variables
|
409
|
-
def local_variables_from_exception(exception, config
|
419
|
+
def local_variables_from_exception(exception, config)
|
410
420
|
return {} unless send_local_variables?(config)
|
411
421
|
return {} unless Exception === exception
|
412
422
|
return {} unless exception.respond_to?(:__honeybadger_bindings_stack)
|
@@ -419,9 +429,7 @@ module Honeybadger
|
|
419
429
|
binding ||= exception.__honeybadger_bindings_stack[0]
|
420
430
|
|
421
431
|
vars = binding.eval('local_variables')
|
422
|
-
|
423
|
-
|
424
|
-
sanitizer.sanitize(h)
|
432
|
+
Hash[vars.map {|arg| [arg, binding.eval(arg.to_s)]}]
|
425
433
|
end
|
426
434
|
|
427
435
|
# Internal: Should local variables be sent?
|
data/lib/honeybadger/plugin.rb
CHANGED
@@ -71,7 +71,7 @@ module Honeybadger
|
|
71
71
|
def ok?(config)
|
72
72
|
@requirements.all? {|r| Execution.new(config, &r).call }
|
73
73
|
rescue => e
|
74
|
-
config.logger.error(sprintf(
|
74
|
+
config.logger.error(sprintf("plugin error name=%s class=%s message=%s\n\t%s", name, e.class, e.message.dump, Array(e.backtrace).join("\n\t")))
|
75
75
|
false
|
76
76
|
end
|
77
77
|
|
@@ -89,7 +89,7 @@ module Honeybadger
|
|
89
89
|
|
90
90
|
@loaded
|
91
91
|
rescue => e
|
92
|
-
config.logger.error(sprintf(
|
92
|
+
config.logger.error(sprintf("plugin error name=%s class=%s message=%s\n\t%s", name, e.class, e.message.dump, Array(e.backtrace).join("\n\t")))
|
93
93
|
@loaded = true
|
94
94
|
false
|
95
95
|
end
|
@@ -3,41 +3,46 @@ module Honeybadger
|
|
3
3
|
class Sanitizer
|
4
4
|
OBJECT_WHITELIST = [Hash, Array, String, Integer, Float, TrueClass, FalseClass, NilClass]
|
5
5
|
|
6
|
+
FILTERED_REPLACEMENT = '[FILTERED]'.freeze
|
7
|
+
|
6
8
|
def initialize(opts = {})
|
7
|
-
@max_depth = opts
|
8
|
-
@filters = Array(opts
|
9
|
+
@max_depth = opts.fetch(:max_depth, 20)
|
10
|
+
@filters = Array(opts.fetch(:filters, nil)).collect do |f|
|
11
|
+
f.kind_of?(Regexp) ? f : f.to_s
|
12
|
+
end
|
9
13
|
end
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
15
|
+
def sanitize(data, depth = 0, stack = nil)
|
16
|
+
if recursive?(data)
|
17
|
+
return '[possible infinite recursion halted]' if stack && stack.include?(data.object_id)
|
18
|
+
stack = stack ? stack.dup : Set.new
|
19
|
+
stack << data.object_id
|
20
|
+
end
|
14
21
|
|
15
|
-
if data.
|
22
|
+
if data.kind_of?(String)
|
23
|
+
sanitize_string(data)
|
24
|
+
elsif data.respond_to?(:to_hash)
|
16
25
|
return '[max depth reached]' if depth >= max_depth
|
17
|
-
data.to_hash
|
18
|
-
|
26
|
+
hash = data.to_hash
|
27
|
+
new_hash = {}
|
28
|
+
hash.each_pair do |key, value|
|
29
|
+
k = key.kind_of?(Symbol) ? key : sanitize(key, depth+1, stack)
|
30
|
+
if filter_key?(k)
|
31
|
+
new_hash[k] = FILTERED_REPLACEMENT
|
32
|
+
else
|
33
|
+
new_hash[k] = sanitize(value, depth+1, stack)
|
34
|
+
end
|
19
35
|
end
|
36
|
+
new_hash
|
20
37
|
elsif data.respond_to?(:to_ary)
|
21
38
|
return '[max depth reached]' if depth >= max_depth
|
22
|
-
data.to_ary.
|
23
|
-
sanitize(value, depth+1, stack
|
24
|
-
end
|
39
|
+
data.to_ary.map do |value|
|
40
|
+
sanitize(value, depth+1, stack)
|
41
|
+
end.compact
|
25
42
|
elsif OBJECT_WHITELIST.any? {|c| data.kind_of?(c) }
|
26
43
|
data
|
27
44
|
else
|
28
|
-
data.to_s
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def filter(hash)
|
33
|
-
{}.tap do |filtered_hash|
|
34
|
-
hash.each_pair do |key, value|
|
35
|
-
if value.respond_to?(:to_hash)
|
36
|
-
filtered_hash[key] = filter(hash[key])
|
37
|
-
else
|
38
|
-
filtered_hash[key] = filter_key?(key) ? '[FILTERED]' : hash[key]
|
39
|
-
end
|
40
|
-
end
|
45
|
+
sanitize_string(data.to_s)
|
41
46
|
end
|
42
47
|
end
|
43
48
|
|
@@ -53,19 +58,44 @@ module Honeybadger
|
|
53
58
|
|
54
59
|
private
|
55
60
|
|
61
|
+
VALID_ENCODINGS = [Encoding::UTF_8, Encoding::ISO_8859_1].freeze
|
62
|
+
ENCODE_OPTS = { invalid: :replace, undef: :replace, replace: '?'.freeze }.freeze
|
63
|
+
UTF8_STRING = ''.freeze
|
64
|
+
|
56
65
|
attr_reader :max_depth, :filters
|
57
66
|
|
67
|
+
def recursive?(data)
|
68
|
+
data.respond_to?(:to_hash) || data.respond_to?(:to_ary)
|
69
|
+
end
|
70
|
+
|
58
71
|
def filter_key?(key)
|
59
72
|
return false unless filters
|
60
73
|
|
61
74
|
filters.any? do |filter|
|
62
75
|
if filter.is_a?(Regexp)
|
63
|
-
key.to_s
|
76
|
+
filter =~ key.to_s
|
64
77
|
else
|
65
78
|
key.to_s.eql?(filter.to_s)
|
66
79
|
end
|
67
80
|
end
|
68
81
|
end
|
82
|
+
|
83
|
+
def valid_encoding?(data)
|
84
|
+
data.valid_encoding? && (
|
85
|
+
VALID_ENCODINGS.include?(data.encoding) ||
|
86
|
+
VALID_ENCODINGS.include?(Encoding.compatible?(UTF8_STRING, data))
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def sanitize_string(data)
|
91
|
+
return data if valid_encoding?(data)
|
92
|
+
|
93
|
+
if data.encoding == Encoding::UTF_8
|
94
|
+
data.encode(Encoding::UTF_16, ENCODE_OPTS).encode!(Encoding::UTF_8)
|
95
|
+
else
|
96
|
+
data.encode(Encoding::UTF_8, ENCODE_OPTS)
|
97
|
+
end
|
98
|
+
end
|
69
99
|
end
|
70
100
|
end
|
71
101
|
end
|
data/lib/honeybadger/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: honeybadger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Honeybadger Industries LLC
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Make managing application errors a more pleasant experience.
|
14
14
|
email:
|
@@ -73,7 +73,6 @@ files:
|
|
73
73
|
- lib/honeybadger/templates/feedback_form.erb
|
74
74
|
- lib/honeybadger/trace.rb
|
75
75
|
- lib/honeybadger/util/http.rb
|
76
|
-
- lib/honeybadger/util/request_sanitizer.rb
|
77
76
|
- lib/honeybadger/util/sanitizer.rb
|
78
77
|
- lib/honeybadger/util/stats.rb
|
79
78
|
- lib/honeybadger/version.rb
|
@@ -1,35 +0,0 @@
|
|
1
|
-
module Honeybadger
|
2
|
-
module Util
|
3
|
-
class RequestSanitizer
|
4
|
-
|
5
|
-
def initialize(sanitizer)
|
6
|
-
@sanitizer = sanitizer
|
7
|
-
end
|
8
|
-
|
9
|
-
def sanitize(request_hash)
|
10
|
-
request_hash.merge({
|
11
|
-
url: sanitize_url(request_hash[:url]),
|
12
|
-
component: request_hash[:component],
|
13
|
-
action: request_hash[:action],
|
14
|
-
params: sanitize_hash(request_hash[:params]),
|
15
|
-
session: sanitize_hash(request_hash[:session]),
|
16
|
-
cgi_data: sanitize_hash(request_hash[:cgi_data])
|
17
|
-
})
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def sanitize_url(url)
|
23
|
-
if url
|
24
|
-
@sanitizer.filter_url(url)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def sanitize_hash(hash)
|
29
|
-
if hash
|
30
|
-
@sanitizer.filter(@sanitizer.sanitize(hash))
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|