honeybadger 2.7.2 → 3.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -3
  3. data/README.md +8 -15
  4. data/lib/honeybadger.rb +9 -232
  5. data/lib/honeybadger/agent.rb +292 -134
  6. data/lib/honeybadger/backend.rb +6 -6
  7. data/lib/honeybadger/backend/base.rb +11 -0
  8. data/lib/honeybadger/backend/server.rb +2 -14
  9. data/lib/honeybadger/cli.rb +0 -2
  10. data/lib/honeybadger/cli/deploy.rb +42 -0
  11. data/lib/honeybadger/cli/exec.rb +138 -0
  12. data/lib/honeybadger/cli/heroku.rb +1 -22
  13. data/lib/honeybadger/cli/install.rb +74 -0
  14. data/lib/honeybadger/cli/main.rb +138 -153
  15. data/lib/honeybadger/cli/notify.rb +66 -0
  16. data/lib/honeybadger/cli/test.rb +266 -0
  17. data/lib/honeybadger/config.rb +178 -162
  18. data/lib/honeybadger/config/defaults.rb +5 -5
  19. data/lib/honeybadger/config/env.rb +8 -6
  20. data/lib/honeybadger/config/ruby.rb +100 -0
  21. data/lib/honeybadger/config/yaml.rb +18 -19
  22. data/lib/honeybadger/const.rb +3 -16
  23. data/lib/honeybadger/context_manager.rb +50 -0
  24. data/lib/honeybadger/init/rails.rb +9 -21
  25. data/lib/honeybadger/init/rake.rb +2 -0
  26. data/lib/honeybadger/init/ruby.rb +9 -0
  27. data/lib/honeybadger/init/sinatra.rb +13 -6
  28. data/lib/honeybadger/notice.rb +29 -14
  29. data/lib/honeybadger/plugins/delayed_job/plugin.rb +4 -5
  30. data/lib/honeybadger/plugins/passenger.rb +1 -2
  31. data/lib/honeybadger/plugins/rails.rb +0 -28
  32. data/lib/honeybadger/plugins/resque.rb +2 -5
  33. data/lib/honeybadger/plugins/shoryuken.rb +2 -2
  34. data/lib/honeybadger/plugins/sidekiq.rb +2 -2
  35. data/lib/honeybadger/plugins/sucker_punch.rb +1 -0
  36. data/lib/honeybadger/plugins/thor.rb +2 -2
  37. data/lib/honeybadger/plugins/warden.rb +1 -0
  38. data/lib/honeybadger/rack/error_notifier.rb +11 -9
  39. data/lib/honeybadger/rack/user_feedback.rb +6 -4
  40. data/lib/honeybadger/rack/user_informer.rb +6 -4
  41. data/lib/honeybadger/ruby.rb +2 -0
  42. data/lib/honeybadger/singleton.rb +26 -0
  43. data/lib/honeybadger/util/http.rb +12 -0
  44. data/lib/honeybadger/util/request_hash.rb +71 -0
  45. data/lib/honeybadger/util/sanitizer.rb +101 -64
  46. data/lib/honeybadger/version.rb +1 -1
  47. data/lib/honeybadger/worker.rb +246 -0
  48. metadata +17 -13
  49. data/lib/honeybadger/agent/batch.rb +0 -50
  50. data/lib/honeybadger/agent/null_worker.rb +0 -26
  51. data/lib/honeybadger/agent/worker.rb +0 -243
  52. data/lib/honeybadger/cli/helpers.rb +0 -160
  53. data/lib/honeybadger/config/callbacks.rb +0 -70
  54. data/lib/honeybadger/plugins/unicorn.rb +0 -27
  55. data/lib/honeybadger/rack/metrics_reporter.rb +0 -16
  56. data/lib/honeybadger/rack/request_hash.rb +0 -55
@@ -1,5 +1,5 @@
1
1
  require 'delayed_job'
2
- require 'honeybadger'
2
+ require 'honeybadger/ruby'
3
3
 
4
4
  module Honeybadger
5
5
  module Plugins
@@ -36,14 +36,13 @@ module Honeybadger
36
36
 
37
37
  block.call(job)
38
38
  rescue Exception => error
