excon 0.92.4 → 0.110.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.
data/excon.gemspec CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  "README.md",
20
20
  "excon.gemspec"
21
21
  ]
22
+ s.required_ruby_version = '>= 2.7.0'
22
23
 
23
24
  s.add_development_dependency('rspec', '>= 3.5.0')
24
25
  s.add_development_dependency('activesupport')
@@ -26,7 +27,6 @@ Gem::Specification.new do |s|
26
27
  s.add_development_dependency('eventmachine', '>= 1.0.4')
27
28
  s.add_development_dependency('open4')
28
29
  s.add_development_dependency('rake')
29
- s.add_development_dependency('rdoc')
30
30
  s.add_development_dependency('shindo')
31
31
  s.add_development_dependency('sinatra')
32
32
  s.add_development_dependency('sinatra-contrib')
@@ -62,6 +62,7 @@ module Excon
62
62
  # @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
63
63
  # @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon'
64
64
  def initialize(params = {})
65
+ @pid = Process.pid
65
66
  @data = Excon.defaults.dup
66
67
  # merge does not deep-dup, so make sure headers is not the original
67
68
  @data[:headers] = @data[:headers].dup
@@ -89,7 +90,9 @@ module Excon
89
90
  end
90
91
 
91
92
  if @data[:scheme] == UNIX
92
- if @data[:host]
93
+ # 'uri' >= v0.12.0 returns an empty string instead of nil for no host.
94
+ # So treat the parameter as present if and only if it is both non-nill and non-empty.
95
+ if @data[:host] && !@data[:host].empty?
93
96
  raise ArgumentError, "The `:host` parameter should not be set for `unix://` connections.\n" +
94
97
  "When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
95
98
  elsif !@data[:socket]
@@ -227,6 +230,13 @@ module Excon
227
230
  def request(params={}, &block)
228
231
  # @data has defaults, merge in new params to override
229
232
  datum = @data.merge(params)
233
+
234
+ # Set the deadline for the current request in order to determine when we have run out of time.
235
+ # Only set when a request timeout has been defined.
236
+ if datum[:timeout]
237
+ datum[:deadline] = Process.clock_gettime(Process::CLOCK_MONOTONIC) + datum[:timeout]
238
+ end
239
+
230
240
  datum[:headers] = @data[:headers].merge(datum[:headers] || {})
231
241
 
232
242
  validate_params(:request, params, datum[:middlewares])
@@ -241,16 +251,17 @@ module Excon
241
251
  datum[:headers]['Authorization'] ||= 'Basic ' + ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
242
252
  end
243
253
 
254
+ host_key = datum[:headers].keys.detect {|k| k.casecmp('Host') == 0 } || 'Host'
244
255
  if datum[:scheme] == UNIX
245
- datum[:headers]['Host'] ||= ''
256
+ datum[:headers][host_key] ||= ''
246
257
  else
247
- datum[:headers]['Host'] ||= datum[:host] + port_string(datum)
258
+ datum[:headers][host_key] ||= datum[:host] + port_string(datum)
248
259
  end
249
260
 
250
261
  # RFC 7230, section 5.4, states that the Host header SHOULD be the first one # to be present.
251
262
  # Some web servers will reject the request if it comes too late, so let's hoist it to the top.
252
- if (host = datum[:headers].delete('Host'))
253
- datum[:headers] = { 'Host' => host }.merge(datum[:headers])
263
+ if (host = datum[:headers].delete(host_key))
264
+ datum[:headers] = { host_key => host }.merge(datum[:headers])
254
265
  end
255
266
 
256
267
  # default to GET if no method specified
@@ -478,6 +489,11 @@ module Excon
478
489
  @_excon_sockets ||= {}
479
490
  @_excon_sockets.compare_by_identity
480
491
 
492
+ if @pid != Process.pid
493
+ @_excon_sockets.clear # GC will take care of closing sockets
494
+ @pid = Process.pid
495
+ end
496
+
481
497
  if @data[:thread_safe_sockets]
482
498
  # In a multi-threaded world, if the same connection is used by multiple
483
499
  # threads at the same time to connect to the same destination, they may
@@ -577,6 +593,8 @@ module Excon
577
593
  raise ArgumentError, "The `:ssl_proxy_headers` parameter should only be used with HTTPS requests."
