elastic-transport 8.0.0 → 8.4.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.
- checksums.yaml +4 -4
- data/.github/workflows/license.yml +2 -2
- data/.github/workflows/otel.yml +48 -0
- data/.github/workflows/tests.yml +45 -5
- data/.gitignore +1 -1
- data/CHANGELOG.md +131 -8
- data/CONTRIBUTING.md +64 -0
- data/Gemfile +10 -9
- data/Gemfile-faraday1.gemfile +40 -0
- data/README.md +7 -528
- data/Rakefile +48 -1
- data/elastic-transport.gemspec +6 -9
- data/lib/elastic/transport/client.rb +66 -45
- data/lib/elastic/transport/meta_header.rb +21 -12
- data/lib/elastic/transport/opentelemetry.rb +166 -0
- data/lib/elastic/transport/transport/base.rb +74 -54
- data/lib/elastic/transport/transport/errors.rb +2 -4
- data/lib/elastic/transport/transport/http/curb.rb +30 -26
- data/lib/elastic/transport/transport/http/faraday.rb +30 -27
- data/lib/elastic/transport/transport/http/manticore.rb +10 -4
- data/lib/elastic/transport/transport/response.rb +3 -3
- data/lib/elastic/transport/transport/serializer/multi_json.rb +3 -3
- data/lib/elastic/transport/transport/sniffer.rb +3 -1
- data/lib/elastic/transport/version.rb +1 -1
- data/lib/elastic/transport.rb +1 -0
- data/spec/elastic/transport/base_spec.rb +26 -25
- data/spec/elastic/transport/client_spec.rb +91 -18
- data/spec/elastic/transport/http/manticore_spec.rb +20 -2
- data/spec/elastic/transport/meta_header_spec.rb +26 -11
- data/spec/elastic/transport/opentelemetry_spec.rb +325 -0
- data/spec/elastic/transport/sniffer_spec.rb +18 -0
- data/spec/spec_helper.rb +16 -1
- data/test/integration/jruby_test.rb +1 -1
- data/test/integration/transport_test.rb +86 -40
- data/test/test_helper.rb +9 -6
- data/test/unit/adapters_test.rb +104 -0
- data/test/unit/connection_test.rb +35 -37
- data/test/unit/transport_base_test.rb +7 -8
- data/test/unit/transport_curb_test.rb +2 -3
- data/test/unit/transport_manticore_test.rb +1 -1
- metadata +23 -76
@@ -15,7 +15,6 @@
|
|
15
15
|
# specific language governing permissions and limitations
|
16
16
|
# under the License.
|
17
17
|
|
18
|
-
require 'base64'
|
19
18
|
require 'elastic/transport/meta_header'
|
20
19
|
|
21
20
|
module Elastic
|
@@ -84,7 +83,7 @@ module Elastic
|
|
84
83
|
#
|
85
84
|
# @option arguments [Integer] :sniffer_timeout Timeout for reloading connections in seconds (1 by default)
|
86
85
|
#
|
87
|
-
# @option arguments [Boolean,Number] :retry_on_failure Retry X times when request fails before raising
|
86
|
+
# @option arguments [Boolean,Number] :retry_on_failure Retry X times when request fails before raising an
|
88
87
|
# exception (false by default)
|
89
88
|
# @option arguments [Number] :delay_on_retry Delay in milliseconds between each retry (0 by default)
|
90
89
|
#
|
@@ -92,7 +91,8 @@ module Elastic
|
|
92
91
|
#
|
93
92
|
# @option arguments [Boolean] :reload_on_failure Reload connections after failure (false by default)
|
94
93
|
#
|
95
|
-
# @option arguments [Integer] :request_timeout The request timeout to be passed to transport in options
|
94
|
+
# @option arguments [Integer] :request_timeout The request timeout to be passed to transport in options in seconds
|
95
|
+
# (the default value is taken from the transport)
|
96
96
|
#
|
97
97
|
# @option arguments [Symbol] :adapter A specific adapter for Faraday (e.g. `:patron`)
|
98
98
|
#
|
@@ -111,7 +111,7 @@ module Elastic
|
|
111
111
|
#
|
112
112
|
# @option arguments [String] :send_get_body_as Specify the HTTP method to use for GET requests with a body.
|
113
113
|
# (Default: GET)
|
114
|
-
# @option arguments [
|
114
|
+
# @option arguments [Boolean] :compression Whether to compress requests. Gzip compression will be used.
|
115
115
|
# The default is false. Responses will automatically be inflated if they are compressed.
|
116
116
|
# If a custom transport object is used, it must handle the request compression and response inflation.
|
117
117
|
#
|
@@ -122,10 +122,9 @@ module Elastic
|
|
122
122
|
# @yield [faraday] Access and configure the `Faraday::Connection` instance directly with a block
|
123
123
|
#
|
124
124
|
def initialize(arguments = {}, &block)
|
125
|
-
@
|
126
|
-
@arguments
|
127
|
-
@arguments[:
|
128
|
-
@arguments[:tracer] ||= @arguments[:trace] ? DEFAULT_TRACER.call() : nil
|
125
|
+
@arguments = arguments.transform_keys(&:to_sym)
|
126
|
+
@arguments[:logger] ||= @arguments[:log] ? DEFAULT_LOGGER.call : nil
|
127
|
+
@arguments[:tracer] ||= @arguments[:trace] ? DEFAULT_TRACER.call : nil
|
129
128
|
@arguments[:reload_connections] ||= false
|
130
129
|
@arguments[:retry_on_failure] ||= false
|
131
130
|
@arguments[:delay_on_retry] ||= 0
|
@@ -134,15 +133,8 @@ module Elastic
|
|
134
133
|
@arguments[:transport_options] ||= {}
|
135
134
|
@arguments[:http] ||= {}
|
136
135
|
@arguments[:enable_meta_header] = arguments.fetch(:enable_meta_header, true)
|
137
|
-
@options[:http] ||= {}
|
138
|
-
|
139
|
-
@hosts ||= __extract_hosts(@arguments[:hosts] ||
|
140
|
-
@arguments[:host] ||
|
141
|
-
@arguments[:url] ||
|
142
|
-
@arguments[:urls] ||
|
143
|
-
ENV['ELASTICSEARCH_URL'] ||
|
144
|
-
DEFAULT_HOST)
|
145
136
|
|
137
|
+
@hosts ||= extract_hosts
|
146
138
|
@send_get_body_as = @arguments[:send_get_body_as] || 'GET'
|
147
139
|
@ca_fingerprint = @arguments.delete(:ca_fingerprint)
|
148
140
|
|
@@ -155,7 +147,7 @@ module Elastic
|
|
155
147
|
else
|
156
148
|
@transport_class = @arguments[:transport_class] || DEFAULT_TRANSPORT_CLASS
|
157
149
|
@transport = if @transport_class == Transport::HTTP::Faraday
|
158
|
-
@arguments[:adapter] ||=
|
150
|
+
@arguments[:adapter] ||= auto_detect_adapter
|
159
151
|
set_meta_header # from include MetaHeader
|
160
152
|
@transport_class.new(hosts: @hosts, options: @arguments) do |faraday|
|
161
153
|
faraday.adapter(@arguments[:adapter])
|
@@ -166,14 +158,39 @@ module Elastic
|
|
166
158
|
@transport_class.new(hosts: @hosts, options: @arguments)
|
167
159
|
end
|
168
160
|
end
|
161
|
+
|
162
|
+
if defined?(::OpenTelemetry) && ENV[OpenTelemetry::ENV_VARIABLE_ENABLED] != 'false'
|
163
|
+
@otel = OpenTelemetry.new(@arguments)
|
164
|
+
end
|
169
165
|
end
|
170
166
|
|
171
167
|
# Performs a request through delegation to {#transport}.
|
172
168
|
#
|
173
|
-
def perform_request(method, path, params = {}, body = nil, headers = nil)
|
174
|
-
method = @send_get_body_as if 'GET'
|
169
|
+
def perform_request(method, path, params = {}, body = nil, headers = nil, opts = {})
|
170
|
+
method = @send_get_body_as if method == 'GET' && body
|
175
171
|
validate_ca_fingerprints if @ca_fingerprint
|
176
|
-
|
172
|
+
if @otel
|
173
|
+
# If no endpoint is specified in the opts, use the HTTP method name
|
174
|
+
span_name = opts[:endpoint] || method
|
175
|
+
@otel.tracer.in_span(span_name) do |span|
|
176
|
+
span['http.request.method'] = method
|
177
|
+
span['db.system'] = 'elasticsearch'
|
178
|
+
opts[:defined_params]&.each do |k, v|
|
179
|
+
if v.respond_to?(:join)
|
180
|
+
span["db.elasticsearch.path_parts.#{k}"] = v.join(',')
|
181
|
+
else
|
182
|
+
span["db.elasticsearch.path_parts.#{k}"] = v
|
183
|
+
end
|
184
|
+
end
|
185
|
+
if body_as_json = @otel.process_body(body, opts[:endpoint])
|
186
|
+
span['db.statement'] = body_as_json
|
187
|
+
end
|
188
|
+
span['db.operation'] = opts[:endpoint] if opts[:endpoint]
|
189
|
+
transport.perform_request(method, path, params || {}, body, headers)
|
190
|
+
end
|
191
|
+
else
|
192
|
+
transport.perform_request(method, path, params || {}, body, headers)
|
193
|
+
end
|
177
194
|
end
|
178
195
|
|
179
196
|
private
|
@@ -211,14 +228,6 @@ module Elastic
|
|
211
228
|
)
|
212
229
|
end
|
213
230
|
|
214
|
-
def add_header(header)
|
215
|
-
headers = @arguments[:transport_options]&.[](:headers) || {}
|
216
|
-
headers.merge!(header)
|
217
|
-
@arguments[:transport_options].merge!(
|
218
|
-
headers: headers
|
219
|
-
)
|
220
|
-
end
|
221
|
-
|
222
231
|
# Normalizes and returns hosts configuration.
|
223
232
|
#
|
224
233
|
# Arrayifies the `hosts_config` argument and extracts `host` and `port` info from strings.
|
@@ -231,7 +240,7 @@ module Elastic
|
|
231
240
|
#
|
232
241
|
# @api private
|
233
242
|
#
|
234
|
-
def
|
243
|
+
def extract_hosts
|
235
244
|
hosts = case hosts_config
|
236
245
|
when String
|
237
246
|
hosts_config.split(',').map { |h| h.strip! || h }
|
@@ -242,12 +251,20 @@ module Elastic
|
|
242
251
|
else
|
243
252
|
Array(hosts_config)
|
244
253
|
end
|
254
|
+
host_list = hosts.map { |host| parse_host(host) }
|
255
|
+
@arguments[:randomize_hosts] ? host_list.shuffle! : host_list
|
256
|
+
end
|
245
257
|
|
246
|
-
|
247
|
-
@
|
258
|
+
def hosts_config
|
259
|
+
@arguments[:hosts] ||
|
260
|
+
@arguments[:host] ||
|
261
|
+
@arguments[:url] ||
|
262
|
+
@arguments[:urls] ||
|
263
|
+
ENV['ELASTICSEARCH_URL'] ||
|
264
|
+
DEFAULT_HOST
|
248
265
|
end
|
249
266
|
|
250
|
-
def
|
267
|
+
def parse_host(host)
|
251
268
|
host_parts = case host
|
252
269
|
when String
|
253
270
|
if host =~ /^[a-z]+\:\/\//
|
@@ -281,8 +298,8 @@ module Elastic
|
|
281
298
|
raise ArgumentError, "Please pass host as a String, URI or Hash -- #{host.class} given."
|
282
299
|
end
|
283
300
|
|
284
|
-
@
|
285
|
-
@
|
301
|
+
@arguments[:http][:user] ||= host_parts[:user]
|
302
|
+
@arguments[:http][:password] ||= host_parts[:password]
|
286
303
|
host_parts[:port] = host_parts[:port].to_i if host_parts[:port]
|
287
304
|
host_parts[:path].chomp!('/') if host_parts[:path]
|
288
305
|
host_parts
|
@@ -291,24 +308,28 @@ module Elastic
|
|
291
308
|
# Auto-detect the best adapter (HTTP "driver") available, based on libraries
|
292
309
|
# loaded by the user, preferring those with persistent connections
|
293
310
|
# ("keep-alive") by default
|
311
|
+
# Check adapters based on the usage of Faraday 1 or 2. Faraday should be defined here
|
312
|
+
# since this is only called when transport class is Transport::HTTP::Faraday
|
294
313
|
#
|
295
314
|
# @return [Symbol]
|
296
315
|
#
|
297
316
|
# @api private
|
298
317
|
#
|
299
|
-
def
|
300
|
-
|
301
|
-
|
302
|
-
:
|
303
|
-
|
304
|
-
:
|
305
|
-
|
306
|
-
:
|
307
|
-
when defined?(::Net::HTTP::Persistent)
|
308
|
-
:net_http_persistent
|
318
|
+
def auto_detect_adapter
|
319
|
+
if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new(2)
|
320
|
+
return :patron if defined?(Faraday::Adapter::Patron)
|
321
|
+
return :typhoeus if defined?(Faraday::Adapter::Typhoeus)
|
322
|
+
return :httpclient if defined?(Faraday::Adapter::HTTPClient)
|
323
|
+
return :net_http_persistent if defined?(Faraday::Adapter::NetHttpPersistent)
|
324
|
+
return :excon if defined?(Faraday::Adapter::Excon)
|
325
|
+
return :async_http if defined?(Async::HTTP::Faraday)
|
309
326
|
else
|
310
|
-
::
|
327
|
+
return :patron if defined?(::Patron)
|
328
|
+
return :typhoeus if defined?(::Typhoeus)
|
329
|
+
return :httpclient if defined?(::HTTPClient)
|
330
|
+
return :net_http_persistent if defined?(::Net::HTTP::Persistent)
|
311
331
|
end
|
332
|
+
::Faraday.default_adapter
|
312
333
|
end
|
313
334
|
end
|
314
335
|
end
|
@@ -15,8 +15,6 @@
|
|
15
15
|
# specific language governing permissions and limitations
|
16
16
|
# under the License.
|
17
17
|
|
18
|
-
require 'base64'
|
19
|
-
|
20
18
|
module Elastic
|
21
19
|
module Transport
|
22
20
|
# Methods for the Elastic meta header used by Cloud.
|
@@ -43,12 +41,18 @@ module Elastic
|
|
43
41
|
def meta_header_service_version
|
44
42
|
if enterprise_search?
|
45
43
|
Elastic::ENTERPRISE_SERVICE_VERSION
|
44
|
+
elsif serverless?
|
45
|
+
if defined?(Elastic::ES_SERVERLESS_SERVICE_VERSION)
|
46
|
+
Elastic::ES_SERVERLESS_SERVICE_VERSION
|
47
|
+
else
|
48
|
+
Elastic::ELASTICSEARCH_SERVICE_VERSION
|
49
|
+
end
|
46
50
|
elsif elasticsearch?
|
47
51
|
Elastic::ELASTICSEARCH_SERVICE_VERSION
|
48
52
|
elsif defined?(Elasticsearch::VERSION)
|
49
53
|
[:es, client_meta_version(Elasticsearch::VERSION)]
|
50
54
|
else
|
51
|
-
[:
|
55
|
+
[:et, client_meta_version(Elastic::Transport::VERSION)]
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
@@ -62,6 +66,11 @@ module Elastic
|
|
62
66
|
called_from?('elasticsearch')
|
63
67
|
end
|
64
68
|
|
69
|
+
def serverless?
|
70
|
+
defined?(ElasticsearchServerless::CLIENT_VERSION) &&
|
71
|
+
called_from?('elasticsearch-serverless')
|
72
|
+
end
|
73
|
+
|
65
74
|
def called_from?(service)
|
66
75
|
!caller.select { |c| c.match?(service) }.empty?
|
67
76
|
end
|
@@ -72,7 +81,7 @@ module Elastic
|
|
72
81
|
def client_meta_version(version)
|
73
82
|
regexp = /^([0-9]+\.[0-9]+\.[0-9]+)(\.?[a-z0-9.-]+)?$/
|
74
83
|
match = version.match(regexp)
|
75
|
-
return "#{match[1]}p" if
|
84
|
+
return "#{match[1]}p" if match[2]
|
76
85
|
|
77
86
|
version
|
78
87
|
end
|
@@ -103,31 +112,31 @@ module Elastic
|
|
103
112
|
adapter_version = case @arguments[:adapter]
|
104
113
|
when :patron
|
105
114
|
version = Patron::VERSION if defined?(::Patron::VERSION)
|
106
|
-
{pt: version}
|
115
|
+
{ pt: version }
|
107
116
|
when :net_http
|
108
117
|
version = if defined?(Net::HTTP::VERSION)
|
109
118
|
Net::HTTP::VERSION
|
110
119
|
elsif defined?(Net::HTTP::HTTPVersion)
|
111
120
|
Net::HTTP::HTTPVersion
|
112
121
|
end
|
113
|
-
{nh: version}
|
122
|
+
{ nh: version }
|
114
123
|
when :typhoeus
|
115
124
|
version = Typhoeus::VERSION if defined?(::Typhoeus::VERSION)
|
116
|
-
{ty: version}
|
125
|
+
{ ty: version }
|
117
126
|
when :httpclient
|
118
127
|
version = HTTPClient::VERSION if defined?(HTTPClient::VERSION)
|
119
|
-
{hc: version}
|
128
|
+
{ hc: version }
|
120
129
|
when :net_http_persistent
|
121
130
|
version = Net::HTTP::Persistent::VERSION if defined?(Net::HTTP::Persistent::VERSION)
|
122
|
-
{np: version}
|
131
|
+
{ np: version }
|
123
132
|
else
|
124
133
|
{}
|
125
134
|
end
|
126
|
-
{fd: Faraday::VERSION}.merge(adapter_version)
|
135
|
+
{ fd: Faraday::VERSION }.merge(adapter_version)
|
127
136
|
elsif defined?(Transport::HTTP::Curb) && @transport_class == Transport::HTTP::Curb
|
128
|
-
{cl: Curl::CURB_VERSION}
|
137
|
+
{ cl: Curl::CURB_VERSION }
|
129
138
|
elsif defined?(Transport::HTTP::Manticore) && @transport_class == Transport::HTTP::Manticore
|
130
|
-
{mc: Manticore::VERSION}
|
139
|
+
{ mc: Manticore::VERSION }
|
131
140
|
end
|
132
141
|
end
|
133
142
|
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
module Elastic
|
19
|
+
module Transport
|
20
|
+
# Wrapper object for Open Telemetry objects, associated config and functionality.
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
class OpenTelemetry
|
24
|
+
OTEL_TRACER_NAME = 'elasticsearch-api'.freeze
|
25
|
+
# Valid values for the enabled config are 'true' and 'false'. Default is 'true'.
|
26
|
+
ENV_VARIABLE_ENABLED = 'OTEL_RUBY_INSTRUMENTATION_ELASTICSEARCH_ENABLED'.freeze
|
27
|
+
# Describes how to handle search queries in the request body when assigned to
|
28
|
+
# a span attribute.
|
29
|
+
# Valid values are 'raw', 'omit', 'sanitize'. Default is 'omit'.
|
30
|
+
ENV_VARIABLE_BODY_STRATEGY = 'OTEL_RUBY_INSTRUMENTATION_ELASTICSEARCH_CAPTURE_SEARCH_QUERY'.freeze
|
31
|
+
ENV_VARIABLE_DEPRECATED_BODY_STRATEGY = 'OTEL_INSTRUMENTATION_ELASTICSEARCH_CAPTURE_SEARCH_QUERY'.freeze
|
32
|
+
DEFAULT_BODY_STRATEGY = 'omit'.freeze
|
33
|
+
# A string list of keys whose values are redacted. This is only relevant if the body strategy is
|
34
|
+
# 'sanitize'. For example, a config 'sensitive-key,other-key' will redact the values at
|
35
|
+
# 'sensitive-key' and 'other-key' in addition to the default keys.
|
36
|
+
ENV_VARIABLE_BODY_SANITIZE_KEYS = 'OTEL_RUBY_INSTRUMENTATION_ELASTICSEARCH_SEARCH_QUERY_SANITIZE_KEYS'.freeze
|
37
|
+
|
38
|
+
# A list of the Elasticsearch endpoints that qualify as "search" endpoints. The search query in
|
39
|
+
# the request body may be captured for these endpoints, depending on the body capture strategy.
|
40
|
+
SEARCH_ENDPOINTS = Set[
|
41
|
+
'search',
|
42
|
+
'async_search.submit',
|
43
|
+
'msearch',
|
44
|
+
'eql.search',
|
45
|
+
'terms_enum',
|
46
|
+
'search_template',
|
47
|
+
'msearch_template',
|
48
|
+
'render_search_template',
|
49
|
+
]
|
50
|
+
|
51
|
+
# Initialize the Open Telemetry wrapper object. Takes the options originally passed to
|
52
|
+
# Client#initialize.
|
53
|
+
def initialize(opts)
|
54
|
+
@tracer = tracer_provider(opts).tracer(OTEL_TRACER_NAME, Elastic::Transport::VERSION)
|
55
|
+
@body_strategy = ENV[ENV_VARIABLE_DEPRECATED_BODY_STRATEGY] ||
|
56
|
+
ENV[ENV_VARIABLE_BODY_STRATEGY] ||
|
57
|
+
DEFAULT_BODY_STRATEGY
|
58
|
+
@sanitize_keys = ENV[ENV_VARIABLE_BODY_SANITIZE_KEYS]&.split(',')&.collect! do |pattern|
|
59
|
+
Regexp.new(pattern.gsub('*', '.*'))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
attr_accessor :tracer
|
63
|
+
|
64
|
+
def tracer_provider(opts)
|
65
|
+
opts[:opentelemetry_tracer_provider] || ::OpenTelemetry.tracer_provider
|
66
|
+
end
|
67
|
+
|
68
|
+
# Process the request body. Applies the body strategy, which can be one of the following:
|
69
|
+
# 'omit' (DEFAULT_BODY_STRATEGY): return nil
|
70
|
+
# 'sanitize': redact values at the default list of keys + any additional keys provided in
|
71
|
+
# the OTEL_RUBY_INSTRUMENTATION_ELASTICSEARCH_SEARCH_QUERY_SANITIZE_KEYS env variable.
|
72
|
+
# 'raw': return the original body, unchanged
|
73
|
+
def process_body(body, endpoint)
|
74
|
+
return if @body_strategy == DEFAULT_BODY_STRATEGY || !search_endpoint?(endpoint)
|
75
|
+
|
76
|
+
if @body_strategy == 'sanitize'
|
77
|
+
Sanitizer.sanitize(body, @sanitize_keys).to_json
|
78
|
+
elsif @body_strategy == 'raw'
|
79
|
+
body.is_a?(String) ? body : body.to_json
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def search_endpoint?(endpoint)
|
84
|
+
SEARCH_ENDPOINTS.include?(endpoint)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Replaces values in a hash with 'REDACTED', given a set of keys to match on.
|
88
|
+
class Sanitizer
|
89
|
+
class << self
|
90
|
+
FILTERED = 'REDACTED'.freeze
|
91
|
+
DEFAULT_KEY_PATTERNS =
|
92
|
+
%w[password passwd pwd secret *key *token* *session* *credit* *card* *auth* set-cookie].map! do |p|
|
93
|
+
Regexp.new(p.gsub('*', '.*'))
|
94
|
+
end
|
95
|
+
|
96
|
+
def sanitize(body, key_patterns = [])
|
97
|
+
patterns = DEFAULT_KEY_PATTERNS
|
98
|
+
patterns += key_patterns if key_patterns
|
99
|
+
sanitize!(DeepDup.dup(body), patterns)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def sanitize!(obj, key_patterns)
|
105
|
+
return obj unless obj.is_a?(Hash)
|
106
|
+
|
107
|
+
obj.each_pair do |k, v|
|
108
|
+
if filter_key?(key_patterns, k)
|
109
|
+
obj[k] = FILTERED
|
110
|
+
elsif v.is_a?(Hash)
|
111
|
+
sanitize!(v, key_patterns)
|
112
|
+
else
|
113
|
+
next
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def filter_key?(key_patterns, key)
|
119
|
+
key_patterns.any? { |regex| regex.match(key) }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Makes a deep copy of an Array or Hash
|
125
|
+
# NB: Not guaranteed to work well with complex objects, only simple Hash,
|
126
|
+
# Array, String, Number, etc.
|
127
|
+
class DeepDup
|
128
|
+
def initialize(obj)
|
129
|
+
@obj = obj
|
130
|
+
end
|
131
|
+
|
132
|
+
def dup
|
133
|
+
deep_dup(@obj)
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.dup(obj)
|
137
|
+
new(obj).dup
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def deep_dup(obj)
|
143
|
+
case obj
|
144
|
+
when Hash then hash(obj)
|
145
|
+
when Array then array(obj)
|
146
|
+
else obj.dup
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def array(arr)
|
151
|
+
arr.map { |obj| deep_dup(obj) }
|
152
|
+
end
|
153
|
+
|
154
|
+
def hash(hsh)
|
155
|
+
result = hsh.dup
|
156
|
+
|
157
|
+
hsh.each_pair do |key, value|
|
158
|
+
result[key] = deep_dup(value)
|
159
|
+
end
|
160
|
+
|
161
|
+
result
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|