39
- ::Honeybadger.notify_or_ignore(
39
+ ::Honeybadger.notify(
40
40
  :component => component,
41
41
  :action => action,
42
42
  :error_class => error.class.name,
43
43
  :error_message => "#{ error.class.name }: #{ error.message }",
44
- :backtrace => error.backtrace,
45
- :exception => error
46
- ) if job.attempts.to_i >= ::Honeybadger::Agent.config[:'delayed_job.attempt_threshold'].to_i
44
+ :backtrace => error.backtrace
45
+ ) if job.attempts.to_i >= ::Honeybadger.config[:'delayed_job.attempt_threshold'].to_i
47
46
  raise error
48
47
  ensure
49
48
  ::Honeybadger.context.clear!
@@ -10,12 +10,11 @@ module Honeybadger
10
10
  execution do
11
11
  ::PhusionPassenger.on_event(:starting_worker_process) do |forked|
12
12
  logger.debug('Starting passenger worker process')
13
- Honeybadger::Agent.fork if forked
14
13
  end
15
14
 
16
15
  ::PhusionPassenger.on_event(:stopping_worker_process) do
17
16
  logger.debug('Stopping passenger worker process')
18
- Honeybadger::Agent.stop
17
+ Honeybadger.stop
19
18
  end
20
19
  end
21
20
  end
@@ -34,34 +34,6 @@ module Honeybadger
34
34
  end
35
35
  end
36
36
 
37
- module ControllerMethods
38
- def honeybadger_request_data
39
- warn('#honeybadger_request_data has been deprecated and has no effect.')
40
- {}
41
- end
42
-
43
- def notify_honeybadger(*args, &block)
44
- warn('#notify_honeybadger has been deprecated; please use `Honeybadger.notify`.')
45
- Honeybadger.notify(*args, &block)
46
- end
47
-
48
- def notify_honeybadger_or_ignore(*args, &block)
49
- warn('#notify_honeybadger_or_ignore has been deprecated; please use `Honeybadger.notify`.')
50
- Honeybadger.notify(*args, &block)
51
- end
52
- end
53
-
54
- Plugin.register :rails_controller_methods do
55
- requirement { defined?(::Rails) }
56
-
57
- execution do
58
- ActiveSupport.on_load(:action_controller) do
59
- # Lazily load action_controller methods
60
- include ::Honeybadger::Plugins::Rails::ControllerMethods
61
- end
62
- end
63
- end
64
-
65
37
  Plugin.register :rails_exceptions_catcher do
66
38
  requirement { defined?(::Rails) }
67
39
 
@@ -1,5 +1,5 @@
1
1
  require 'honeybadger/plugin'
2
- require 'honeybadger'
2
+ require 'honeybadger/ruby'
3
3
 
4
4
  module Honeybadger
5
5
  module Plugins
@@ -20,7 +20,7 @@ module Honeybadger
20
20
 
21
21
  def send_exception?(e, args)
22
22
  return true unless respond_to?(:retry_criteria_valid?)
23
- return true if ::Honeybadger::Agent.config[:'resque.resque_retry.send_exceptions_when_retrying']
23
+ return true if ::Honeybadger.config[:'resque.resque_retry.send_exceptions_when_retrying']
24
24
 
25
25
  !retry_criteria_valid?(e)
26
26
  rescue => e
@@ -61,9 +61,6 @@ module Honeybadger
61
61
 
62
62
  execution do
63
63
  ::Resque::Job.send(:include, Installer)
64
- ::Resque.after_fork do |job|
65
- Honeybadger::Agent.fork
66
- end
67
64
  end
68
65
  end
69
66
  end
@@ -1,5 +1,5 @@
1
1
  require 'honeybadger/plugin'
2
- require 'honeybadger'
2
+ require 'honeybadger/ruby'
3
3
 
4
4
  module Honeybadger
5
5
  module Plugins
@@ -16,7 +16,7 @@ module Honeybadger
16
16
  yield
17
17
  rescue => e
18
18
  receive_count = sqs_msg.attributes['ApproximateReceiveCount'.freeze]