578
594
  end
579
595
  if @data[:proxy][:scheme] == UNIX
596
+ # URI.parse might return empty string for security reasons.
597
+ @data[:proxy][:host] = nil if @data[:proxy][:host] == ""
580
598
  if @data[:proxy][:host]
581
599
  raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
582
600
  "When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
@@ -46,6 +46,7 @@ module Excon
46
46
  :chunk_size,
47
47
  :debug_request,
48
48
  :debug_response,
49
+ :dns_timeouts,
49
50
  :headers,
50
51
  :instrumentor, # Used for setting logging within Connection
51
52
  :logger,
@@ -58,8 +59,10 @@ module Excon
58
59
  :query,
59
60
  :read_timeout,
60
61
  :request_block,
62
+ :resolv_resolver,
61
63
  :response_block,
62
64
  :stubs,
65
+ :timeout,
63
66
  :user,
64
67
  :versions,
65
68
  :write_timeout
@@ -72,6 +75,8 @@ module Excon
72
75
  :client_key_pass,
73
76
  :client_cert,
74
77
  :client_cert_data,
78
+ :client_chain,
79
+ :client_chain_data,
75
80
  :certificate,
76
81
  :certificate_path,
77
82
  :disable_proxy,
@@ -132,41 +137,45 @@ module Excon
132
137
  end
133
138
  # these come last as they rely on the above
