excon 0.85.0 → 0.99.0

Sign up to get free protection for your applications and to get access to all the features.
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: []