19
- if receive_count && ::Honeybadger::Agent.config[:'shoryuken.attempt_threshold'].to_i <= receive_count.to_i
19
+ if receive_count && ::Honeybadger.config[:'shoryuken.attempt_threshold'].to_i <= receive_count.to_i
20
20
  Honeybadger.notify(e, parameters: body)
21
21
  end
22
22
  raise e
@@ -1,5 +1,5 @@
1
1
  require 'honeybadger/plugin'
2
- require 'honeybadger'
2
+ require 'honeybadger/ruby'
3
3
 
4
4
  module Honeybadger
5
5
  module Plugins
@@ -27,7 +27,7 @@ module Honeybadger
27
27
  return if params['retry'.freeze] && params['retry_count'.freeze].to_i < config[:'sidekiq.attempt_threshold'].to_i
28
28
  opts = {parameters: params}
29
29
  opts[:component] = params['wrapped'.freeze] || params['class'.freeze] if config[:'sidekiq.use_component']
30
- Honeybadger.notify_or_ignore(ex, opts)
30
+ Honeybadger.notify(ex, opts)
31
31
  }
32
32
  end
33
33
  end
@@ -1,4 +1,5 @@
1
1
  require 'honeybadger/plugin'
2
+ require 'honeybadger/ruby'
2
3
 
3
4
  module Honeybadger
4
5
  Plugin.register do
@@ -1,5 +1,5 @@
1
1
  require 'honeybadger/plugin'
2
- require 'honeybadger'
2
+ require 'honeybadger/ruby'
3
3
 
4
4
  module Honeybadger
5
5
  module Plugins
@@ -16,7 +16,7 @@ module Honeybadger
16
16
  def invoke_command_with_honeybadger(*args)
17
17
  invoke_command_without_honeybadger(*args)
18
18
  rescue Exception => e
19
- Honeybadger.notify_or_ignore(e)
19
+ Honeybadger.notify(e)
20
20
  raise
21
21
  end
22
22
  end
@@ -1,4 +1,5 @@
1
1
  require 'honeybadger/plugin'
2
+ require 'honeybadger/ruby'
2
3
 
3
4
  module Honeybadger
4
5
  Plugin.register do
@@ -1,6 +1,7 @@
1
- require 'rack/request'
2
- require 'honeybadger'
3
1
  require 'forwardable'
2
+ require 'rack/request'
3
+
4
+ require 'honeybadger/ruby'
4
5
 
5
6
  module Honeybadger
6
7
  module Rack
@@ -21,13 +22,13 @@ module Honeybadger
21
22
  class ErrorNotifier
22
23
  extend Forwardable
23
24
 
24
- def initialize(app, config)
25
+ def initialize(app, agent = nil)
25
26
  @app = app
26
- @config = config
27
+ @agent = agent.kind_of?(Agent) ? agent : Honeybadger::Agent.instance
27
28
  end
28
29
 
29
30
  def call(env)
30
- config.with_request(::Rack::Request.new(env)) do
31
+ agent.with_rack_env(env) do
31
32
  begin
32
33
  env['honeybadger.config'] = config
33
34
  response = @app.call(env)
@@ -44,13 +45,14 @@ module Honeybadger
44
45
  response
45
46
  end
46
47
  ensure
47
- Honeybadger.context.clear!
48
+ agent.context.clear!
48
49
  end
49
50
 
50
51
  private
51
52
 
52
- attr_reader :config
53
- def_delegator :@config, :logger
53
+ attr_reader :agent
54
+ def_delegator :agent, :config
55
+ def_delegator :config, :logger
54
56
 
55
57
  def ignored_user_agent?(env)
56
58
  true if config[:'exceptions.ignored_user_agents'].
@@ -60,7 +62,7 @@ module Honeybadger
60
62
 
61
63
  def notify_honeybadger(exception, env)
62
64
  return if ignored_user_agent?(env)
63
- Honeybadger.notify_or_ignore(exception)
65
+ agent.notify(exception)
64
66
  end
65
67
 
66
68
  def framework_exception(env)
@@ -19,12 +19,13 @@ module Honeybadger
19
19
  class UserFeedback
20
20
  extend Forwardable
21
21
 
22
- def initialize(app, config)
22
+ def initialize(app, agent = nil)
23
23
  @app = app
