excon 0.85.0 → 0.99.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
@@ -1,4 +1,5 @@
1
- require File.join(File.dirname(__FILE__), 'lib', 'excon', 'version')
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
2
+ require 'excon/version'
2
3
 
3
4
  Gem::Specification.new do |s|
4
5
  s.name = 'excon'
@@ -25,7 +26,6 @@ Gem::Specification.new do |s|
25
26
  s.add_development_dependency('eventmachine', '>= 1.0.4')
26
27
  s.add_development_dependency('open4')
27
28
  s.add_development_dependency('rake')
28
- s.add_development_dependency('rdoc')
29
29
  s.add_development_dependency('shindo')
30
30
  s.add_development_dependency('sinatra')
31
31
  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]
@@ -190,6 +193,11 @@ module Excon
190
193
  case error
191
194
  when Excon::Errors::InvalidHeaderKey, Excon::Errors::InvalidHeaderValue, Excon::Errors::StubNotFound, Excon::Errors::Timeout
192
195
  raise(error)
196
+ when Errno::EPIPE
197
+ # Read whatever remains in the pipe to aid in debugging
198
+ response = socket.read
199
+ error = Excon::Error.new(response + error.message)
200
+ raise_socket_error(error)
193
201
  else
194
202
  raise_socket_error(error)
195
203
  end
@@ -248,6 +256,11 @@ module Excon
248
256
  datum[:headers] = { 'Host' => host }.merge(datum[:headers])
249
257
  end
250
258
 
259
+ # default to GET if no method specified
260
+ unless datum[:method]
261
+ datum[:method] = :get
262
+ end
263
+
251
264
  # if path is empty or doesn't start with '/', insert one
252
265
  unless datum[:path][0, 1] == '/'
253
266
  datum[:path] = datum[:path].dup.insert(0, '/')
@@ -468,6 +481,11 @@ module Excon
468
481
  @_excon_sockets ||= {}
469
482
  @_excon_sockets.compare_by_identity
470
483
 
484
+ if @pid != Process.pid
485
+ @_excon_sockets.clear # GC will take care of closing sockets
486
+ @pid = Process.pid
487
+ end
488
+
471
489
  if @data[:thread_safe_sockets]
472
490
  # In a multi-threaded world, if the same connection is used by multiple
473
491
  # threads at the same time to connect to the same destination, they may
@@ -567,6 +585,8 @@ module Excon
567
585
  raise ArgumentError, "The `:ssl_proxy_headers` parameter should only be used with HTTPS requests."
568
586
  end
569
587
  if @data[:proxy][:scheme] == UNIX
588
+ # URI.parse might return empty string for security reasons.
589
+ @data[:proxy][:host] = nil if @data[:proxy][:host] == ""
570
590
  if @data[:proxy][:host]
571
591
  raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
572
592
  "When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
@@ -72,6 +72,8 @@ module Excon
72
72
  :client_key_pass,
73
73
  :client_cert,
74
74
  :client_cert_data,
75
+ :client_chain,
76
+ :client_chain_data,
75
77
  :certificate,
76
78
  :certificate_path,
77
79
  :disable_proxy,
@@ -28,7 +28,7 @@ module Excon
28
28
  info << "?"
29
29
 
30
30
  if params[:query].is_a?(Hash)
31
- info << params.to_a.map{ |key,value| "#{key}=#{value}" }.join('&')
31
+ info << params[:query].to_a.map { |key,value| "#{key}=#{value}" }.join('&')
32
32
  else
33
33
  info << params[:query]
34
34
  end
@@ -2,6 +2,10 @@
2
2
  module Excon
3
3
  module Middleware
4
4
  class Decompress < Excon::Middleware::Base
5
+
6
+ INFLATE_ZLIB_OR_GZIP = 47 # Zlib::MAX_WBITS + 32
7
+ INFLATE_RAW = -15 # Zlib::MAX_WBITS * -1
8
+
5
9
  def request_call(datum)
6
10
  unless datum.has_key?(:response_block)