134
139
  DEFAULTS = {
135
- :chunk_size => CHUNK_SIZE || DEFAULT_CHUNK_SIZE,
140
+ :chunk_size => CHUNK_SIZE || DEFAULT_CHUNK_SIZE,
136
141
  # see https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
137
142
  # list provided then had DES related things sorted to the end
138
- :ciphers => 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:DES-CBC3-SHA:!DSS',
139
- :connect_timeout => 60,
140
- :debug_request => false,
141
- :debug_response => false,
142
- :headers => {
143
+ :ciphers => 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:DES-CBC3-SHA:!DSS',
144
+ :connect_timeout => 60,
145
+ :debug_request => false,
146
+ :debug_response => false,
147
+ :dns_timeouts => nil,
148
+ # nil allows Resolv::DNS to set its default timeouts value (see https://ruby-doc.org/3.2.2/stdlibs/resolv/Resolv/DNS.html#method-i-timeouts-3D)
149
+ :headers => {
143
150
  'User-Agent' => USER_AGENT,
144
151
  'Accept' => '*/*'
145
152
  },
146
- :idempotent => false,
147
- :instrumentor_name => 'excon',
148
- :middlewares => [
153
+ :idempotent => false,
154
+ :instrumentor_name => 'excon',
155
+ :middlewares => [
149
156
  Excon::Middleware::ResponseParser,
150
157
  Excon::Middleware::Expects,
151
158
  Excon::Middleware::Idempotent,
152
159
  Excon::Middleware::Instrumentor,
153
160
  Excon::Middleware::Mock
154
161
  ],
155
- :mock => false,
156
- :nonblock => true,
157
- :omit_default_port => false,
158
- :persistent => false,
159
- :read_timeout => 60,
160
- :retry_errors => DEFAULT_RETRY_ERRORS,
161
- :retry_limit => DEFAULT_RETRY_LIMIT,
162
- :ssl_verify_peer => true,
163
- :ssl_uri_schemes => [HTTPS],
164
- :stubs => :global,
165
- :tcp_nodelay => false,
166
- :thread_safe_sockets => true,
167
- :uri_parser => URI,
168
- :versions => VERSIONS,
169
- :write_timeout => 60
162
+ :mock => false,
163
+ :nonblock => true,
164
+ :omit_default_port => false,
165
+ :persistent => false,
166
+ :read_timeout => 60,
167
+ :resolv_resolver => nil,
168
+ :retry_errors => DEFAULT_RETRY_ERRORS,
169
+ :retry_limit => DEFAULT_RETRY_LIMIT,
170
+ :ssl_verify_peer => true,
171
+ :ssl_uri_schemes => [HTTPS],
172
+ :stubs => :global,
173
+ :tcp_nodelay => false,
174
+ :thread_safe_sockets => true,
175
+ :timeout => nil,
176
+ :uri_parser => URI,
177
+ :versions => VERSIONS,
178
+ :write_timeout => 60
170
179
  }
171
180
 
172
181
  end
@@ -20,15 +20,24 @@ module Excon
20
20
  def host
21
21
  @data[:host]
22
22
  end
23
+ def scheme
24
+ @data[:scheme]
25
+ end
23
26
  def local_address
24
27
  @data[:local_address]
25
28
  end
26
29
  def local_port
27
30
  @data[:local_port]
28
31
  end
32
+ def http_method # can't be named "method"
33
+ @data[:method]
34
+ end
29
35
  def path
30
36
  @data[:path]
31
37
  end
38
+ def query
39
+ @data[:query]
40
+ end
32
41
  def port
33
42
  @data[:port]
34
43
  end
@@ -72,9 +81,13 @@ module Excon
72
81
  :body => String.new,
73
82
  :cookies => [],
74
83
  :host => datum[:host],
84
+ :scheme => datum[:scheme],
85
+ :method => datum[:method],
75
86
  :headers => Excon::Headers.new,
76
87
  :path => datum[:path],
88
+ :query => datum[:query],
77
89
  :port => datum[:port],
90
+ :omit_default_port => datum[:omit_default_port],
78
91
  :status => status,
79
92
  :status_line => line,
80
93
  :reason_phrase => reason_phrase
data/lib/excon/socket.rb CHANGED
@@ -25,6 +25,13 @@ module Excon
25
25
  else # Ruby <= 2.0
26
26
  [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable]
27
27
  end
28
+ # Maps a socket operation to a timeout property.
29
+ OPERATION_TO_TIMEOUT = {
30
+ :connect_read => :connect_timeout,
31
+ :connect_write => :connect_timeout,
32
+ :read => :read_timeout,
33
+ :write => :write_timeout
34
+ }.freeze
28
35
 
29
36
  def params
30
37
  Excon.display_warning('Excon::Socket#params is deprecated use Excon::Socket#data instead.')
@@ -40,18 +47,22 @@ module Excon
40
47
 
41
48
  def_delegators(:@socket, :close)
42
49
 
50
+
43
51
  def initialize(data = {})
44
52
  @data = data
45
53
  @nonblock = data[:nonblock]
46
54
  @port ||= @data[:port] || 80
47
55
  @read_buffer = String.new
56
+ @read_offset = 0
48
57
  @eof = false
58
+ @backend_eof = false
59
+
49
60
  connect
50
61
  end
51
62
 
52
63
  def read(max_length = nil)
53
64
  if @eof
54
- return max_length ? nil : ''
65
+ max_length ? nil : ''
55
66
  elsif @nonblock
56
67
  read_nonblock(max_length)
57
68
  else
@@ -60,10 +71,25 @@ module Excon
60
71
  end
61
72
 
62
73
  def readline
63
- if @nonblock && RUBY_VERSION.to_f > 1.8_7
64
- buffer = String.new
65
- buffer << (read_nonblock(1) || raise(EOFError)) while buffer[-1] != "\n"
66
- buffer
74
+ if @nonblock
75
+ result = String.new
76
+ block = consume_read_buffer
77
+
78
+ loop do
79
+ idx = block.index("\n")
80
+
81
+ if idx.nil?
82
+ result << block
83
+ else
84
+ result << block[0..idx]
85
+ rewind_read_buffer(block, idx)
86
+ break
87
+ end
88
+
89
+ block = read_nonblock(@data[:chunk_size]) || raise(EOFError)
90
+ end
91
+
92
+ result
67
93
  else # nonblock/legacy
68
94
  begin
69
95
  Timeout.timeout(@data[:read_timeout]) do
@@ -106,7 +132,17 @@ module Excon
106
132
  family = @data[:proxy][:family]
107
133
  end
108
134
 
109
- Resolv.each_address(hostname) do |ip|
135
+ resolver = @data[:resolv_resolver] || Resolv::DefaultResolver
136
+
137
+ # Deprecated
138
+ if @data[:dns_timeouts]
139
+ Excon.display_warning('dns_timeouts is deprecated, use resolv_resolver instead.')
140
+ dns_resolver = Resolv::DNS.new
141
+ dns_resolver.timeouts = @data[:dns_timeouts]
142
+ resolver = Resolv.new([Resolv::Hosts.new, dns_resolver])
143
+ end
144
+
145
+ resolver.each_address(hostname) do |ip|
110
146
  # already succeeded on previous addrinfo
111
147
  if @socket
112
148
  break
@@ -172,20 +208,55 @@ module Excon
172
208
  end
173
209
  end
174
210
 
211
+ # Consume any bytes remaining in the read buffer before making a system call.
212
+ def consume_read_buffer
213
+ block = @read_buffer[@read_offset..-1]
214
+
215
+ @read_offset = @read_buffer.length
216
+
217
+ block
218
+ end
219
+
220
+ # Rewind the read buffer to just after the given index.
221
+ # The offset is moved back to the start of the current chunk and then forward until just after the index.
222
+ def rewind_read_buffer(chunk, idx)
223
+ @read_offset = @read_offset - chunk.length + (idx + 1)
224
+ @eof = false
225
+ end
226
+
175
227
  def read_nonblock(max_length)
176
228
  begin
229
+ if @read_offset != 0 && @read_offset >= @read_buffer.length
230
+ # Clear the buffer so we can test for emptiness below
231
+ @read_buffer.clear
232
+ # Reset the offset so it matches the length of the buffer when empty.
233
+ @read_offset = 0
234
+ end
235
+
177
236
  if max_length
178
- until @read_buffer.length >= max_length
179
- @read_buffer << @socket.read_nonblock(max_length - @read_buffer.length)
237
+ until @backend_eof || readable_bytes >= max_length
238
+ if @read_buffer.empty?
239
+ # Avoid allocating a new buffer string when the read buffer is empty
240
+ @read_buffer = @socket.read_nonblock(max_length, @read_buffer)
241
+ else
242
+ @read_buffer << @socket.read_nonblock(max_length - readable_bytes)
243
+ end
180
244
  end
181
245
  else
182
- loop do
183
- @read_buffer << @socket.read_nonblock(@data[:chunk_size])
246
+ until @backend_eof
247
+ if @read_buffer.empty?
248
+ # Avoid allocating a new buffer string when the read buffer is empty
249
+ @read_buffer = @socket.read_nonblock(@data[:chunk_size], @read_buffer)
250
+ else
251
+ @read_buffer << @socket.read_nonblock(@data[:chunk_size])
252
+ end
184
253
  end
185
254
  end
186
255
  rescue OpenSSL::SSL::SSLError => error
187
256
  if error.message == 'read would block'
188
- select_with_timeout(@socket, :read) && retry
257
+ if @read_buffer.empty?
258
+ select_with_timeout(@socket, :read) && retry
259
+ end
189
260
  else
190
261
  raise(error)
191
262
  end
@@ -195,21 +266,37 @@ module Excon
195
266
  select_with_timeout(@socket, :read) && retry
196
267
  end
197
268
  rescue EOFError
198
- @eof = true
269
+ @backend_eof = true
199
270
  end
200
271
 
201
272
  if max_length
202
273
  if @read_buffer.empty?
203
- nil # EOF met at beginning
274
+ # EOF met at beginning
275
+ @eof = @backend_eof
276
+ nil
204
277
  else
205
- @read_buffer.slice!(0, max_length)
278
+ start = @read_offset
279
+
280
+ # Ensure that we can seek backwards when reading until a terminator string.
281
+ # The read offset must never point past the end of the read buffer.
282
+ @read_offset += max_length > readable_bytes ? readable_bytes : max_length
283
+ @read_buffer[start...@read_offset]
206
284
  end
207
285
  else
208
286
  # read until EOFError, so return everything
209
- @read_buffer.slice!(0, @read_buffer.length)
287
+ start = @read_offset
288
+
289
+ @read_offset = @read_buffer.length
290
+ @eof = @backend_eof
291
+
292
+ @read_buffer[start..-1]
210
293
  end
211
294
  end
212
295
 
296
+ def readable_bytes
297
+ @read_buffer.length - @read_offset
298
+ end
299
+
213
300
  def read_block(max_length)
214
301
  @socket.read(max_length)
215
302
  rescue OpenSSL::SSL::SSLError => error
@@ -219,9 +306,7 @@ module Excon
219
306
  raise(error)
220
307
  end
221
308
  rescue *READ_RETRY_EXCEPTION_CLASSES
222
- if @read_buffer.empty?
223
- select_with_timeout(@socket, :read) && retry
224
- end
309
+ select_with_timeout(@socket, :read) && retry
225
310
  rescue EOFError
226
311
  @eof = true
227
312
  end
@@ -272,17 +357,33 @@ module Excon
272
357
  end
273
358
 
274
359
  def select_with_timeout(socket, type)
360
+ timeout_kind = type
361
+ timeout = @data[OPERATION_TO_TIMEOUT[type]]
362
+
363
+ # Check whether the request has a timeout configured.
364
+ if @data.include?(:deadline)
365
+ request_timeout = request_time_remaining
366
+
367
+ # If the time remaining until the request times out is less than the timeout for the type of select,
368
+ # use the time remaining as the timeout instead.
369
+ if request_timeout < timeout
370
+ timeout_kind = :request
371
+ timeout = request_timeout
372
+ end
373
+ end
374
+
275
375
  select = case type
276
376
  when :connect_read
277
- IO.select([socket], nil, nil, @data[:connect_timeout])
377
+ IO.select([socket], nil, nil, timeout)
278
378
  when :connect_write
279
- IO.select(nil, [socket], nil, @data[:connect_timeout])
379
+ IO.select(nil, [socket], nil, timeout)
280
380
  when :read
281
- IO.select([socket], nil, nil, @data[:read_timeout])
381
+ IO.select([socket], nil, nil, timeout)
282
382
  when :write
283
- IO.select(nil, [socket], nil, @data[:write_timeout])
383
+ IO.select(nil, [socket], nil, timeout)
284
384
  end
285
- select || raise(Excon::Errors::Timeout.new("#{type} timeout reached"))
385
+
386
+ select || raise(Excon::Errors::Timeout.new("#{timeout_kind} timeout reached"))
286
387
  end
287
388
 
288
389
  def unpacked_sockaddr
@@ -292,5 +393,16 @@ module Excon
292
393
  raise
293
394
  end
294
395
  end
396
+
397
+ # Returns the remaining time in seconds until we reach the deadline for the request timeout.
398
+ # Raises an exception if we have exceeded the request timeout's deadline.
399
+ def request_time_remaining
400
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
401
+ deadline = @data[:deadline]
402
+
403
+ raise(Excon::Errors::Timeout.new('request timeout reached')) if now >= deadline
404
+
405
+ deadline - now
406
+ end
295
407
  end
296
408
  end
@@ -90,6 +90,14 @@ module Excon
90
90
  else
91
91
  ssl_context.key = OpenSSL::PKey::RSA.new(client_key_data, client_key_pass)
92
92
  end
93
+ if client_chain_data && OpenSSL::X509::Certificate.respond_to?(:load)
94
+ ssl_context.extra_chain_cert = OpenSSL::X509::Certificate.load(client_chain_data)
95
+ elsif client_chain_data
96
+ certs = client_chain_data.scan(/-----BEGIN CERTIFICATE-----(?:.|\n)+?-----END CERTIFICATE-----/)
97
+ ssl_context.extra_chain_cert = certs.map do |cert|
98
+ OpenSSL::X509::Certificate.new(cert)
99
+ end
100
+ end
93
101
  elsif @data.key?(:certificate) && @data.key?(:private_key)
94
102
  ssl_context.cert = OpenSSL::X509::Certificate.new(@data[:certificate])
95
103
  if OpenSSL::PKey.respond_to? :read
@@ -133,7 +141,7 @@ module Excon
133
141
 
134
142
  # Server Name Indication (SNI) RFC 3546
135
143
  if @socket.respond_to?(:hostname=)
136
- @socket.hostname = @data[:host]
144
+ @socket.hostname = @data[:ssl_verify_peer_host] || @data[:host]
137
145
  end
138
146
 
139
147
  begin
@@ -171,6 +179,14 @@ module Excon
171
179
  end
172
180
  end
173
181
 
182
+ def client_chain_data
183
+ @client_chain_data ||= if (ccd = @data[:client_chain_data])
184
+ ccd
185
+ elsif (path = @data[:client_chain])
186
+ File.read path
187
+ end
188
+ end
189
+
174
190
  def connect
175
191
  # backwards compatability for things lacking nonblock
176
192
  @nonblock = HAVE_NONBLOCK && @nonblock
@@ -42,7 +42,6 @@ module Excon
42
42
  if RUBY_PLATFORM == 'java'
43
43
  @pid, @write, @read, @error = IO.popen4(*args)
44
44
  else
45
- GC.disable if RUBY_VERSION < '1.9'
46
45
  @pid, @write, @read, @error = Open4.popen4(*args)
47
46
  end
48
47
  @started_at = Time.now
@@ -57,7 +56,6 @@ module Excon
57
56
  Process.kill('USR1', pid)
58
57
  else
59
58
  Process.kill(9, pid)
60
- GC.enable if RUBY_VERSION < '1.9'
61
59
  Process.wait(pid)
62
60
  end
63
61
 
data/lib/excon/version.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Excon
3
- VERSION = '0.92.4'
4
+ VERSION = '0.110.0'
4
5
  end
data/lib/excon.rb CHANGED
@@ -198,8 +198,13 @@ module Excon
198
198
  headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
199
199
  case value = stub[:headers][key]
200
200
  when Regexp
201
- if (match = value.match(request_params[:headers][key]))
202
- captures[:headers][key] = match.captures
201
+ case request_params[:headers][key]
202
+ when String
203
+ if (match = value.match(request_params[:headers][key]))
204
+ captures[:headers][key] = match.captures
205
+ end
206
+ when Regexp # for unstub on regex params
207
+ match = (value == request_params[:headers][key])
203
208
  end
204
209
  match
205
210
  else
@@ -209,8 +214,13 @@ module Excon
209
214
  non_headers_match = (stub.keys - [:headers]).all? do |key|
210
215
  case value = stub[key]
211
216
  when Regexp
212
- if (match = value.match(request_params[key]))
213
- captures[key] = match.captures
217
+ case request_params[key]
218
+ when String
219
+ if (match = value.match(request_params[key]))
220
+ captures[key] = match.captures
221
+ end
222
+ when Regexp # for unstub on regex params
223
+ match = (value == request_params[key])
214
224
  end
215
225
  match
216
226
  else
@@ -235,12 +245,13 @@ module Excon
235
245
  end
236
246
  end
237
247
 
238
- # remove first/oldest stub matching request_params
248
+ # remove first/oldest stub matching request_params or nil if none match
239
249
  # @param request_params [Hash<Symbol, >] request params to match against, omitted params match all
240
250
  # @return [Hash<Symbol, >] response params from deleted stub
241
251
  def unstub(request_params = {})
242
- stub = stub_for(request_params)
243
- Excon.stubs.delete_at(Excon.stubs.index(stub))
252
+ if (stub = stub_for(request_params))
253
+ Excon.stubs.delete_at(Excon.stubs.index(stub))
254
+ end
244
255
  end
245
256
 
246
257
  # Generic non-persistent HTTP methods
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: excon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.92.4
4
+ version: 0.110.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dpiddy (Dan Peterson)
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-07-20 00:00:00.000000000 Z
13
+ date: 2024-03-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -96,20 +96,6 @@ dependencies:
96
96
  - - ">="
97
97
  - !ruby/object:Gem::Version
98
98
  version: '0'
99
- - !ruby/object:Gem::Dependency
100
- name: rdoc
101
- requirement: !ruby/object:Gem::Requirement
102
- requirements:
103
- - - ">="
104
- - !ruby/object:Gem::Version
105
- version: '0'
106
- type: :development
107
- prerelease: false
108
- version_requirements: !ruby/object:Gem::Requirement
109
- requirements:
110
- - - ">="
111
- - !ruby/object:Gem::Version
112
- version: '0'
113
99
  - !ruby/object:Gem::Dependency
114
100
  name: shindo
115
101
  requirement: !ruby/object:Gem::Requirement
@@ -258,14 +244,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
258
244
  requirements:
259
245
  - - ">="
260
246
  - !ruby/object:Gem::Version
261
- version: '0'
247
+ version: 2.7.0
262
248
  required_rubygems_version: !ruby/object:Gem::Requirement
263
249
  requirements:
264
250
  - - ">="
265
251
  - !ruby/object:Gem::Version
266
252
  version: '0'
267
253
  requirements: []
268
- rubygems_version: 3.3.5
254
+ rubygems_version: 3.4.22
269
255
  signing_key:
270
256
  specification_version: 4
271
257
  summary: speed, persistence, http(s)