24
- @config = config
24
+ @agent = agent.kind_of?(Agent) ? agent : Honeybadger::Agent.instance
25
25
  end
26
26
 
27
27
  def call(env)
28
+ return @app.call(env) unless config[:'feedback.enabled']
28
29
  status, headers, body = @app.call(env)
29
30
  if env['honeybadger.error_id'] && form = render_form(env['honeybadger.error_id'])
30
31
  new_body = []
@@ -67,8 +68,9 @@ module Honeybadger
67
68
 
68
69
  private
69
70
 
70
- attr_reader :config
71
- def_delegator :@config, :logger
71
+ attr_reader :agent
72
+ def_delegator :agent, :config
73
+ def_delegator :config, :logger
72
74
  end
73
75
  end
74
76
  end
@@ -5,9 +5,9 @@ module Honeybadger
5
5
  class UserInformer
6
6
  extend Forwardable
7
7
 
8
- def initialize(app, config)
8
+ def initialize(app, agent = nil)
9
9
  @app = app
10
- @config = config
10
+ @agent = agent.kind_of?(Agent) ? agent : Honeybadger::Agent.instance
11
11
  end
12
12
 
13
13
  def replacement(with)
@@ -15,6 +15,7 @@ module Honeybadger
15
15
  end
16
16
 
17
17
  def call(env)
18
+ return @app.call(env) unless config[:'user_informer.enabled']
18
19
  status, headers, body = @app.call(env)
19
20
  if env['honeybadger.error_id']
20
21
  new_body = []
@@ -31,8 +32,9 @@ module Honeybadger
31
32
 
32
33
  private
33
34
 
34
- attr_reader :config
35
- def_delegator :@config, :logger
35
+ attr_reader :agent
36
+ def_delegator :agent, :config
37
+ def_delegator :config, :logger
36
38
  end
37
39
  end
38
40
  end
@@ -0,0 +1,2 @@
1
+ require 'honeybadger/const'
2
+ require 'honeybadger/singleton'
@@ -0,0 +1,26 @@
1
+ require 'forwardable'
2
+ require 'honeybadger/agent'
3
+
4
+ # The Singleton module includes the public API for Honeybadger which can be
5
+ # accessed via the global agent (i.e. `Honeybadger.notify`) or via instances of
6
+ # the `Honeybadger::Agent` class.
7
+ module Honeybadger
8
+ extend Forwardable
9
+ extend self
10
+
11
+ def_delegators :'Agent.instance', :init!, :config, :configure, :notify,
12
+ :context, :get_context, :flush, :stop, :with_rack_env, :exception_filter,
13
+ :exception_fingerprint, :backtrace_filter
14
+
15
+ def load_plugins!
16
+ Dir[File.expand_path('../plugins/*.rb', __FILE__)].each do |plugin|
17
+ require plugin
18
+ end
19
+ Plugin.load!(self.config)
20
+ end
21
+
22
+ # Deprecated
23
+ def start(config = {})
24
+ true
25
+ end
26
+ end
@@ -21,6 +21,18 @@ module Honeybadger
21
21
  'User-Agent'.freeze => "HB-Ruby #{VERSION}; #{RUBY_VERSION}; #{RUBY_PLATFORM}".freeze
22
22
  }.freeze
23
23
 
24
+ ERRORS = [Timeout::Error,
25
+ Errno::EINVAL,
26
+ Errno::ECONNRESET,
27
+ Errno::ECONNREFUSED,
28
+ Errno::ENETUNREACH,
29
+ EOFError,
30
+ Net::HTTPBadResponse,
31
+ Net::HTTPHeaderSyntaxError,
32
+ Net::ProtocolError,
33
+ OpenSSL::SSL::SSLError,
34
+ SocketError].freeze
35
+
24
36
  def initialize(config)
25
37
  @config = config
26
38
  end