7
11
  key = datum[:headers].keys.detect {|k| k.to_s.casecmp('Accept-Encoding') == 0 } || 'Accept-Encoding'
@@ -19,8 +23,11 @@ module Excon
19
23
  encodings = Utils.split_header_value(datum[:response][:headers][key])
20
24
  if (encoding = encodings.last)
21
25
  if encoding.casecmp('deflate') == 0
22
- # assume inflate omits header
23
- datum[:response][:body] = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(body)
26
+ datum[:response][:body] = begin
27
+ Zlib::Inflate.new(INFLATE_ZLIB_OR_GZIP).inflate(body)
28
+ rescue Zlib::DataError # fallback to raw on error
29
+ Zlib::Inflate.new(INFLATE_RAW).inflate(body)
30
+ end
24
31
  encodings.pop
25
32
  elsif encoding.casecmp('gzip') == 0 || encoding.casecmp('x-gzip') == 0
26
33
  datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(body)).read
@@ -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
@@ -40,12 +40,14 @@ module Excon
40
40
 
41
41
  def_delegators(:@socket, :close)
42
42
 
43
+
43
44
  def initialize(data = {})
44
45
  @data = data
45
46
  @nonblock = data[:nonblock]
46
47
  @port ||= @data[:port] || 80
47
48
  @read_buffer = String.new
48
49
  @eof = false
50
+ @backend_eof = false
49
51
  connect
50
52
  end
51
53
 
@@ -60,19 +62,31 @@ module Excon
60
62
  end
61
63
 
62
64
  def readline
63
- return legacy_readline if RUBY_VERSION.to_f <= 1.8_7
64
- buffer = String.new
65
- buffer << (read_nonblock(1) || raise(EOFError)) while buffer[-1] != "\n"
66
- buffer
67
- end
68
-
69
- def legacy_readline
70
- begin
71
- Timeout.timeout(@data[:read_timeout]) do
72
- @socket.readline
65
+ if @nonblock && RUBY_VERSION.to_f > 1.8_7
66
+ result = String.new
67
+ block = @read_buffer
68
+ @read_buffer = String.new
69
+
70
+ loop do
71
+ idx = block.index("\n")
72
+ if idx.nil?
73
+ result << block
74
+ else
75
+ result << block.slice!(0, idx+1)
76
+ add_to_read_buffer(block)
77
+ break
78
+ end
79
+ block = read_nonblock(@data[:chunk_size]) || raise(EOFError)
80
+ end
81
+ result
82
+ else # nonblock/legacy
83
+ begin
84
+ Timeout.timeout(@data[:read_timeout]) do
85
+ @socket.readline
86
+ end
87
+ rescue Timeout::Error
88
+ raise Excon::Errors::Timeout.new('read timeout reached')
73
89
  end
74
- rescue Timeout::Error
75
- raise Excon::Errors::Timeout.new('read timeout reached')
76
90
  end
77
91
  end
78
92
 
@@ -173,20 +187,27 @@ module Excon
173
187
  end
174
188
  end
175
189
 
190
+ def add_to_read_buffer(str)
191
+ @read_buffer << str
192
+ @eof = false
193
+ end
194
+
176
195
  def read_nonblock(max_length)
177
196
  begin
178
197
  if max_length
179
- until @read_buffer.length >= max_length
198
+ until @backend_eof || @read_buffer.length >= max_length
180
199
  @read_buffer << @socket.read_nonblock(max_length - @read_buffer.length)
181
200
  end
182
201
  else
183
- loop do
202
+ while !@backend_eof
184
203
  @read_buffer << @socket.read_nonblock(@data[:chunk_size])
185
204
  end
186
205
  end
187
206
  rescue OpenSSL::SSL::SSLError => error
188
207
  if error.message == 'read would block'
189
- select_with_timeout(@socket, :read) && retry
208
+ if @read_buffer.empty?
209
+ select_with_timeout(@socket, :read) && retry
210
+ end
190
211
  else
