honeybadger 2.7.2 → 3.0.0.beta1

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