@@ -0,0 +1,71 @@
1
+ require 'set'
2
+
3
+ module Honeybadger
4
+ module Util
5
+ # Internal: Constructs a request hash from a Rack::Request matching the
6
+ # /v1/notices API specification.
7
+ module RequestHash
8
+ HTTP_HEADER_PREFIX = 'HTTP_'.freeze
9
+
10
+ CGI_WHITELIST = %w(
11
+ AUTH_TYPE
12
+ CONTENT_LENGTH
13
+ CONTENT_TYPE
14
+ GATEWAY_INTERFACE
15
+ HTTPS
16
+ REMOTE_ADDR
17
+ REMOTE_HOST
18
+ REMOTE_IDENT
19
+ REMOTE_USER
20
+ REQUEST_METHOD
21
+ SERVER_NAME
22
+ SERVER_PORT
23
+ SERVER_PROTOCOL
24
+ SERVER_SOFTWARE
25
+ ).freeze
26
+
27
+ def self.from_env(env)
28
+ return {} unless defined?(::Rack::Request)
29
+
30
+ hash, request = {}, ::Rack::Request.new(env)
31
+
32
+ hash[:url] = extract_url(request)
33
+ hash[:params] = extract_params(request)
34
+ hash[:component] = hash[:params]['controller']
35
+ hash[:action] = hash[:params]['action']
36
+ hash[:session] = extract_session(request)
37
+ hash[:cgi_data] = extract_cgi_data(request)
38
+
39
+ hash
40
+ end
41
+
42
+ def self.extract_url(request)
43
+ request.env['honeybadger.request.url'] || request.url
44
+ rescue => e
45
+ "Failed to access URL -- #{e}"
46
+ end
47
+
48
+ def self.extract_params(request)
49
+ (request.env['action_dispatch.request.parameters'] || request.params).to_hash || {}
50
+ rescue => e
51
+ { error: "Failed to access params -- #{e}" }
52
+ end
53
+
54
+ def self.extract_session(request)
55
+ request.session.to_hash
56
+ rescue => e
57
+ # Rails raises ArgumentError when `config.secret_token` is missing, and
58
+ # ActionDispatch::Session::SessionRestoreError when the session can't be
59
+ # restored.
60
+ { error: "Failed to access session data -- #{e}" }
61
+ end
62
+
63
+ def self.extract_cgi_data(request)
64
+ request.env.each_with_object({}) do |(k,v), env|
65
+ next unless k.start_with?(HTTP_HEADER_PREFIX) || CGI_WHITELIST.include?(k)
66
+ env[k] = v
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,133 +1,170 @@
1
+ require 'bigdecimal'
1
2
  require 'set'
2
3
 
3
4
  module Honeybadger
4
5
  module Util
6
+ # Internal: Sanitizer sanitizes data for sending to Honeybadger's API. The
7
+ # filters are based on Rails' HTTP parameter filter.
5
8
  class Sanitizer
6
- FILTERED_REPLACEMENT = '[FILTERED]'.freeze
9
+ COOKIE_PAIRS = /[;,]\s?/
10
+ COOKIE_SEP = '='.freeze
11
+ COOKIE_PAIR_SEP = '; '.freeze
7
12
 
8
- TRUNCATION_REPLACEMENT = '[TRUNCATED]'.freeze
13
+ ENCODE_OPTS = { invalid: :replace, undef: :replace, replace: '?'.freeze }.freeze
14
+
15
+ FILTERED = '[FILTERED]'.freeze
16
+
17
+ IMMUTABLE = [NilClass, FalseClass, TrueClass, Symbol, Numeric, BigDecimal, Method].freeze
9
18
 
10
19
  MAX_STRING_SIZE = 2048
11
20
 
12
- COOKIE_PAIRS = /[;,]\s?/
13
- COOKIE_SEP = '='.freeze
14
- COOKIE_PAIR_SEP = '; '.freeze
21
+ TRUNCATION_REPLACEMENT = '[TRUNCATED]'.freeze
22
+
23
+ VALID_ENCODINGS = [Encoding::UTF_8, Encoding::ISO_8859_1].freeze
24
+
25
+ def self.sanitize(data)
26
+ @sanitizer ||= new
27
+ @sanitizer.sanitize(data)
28
+ end
15
29
 
16
- def initialize(opts = {})
17
- @max_depth = opts.fetch(:max_depth, 20)
30
+ def initialize(max_depth: 20, filters: [])
31
+ @filters = !filters.empty?
32
+ @max_depth = max_depth
18
33
 