191
212
  raise(error)
192
213
  end
@@ -196,10 +217,10 @@ module Excon
196
217
  select_with_timeout(@socket, :read) && retry
197
218
  end
198
219
  rescue EOFError
199
- @eof = true
220
+ @backend_eof = true
200
221
  end
201
222
 
202
- if max_length
223
+ ret = if max_length
203
224
  if @read_buffer.empty?
204
225
  nil # EOF met at beginning
205
226
  else
@@ -209,6 +230,8 @@ module Excon
209
230
  # read until EOFError, so return everything
210
231
  @read_buffer.slice!(0, @read_buffer.length)
211
232
  end
233
+ @eof = @backend_eof && @read_buffer.empty?
234
+ ret
212
235
  end
213
236
 
214
237
  def read_block(max_length)
@@ -220,9 +243,7 @@ module Excon
220
243
  raise(error)
221
244
  end
222
245
  rescue *READ_RETRY_EXCEPTION_CLASSES
223
- if @read_buffer.empty?
224
- select_with_timeout(@socket, :read) && retry
225
- end
246
+ select_with_timeout(@socket, :read) && retry
226
247
  rescue EOFError
227
248
  @eof = true
228
249
  end
@@ -54,11 +54,13 @@ module Excon
54
54
  ssl_context.cert_store = cert_store
55
55
  end
56
56
 
57
- # no defaults, fallback to bundled
58
- unless ca_file || ca_path || cert_store
57
+ if cert_store.nil?
59
58
  ssl_context.cert_store = OpenSSL::X509::Store.new
60
59
  ssl_context.cert_store.set_default_paths
60
+ end
61
61
 
62
+ # no defaults, fallback to bundled
63
+ unless ca_file || ca_path || cert_store
62
64
  # workaround issue #257 (JRUBY-6970)
63
65
  ca_file = DEFAULT_CA_FILE
64
66
  ca_file = ca_file.gsub(/^jar:/, '') if ca_file =~ /^jar:file:\//
@@ -88,6 +90,14 @@ module Excon
88
90
  else
89
91
  ssl_context.key = OpenSSL::PKey::RSA.new(client_key_data, client_key_pass)
90
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
91
101
  elsif @data.key?(:certificate) && @data.key?(:private_key)
92
102
  ssl_context.cert = OpenSSL::X509::Certificate.new(@data[:certificate])
93
103
  if OpenSSL::PKey.respond_to? :read
@@ -131,7 +141,7 @@ module Excon
131
141
 
132
142
  # Server Name Indication (SNI) RFC 3546
133
143
  if @socket.respond_to?(:hostname=)
134
- @socket.hostname = @data[:host]
144
+ @socket.hostname = @data[:ssl_verify_peer_host] || @data[:host]
135
145
  end
136
146
 
137
147
  begin
@@ -169,6 +179,14 @@ module Excon
169
179
  end
170
180
  end
171
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
+
172
190
  def connect
173
191
  # backwards compatability for things lacking nonblock
174
192
  @nonblock = HAVE_NONBLOCK && @nonblock
@@ -4,10 +4,13 @@ module Excon
4
4
  module Server
5
5
  module Exec
6
6
  def start(app_str = app)
7
- line = ''
8
7
  open_process(app_str)
8
+ process_stderr = ""
9
+ line = ''
9
10
  until line =~ /\Aready\Z/
10
11
  line = error.gets
12
+ raise process_stderr if line.nil?
13
+ process_stderr << line
11
14
  fatal_time = elapsed_time > timeout
12
15
  if fatal_time
13
16
  msg = "executable #{app_str} has taken too long to start"
@@ -4,10 +4,13 @@ module Excon
4
4
  module Server
5
5
  module Puma
6
6
  def start(app_str = app, bind_uri = bind)
7
- open_process('puma', '-b', bind_uri.to_s, app_str)
7
+ open_process(RbConfig.ruby, '-S', 'puma', '-b', bind_uri.to_s, app_str)
8
+ process_stderr = ""
8
9
  line = ''
9
10
  until line =~ /Use Ctrl-C to stop/
10
11
  line = read.gets
12
+ raise process_stderr if line.nil?
13
+ process_stderr << line
11
14
  fatal_time = elapsed_time > timeout
12
15
  raise 'puma server has taken too long to start' if fatal_time
13
16
  end
@@ -13,6 +13,8 @@ module Excon
13
13
  bind_str = "#{host}:#{bind_uri.port}"
14
14
  end
15
15
  args = [
16
+ RbConfig.ruby,
17
+ '-S',
16
18
  'unicorn',
17
19
  '--no-default-middleware',
18
20
  '-l',
@@ -20,9 +22,12 @@ module Excon
20
22
  app_str
21
23
  ]
22
24
  open_process(*args)
25
+ process_stderr = ''
23
26
  line = ''
24
27
  until line =~ /worker\=0 ready/
25
28
  line = error.gets
29
+ raise process_stderr if line.nil?
30
+ process_stderr << line
26
31
  fatal_time = elapsed_time > timeout
27
32
  raise 'unicorn server has taken too long to start' if fatal_time
28
33
  end
@@ -7,10 +7,13 @@ module Excon
7
7
  bind_uri = URI.parse(bind_uri) unless bind_uri.is_a? URI::Generic
8
8
  host = bind_uri.host.gsub(/[\[\]]/, '')
9
9
  port = bind_uri.port.to_s
10
- open_process('rackup', '-s', 'webrick', '--host', host, '--port', port, app_str)
10
+ open_process(RbConfig.ruby, '-S', 'rackup', '-s', 'webrick', '--host', host, '--port', port, app_str)
11
+ process_stderr = ""
11
12
  line = ''
12
13
  until line =~ /HTTPServer#start/
13
14
  line = error.gets
15
+ raise process_stderr if line.nil?
16
+ process_stderr << line
14
17
  fatal_time = elapsed_time > timeout
15
18
  raise 'webrick server has taken too long to start' if fatal_time
16
19
  end
data/lib/excon/utils.rb CHANGED
@@ -131,7 +131,8 @@ module Excon
131
131
  end
132
132
  [values].flatten.each do |value|
133
133
  if value.to_s.match(/[\r\n]/)
134
- raise Excon::Errors::InvalidHeaderValue.new(value.to_s.inspect + ' contains forbidden "\r" or "\n"')
134
+ # Don't include the potentially sensitive header value (i.e. authorization token) in the message
135
+ raise Excon::Errors::InvalidHeaderValue.new(key.to_s + ' header value contains forbidden "\r" or "\n"')
135
136
  end
136
137
  headers_str << key.to_s << ': ' << value.to_s << CR_NL
137
138
  end
data/lib/excon/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Excon
3
- VERSION = '0.85.0'
3
+ VERSION = '0.99.0'
4
4
  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
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: excon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.85.0
4
+ version: 0.99.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dpiddy (Dan Peterson)
8
8
  - geemus (Wesley Beary)
9
9
  - nextmat (Matt Sanders)
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2021-07-16 00:00:00.000000000 Z
13
+ date: 2023-02-03 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
@@ -249,7 +235,7 @@ metadata:
249
235
  documentation_uri: https://github.com/excon/excon/blob/master/README.md
250
236
  source_code_uri: https://github.com/excon/excon
251
237
  wiki_uri: https://github.com/excon/excon/wiki
252
- post_install_message:
238
+ post_install_message:
253
239
  rdoc_options:
254
240
  - "--charset=UTF-8"
255
241
  require_paths:
@@ -265,8 +251,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
265
251
  - !ruby/object:Gem::Version
266
252
  version: '0'
267
253
  requirements: []
268
- rubygems_version: 3.2.15
269
- signing_key:
254
+ rubygems_version: 3.4.1
255
+ signing_key:
270
256
  specification_version: 4
271
257
  summary: speed, persistence, http(s)
272
258
  test_files: []