19
- if filters = opts.fetch(:filters, nil)
20
- @filters = Array(filters).collect do |f|
21
- f.kind_of?(Regexp) ? f : f.to_s
34
+ strings, @regexps, @blocks = [], [], []
35
+
36
+ filters.each do |item|
37
+ case item
38
+ when Proc
39
+ @blocks << item
40
+ when Regexp
41
+ @regexps << item
42
+ else
43
+ strings << Regexp.escape(item.to_s)
22
44
  end
23
45
  end
46
+
47
+ @deep_regexps, @regexps = @regexps.partition { |r| r.to_s.include?('\\.'.freeze) }
48
+ deep_strings, @strings = strings.partition { |s| s.include?('\\.'.freeze) }
49
+
50
+ @regexps << Regexp.new(strings.join('|'.freeze), true) unless strings.empty?
51
+ @deep_regexps << Regexp.new(deep_strings.join('|'.freeze), true) unless deep_strings.empty?
24
52
  end
25
53
 
26
- def sanitize(data, depth = 0, stack = nil)
27
- if data.kind_of?(Hash) || data.kind_of?(Array) || data.kind_of?(Set)
28
- return '[possible infinite recursion halted]' if stack && stack.include?(data.object_id)
54
+ def sanitize(data, depth = 0, stack = nil, parents = [])
55
+ if enumerable?(data)
56
+ return '[possible infinite recursion halted]'.freeze if stack && stack.include?(data.object_id)
29
57
  stack = stack ? stack.dup : Set.new
30
58
  stack << data.object_id
31
59
  end
32
60
 
33
61
  case data
34
62
  when Hash
35
- return '[max depth reached]' if depth >= max_depth
63
+ return '[max depth reached]'.freeze if depth >= max_depth
36
64
  hash = data.to_hash
37
65
  new_hash = {}
38
66
  hash.each_pair do |key, value|
39
- k = key.kind_of?(Symbol) ? key : sanitize(key, depth+1, stack)
40
- if filter_key?(k)
41
- new_hash[k] = FILTERED_REPLACEMENT
67
+ parents.push(key) if deep_regexps
68
+ key = key.kind_of?(Symbol) ? key : sanitize(key, depth+1, stack, parents)
69
+ if filter_key?(key, parents)
70
+ new_hash[key] = FILTERED
42
71
  else
43
- new_hash[k] = sanitize(value, depth+1, stack)
72
+ value = sanitize(value, depth+1, stack, parents)
73
+ if blocks.any? && !enumerable?(value)
74
+ key = key.dup if can_dup?(key)
75
+ value = value.dup if can_dup?(value)
76
+ blocks.each { |b| b.call(key, value) }
77
+ end
78
+ new_hash[key] = value
44
79
  end
80
+ parents.pop if deep_regexps
45
81
  end
46
82
  new_hash
47
83
  when Array, Set
48
- return '[max depth reached]' if depth >= max_depth
84
+ return '[max depth reached]'.freeze if depth >= max_depth
49
85
  data.to_a.map do |value|
50
- sanitize(value, depth+1, stack)
86
+ sanitize(value, depth+1, stack, parents)
51
87
  end.compact
52
88
  when Numeric, TrueClass, FalseClass, NilClass
53
89
  data
54
90
  when String
55
- self.class.sanitize_string(data.to_s)
91
+ sanitize_string(data)
56
92
  else # all other objects:
57
- self.class.sanitize_string(data.to_s)
93
+ data.respond_to?(:to_s) ? sanitize_string(data.to_s) : nil
58
94
  end
59
95
  end
60
96
 
97
+ def sanitize_string(string)
98
+ string = valid_encoding(string.to_s)
99
+ return string unless string.respond_to?(:size) && string.size > MAX_STRING_SIZE
100
+ string[0...MAX_STRING_SIZE] + TRUNCATION_REPLACEMENT
101
+ end
102
+
61
103
  def filter_cookies(raw_cookies)
62
- return raw_cookies unless filters
104
+ return raw_cookies unless filters?
63
105
 
64
106
  cookies = []
65
- raw_cookies.split(COOKIE_PAIRS).each do |pair|
107
+
108
+ raw_cookies.to_s.split(COOKIE_PAIRS).each do |pair|
66
109
  name, values = pair.split(COOKIE_SEP, 2)
67
- values = FILTERED_REPLACEMENT if filter_key?(name)
68
- cookies << "#{ name }=#{ values }"
110
+ values = FILTERED if filter_key?(name)
111
+ cookies << "#{name}=#{values}"
69
112
  end
70
113
 
71
114
  cookies.join(COOKIE_PAIR_SEP)
72
115
  end
73
116
 
74
117
  def filter_url(url)
75
- return url unless filters
118
+ return url unless filters?
76
119
 
77
120
  filtered_url = url.to_s.dup
121
+
78
122
  filtered_url.scan(/(?:^|&|\?)([^=?&]+)=([^&]+)/).each do |m|
79
123
  next unless filter_key?(m[0])
80
- filtered_url.gsub!(/#{Regexp.escape(m[1])}/, FILTERED_REPLACEMENT)
124
+ filtered_url.gsub!(/#{Regexp.escape(m[1])}/, FILTERED)
81
125
  end
82
126
 
83
127
  filtered_url
84
128
  end
85
129
 
86
- VALID_ENCODINGS = [Encoding::UTF_8, Encoding::ISO_8859_1].freeze
87
- ENCODE_OPTS = { invalid: :replace, undef: :replace, replace: '?'.freeze }.freeze
88
- UTF8_STRING = ''.freeze
89
-
90
- class << self
91
-
92
- def valid_encoding?(data)
93
- data.valid_encoding? && (
94
- VALID_ENCODINGS.include?(data.encoding) ||
95
- VALID_ENCODINGS.include?(Encoding.compatible?(UTF8_STRING, data))
96
- )
97
- end
130
+ private
98
131
 
99
- def valid_encoding(data)
100
- return data if valid_encoding?(data)
132
+ attr_reader :max_depth, :regexps, :deep_regexps, :blocks
101
133
 
102
- if data.encoding == Encoding::UTF_8
103
- data.encode(Encoding::UTF_16, ENCODE_OPTS).encode!(Encoding::UTF_8)
104
- else
105
- data.encode(Encoding::UTF_8, ENCODE_OPTS)
106
- end
107
- end
134
+ def filters?
135
+ !!@filters
136
+ end
108
137
 
109
- def sanitize_string(data)
110
- data = valid_encoding(data.to_s)
111
- return data unless data.respond_to?(:size) && data.size > MAX_STRING_SIZE
112
- data[0...MAX_STRING_SIZE] + TRUNCATION_REPLACEMENT
113
- end
138
+ def filter_key?(key, parents = nil)
139
+ return false unless filters?
140
+ return true if regexps.any? { |r| key =~ r }
141
+ return true if deep_regexps && parents && (joined = parents.join(".")) && deep_regexps.any? { |r| joined =~ r }
142
+ false
143
+ end
114
144
 
145
+ def valid_encoding?(string)
146
+ string.valid_encoding? && (
147
+ VALID_ENCODINGS.include?(string.encoding) ||
148
+ VALID_ENCODINGS.include?(Encoding.compatible?(''.freeze, string))
149
+ )
115
150
  end
116
151
 
117
- private
152
+ def valid_encoding(string)
153
+ return string if valid_encoding?(string)
118
154
 
119
- attr_reader :max_depth, :filters
155
+ if string.encoding == Encoding::UTF_8
156
+ string.encode(Encoding::UTF_16, ENCODE_OPTS).encode!(Encoding::UTF_8)
157
+ else
158
+ string.encode(Encoding::UTF_8, ENCODE_OPTS)
159
+ end
160
+ end
120
161
 
121
- def filter_key?(key)
122
- return false unless filters
162
+ def enumerable?(data)
163
+ data.kind_of?(Hash) || data.kind_of?(Array) || data.kind_of?(Set)
164
+ end
123
165
 
124
- filters.any? do |filter|
125
- if filter.is_a?(Regexp)
126
- filter =~ key.to_s
127
- else
128
- key.to_s.eql?(filter.to_s)
129
- end
130
- end
166
+ def can_dup?(obj)
167
+ !IMMUTABLE.any? {|k| obj.kind_of?(k) }
131
168
  end
132
169
  end
133
170
  end