excon 0.71.1 → 0.89.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'
@@ -11,7 +12,7 @@ Gem::Specification.new do |s|
11
12
  s.license = 'MIT'
12
13
  s.rdoc_options = ["--charset=UTF-8"]
13
14
  s.extra_rdoc_files = %w[README.md CONTRIBUTORS.md CONTRIBUTING.md]
14
- s.files = `git ls-files -- {data,lib}/*`.split("\n") + [
15
+ s.files = `git ls-files -- data/* lib/*`.split("\n") + [
15
16
  "CONTRIBUTING.md",
16
17
  "CONTRIBUTORS.md",
17
18
  "LICENSE.md",
@@ -31,6 +32,7 @@ Gem::Specification.new do |s|
31
32
  s.add_development_dependency('sinatra-contrib')
32
33
  s.add_development_dependency('json', '>= 1.8.5')
33
34
  s.add_development_dependency('puma')
35
+ s.add_development_dependency('webrick')
34
36
 
35
37
  s.metadata = {
36
38
  'homepage_uri' => 'https://github.com/excon/excon',
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require 'ipaddr'
3
+
2
4
  module Excon
3
5
  class Connection
4
6
  include Utils
@@ -77,7 +79,7 @@ module Excon
77
79
 
78
80
  setup_proxy
79
81
 
80
- if ENV.has_key?('EXCON_STANDARD_INSTRUMENTOR')
82
+ if ENV.has_key?('EXCON_STANDARD_INSTRUMENTOR')
81
83
  @data[:instrumentor] = Excon::StandardInstrumentor
82
84
  end
83
85
 
@@ -113,7 +115,7 @@ module Excon
113
115
  # we already have data from a middleware, so bail
114
116
  return datum
115
117
  else
116
- socket.data = datum
118
+ socket(datum).data = datum
117
119
  # start with "METHOD /path"
118
120
  request = datum[:method].to_s.upcase + ' '
119
121
  if datum[:proxy] && datum[:scheme] != HTTPS
@@ -142,35 +144,25 @@ module Excon
142
144
  end
143
145
 
144
146
  # add headers to request
145
- datum[:headers].each do |key, values|
146
- if key.to_s.match(/[\r\n]/)
147
- raise Excon::Errors::InvalidHeaderKey.new(key.to_s.inspect + ' contains forbidden "\r" or "\n"')
148
- end
149
- [values].flatten.each do |value|
150
- if value.to_s.match(/[\r\n]/)
151
- raise Excon::Errors::InvalidHeaderValue.new(value.to_s.inspect + ' contains forbidden "\r" or "\n"')
152
- end
153
- request << key.to_s << ': ' << value.to_s << CR_NL
154
- end
155
- end
147
+ request << Utils.headers_hash_to_s(datum[:headers])
156
148
 
157
149
  # add additional "\r\n" to indicate end of headers
158
150
  request << CR_NL
159
151
 
160
152
  if datum.has_key?(:request_block)
161
- socket.write(request) # write out request + headers
153
+ socket(datum).write(request) # write out request + headers
162
154
  while true # write out body with chunked encoding
163
155
  chunk = datum[:request_block].call
164
156
  chunk = binary_encode(chunk)
165
157
  if chunk.length > 0
166
- socket.write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
158
+ socket(datum).write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
167
159
  else
168
- socket.write(String.new("0#{CR_NL}#{CR_NL}"))
160
+ socket(datum).write(String.new("0#{CR_NL}#{CR_NL}"))
169
161
  break
170
162
  end
171
163
  end
172
164
  elsif body.nil?
173
- socket.write(request) # write out request + headers
165
+ socket(datum).write(request) # write out request + headers
174
166
  else # write out body
175
167
  if body.respond_to?(:binmode) && !body.is_a?(StringIO)
176
168
  body.binmode
@@ -184,13 +176,13 @@ module Excon
184
176
  chunk = body.read([datum[:chunk_size] - request.length, 0].max)
185
177
  if chunk
186
178
  chunk = binary_encode(chunk)
187
- socket.write(request << chunk)
179
+ socket(datum).write(request << chunk)
188
180
  else
189
- socket.write(request) # write out request + headers
181
+ socket(datum).write(request) # write out request + headers
190
182
  end
191
183
 
192
- while chunk = body.read(datum[:chunk_size])
193
- socket.write(chunk)
184
+ while (chunk = body.read(datum[:chunk_size]))
185
+ socket(datum).write(chunk)
194
186
  end
195
187
  end
196
188
  end
@@ -198,6 +190,11 @@ module Excon
198
190
  case error
199
191
  when Excon::Errors::InvalidHeaderKey, Excon::Errors::InvalidHeaderValue, Excon::Errors::StubNotFound, Excon::Errors::Timeout
200
192
  raise(error)
193
+ when Errno::EPIPE
194
+ # Read whatever remains in the pipe to aid in debugging
195
+ response = socket.read
196
+ error = Excon::Error.new(response + error.message)
197
+ raise_socket_error(error)
201
198
  else
202
199
  raise_socket_error(error)
203
200
  end
@@ -250,6 +247,12 @@ module Excon
250
247
  datum[:headers]['Host'] ||= datum[:host] + port_string(datum)
251
248
  end
252
249
 
250
+ # RFC 7230, section 5.4, states that the Host header SHOULD be the first one # to be present.
251
+ # 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])
254
+ end
255
+
253
256
  # if path is empty or doesn't start with '/', insert one
254
257
  unless datum[:path][0, 1] == '/'
255
258
  datum[:path] = datum[:path].dup.insert(0, '/')
@@ -280,7 +283,7 @@ module Excon
280
283
  @persistent_socket_reusable = true
281
284
 
282
285
  if datum[:persistent]
283
- if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
286
+ if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 })
284
287
  if datum[:response][:headers][key].casecmp('close') == 0
285
288
  reset
286
289
  end
@@ -320,7 +323,7 @@ module Excon
320
323
  end
321
324
 
322
325
  if @data[:persistent]
323
- if key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
326
+ if (key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 })
324
327
  if responses.last[:headers][key].casecmp('close') == 0
325
328
  reset
326
329
  end
@@ -348,7 +351,7 @@ module Excon
348
351
  end
349
352
 
350
353
  def reset
351
- if old_socket = sockets.delete(@socket_key)
354
+ if (old_socket = sockets.delete(@socket_key))
352
355
  old_socket.close rescue nil
353
356
  end
354
357
  @persistent_socket_reusable = true
@@ -455,26 +458,27 @@ module Excon
455
458
  end
456
459
  end
457
460
 
458
- def socket
459
- unix_proxy = @data[:proxy] ? @data[:proxy][:scheme] == UNIX : false
460
- sockets[@socket_key] ||= if @data[:scheme] == UNIX || unix_proxy
461
- Excon::UnixSocket.new(@data)
462
- elsif @data[:ssl_uri_schemes].include?(@data[:scheme])
463
- Excon::SSLSocket.new(@data)
461
+ def socket(datum = @data)
462
+ unix_proxy = datum[:proxy] ? datum[:proxy][:scheme] == UNIX : false
463
+ sockets[@socket_key] ||= if datum[:scheme] == UNIX || unix_proxy
464
+ Excon::UnixSocket.new(datum)
465
+ elsif datum[:ssl_uri_schemes].include?(datum[:scheme])
466
+ Excon::SSLSocket.new(datum)
464
467
  else
465
- Excon::Socket.new(@data)
468
+ Excon::Socket.new(datum)
466
469
  end
467
470
  end
468
471
 
469
472
  def sockets
470
473
  @_excon_sockets ||= {}
474
+ @_excon_sockets.compare_by_identity
471
475
 
472
476
  if @data[:thread_safe_sockets]
473
477
  # In a multi-threaded world, if the same connection is used by multiple
474
478
  # threads at the same time to connect to the same destination, they may
475
479
  # stomp on each other's sockets. This ensures every thread gets their
476
480
  # own socket cache, within the context of a single connection.
477
- @_excon_sockets[Thread.current.object_id] ||= {}
481
+ @_excon_sockets[Thread.current] ||= {}
478
482
  else
479
483
  @_excon_sockets
480
484
  end
@@ -488,6 +492,47 @@ module Excon
488
492
  end
489
493
  end
490
494
 
495
+ def proxy_match_host_port(host, port)
496
+ host_match = if host.is_a? IPAddr
497
+ begin
498
+ host.include? @data[:host]
499
+ rescue IPAddr::Error
500
+ false
501
+ end
502
+ else
503
+ /(^|\.)#{host}$/.match(@data[:host])
504
+ end
505
+ host_match && (port.nil? || port.to_i == @data[:port])
506
+ end
507
+
508
+ def proxy_from_env
509
+ if (no_proxy_env = ENV['no_proxy'] || ENV['NO_PROXY'])
510
+ no_proxy_list = no_proxy_env.scan(/\s*(?:\[([\dA-Fa-f:\/]+)\]|\*?\.?([^\s,:]+))(?::(\d+))?\s*/i).map { |e|
511
+ if e[0]
512
+ begin
513
+ [IPAddr.new(e[0]), e[2]]
514
+ rescue IPAddr::Error
515
+ nil
516
+ end
517
+ else
518
+ begin
519
+ [IPAddr.new(e[1]), e[2]]
520
+ rescue IPAddr::Error
521
+ [e[1], e[2]]
522
+ end
523
+ end
524
+ }.reject { |e| e.nil? || e[0].nil? }
525
+ end
526
+
527
+ unless no_proxy_env && no_proxy_list.index { |h| proxy_match_host_port(h[0], h[1]) }
528
+ if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
529
+ @data[:proxy] = ENV['https_proxy'] || ENV['HTTPS_PROXY']
530
+ elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
531
+ @data[:proxy] = ENV['http_proxy'] || ENV['HTTP_PROXY']
532
+ end
533
+ end
534
+ end
535
+
491
536
  def setup_proxy
492
537
  if @data[:disable_proxy]
493
538
  if @data[:proxy]
@@ -496,64 +541,57 @@ module Excon
496
541
  return
497
542
  end
498
543
 
499
- unless @data[:scheme] == UNIX
500
- if no_proxy_env = ENV["no_proxy"] || ENV["NO_PROXY"]
501
- no_proxy_list = no_proxy_env.scan(/\*?\.?([^\s,:]+)(?::(\d+))?/i).map { |s| [s[0], s[1]] }
544
+ return if @data[:scheme] == UNIX
545
+
546
+ proxy_from_env
547
+
548
+ case @data[:proxy]
549
+ when nil
550
+ @data.delete(:proxy)
551
+ when ''
552
+ @data.delete(:proxy)
553
+ when Hash
554
+ # no processing needed
555
+ when String, URI
556
+ uri = @data[:proxy].is_a?(String) ? URI.parse(@data[:proxy]) : @data[:proxy]
557
+ @data[:proxy] = {
558
+ :host => uri.host,
559
+ :hostname => uri.hostname,
560
+ # path is only sensible for a Unix socket proxy
561
+ :path => uri.scheme == UNIX ? uri.path : nil,
562
+ :port => uri.port,
563
+ :scheme => uri.scheme,
564
+ }
565
+ if uri.password
566
+ @data[:proxy][:password] = uri.password
502
567
  end
503
-
504
- unless no_proxy_env && no_proxy_list.index { |h| /(^|\.)#{h[0]}$/.match(@data[:host]) && (h[1].nil? || h[1].to_i == @data[:port]) }
505
- if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
506
- @data[:proxy] = ENV['https_proxy'] || ENV['HTTPS_PROXY']
507
- elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
508
- @data[:proxy] = ENV['http_proxy'] || ENV['HTTP_PROXY']
509
- end
568
+ if uri.user
569
+ @data[:proxy][:user] = uri.user
510
570
  end
511
-
512
- case @data[:proxy]
513
- when nil
514
- @data.delete(:proxy)
515
- when ''
516
- @data.delete(:proxy)
517
- when Hash
518
- # no processing needed
519
- when String, URI
520
- uri = @data[:proxy].is_a?(String) ? URI.parse(@data[:proxy]) : @data[:proxy]
521
- @data[:proxy] = {
522
- :host => uri.host,
523
- :hostname => uri.hostname,
524
- # path is only sensible for a Unix socket proxy
525
- :path => uri.scheme == UNIX ? uri.path : nil,
526
- :port => uri.port,
527
- :scheme => uri.scheme,
528
- }
529
- if uri.password
530
- @data[:proxy][:password] = uri.password
531
- end
532
- if uri.user
533
- @data[:proxy][:user] = uri.user
534
- end
535
- if @data[:proxy][:scheme] == UNIX
536
- if @data[:proxy][:host]
537
- raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
538
- "When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
539
- end
540
- else
541
- unless uri.host && uri.port && uri.scheme
542
- raise Excon::Errors::ProxyParse, "Proxy is invalid"
543
- end
571
+ if @data[:ssl_proxy_headers] && !@data[:ssl_uri_schemes].include?(@data[:scheme])
572
+ raise ArgumentError, "The `:ssl_proxy_headers` parameter should only be used with HTTPS requests."
573
+ end
574
+ if @data[:proxy][:scheme] == UNIX
575
+ if @data[:proxy][:host]
576
+ raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
577
+ "When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
544
578
  end
545
579
  else
546
- raise Excon::Errors::ProxyParse, "Proxy is invalid"
580
+ unless uri.host && uri.port && uri.scheme
581
+ raise Excon::Errors::ProxyParse, "Proxy is invalid"
582
+ end
547
583
  end
584
+ else
585
+ raise Excon::Errors::ProxyParse, "Proxy is invalid"
586
+ end
548
587
 
549
- if @data.has_key?(:proxy) && @data[:scheme] == 'http'
550
- @data[:headers]['Proxy-Connection'] ||= 'Keep-Alive'
551
- # https credentials happen in handshake
552
- if @data[:proxy].has_key?(:user) || @data[:proxy].has_key?(:password)
553
- user, pass = Utils.unescape_form(@data[:proxy][:user].to_s), Utils.unescape_form(@data[:proxy][:password].to_s)
554
- auth = ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
555
- @data[:headers]['Proxy-Authorization'] = 'Basic ' + auth
556
- end
588
+ if @data.has_key?(:proxy) && @data[:scheme] == 'http'
589
+ @data[:headers]['Proxy-Connection'] ||= 'Keep-Alive'
590
+ # https credentials happen in handshake
591
+ if @data[:proxy].has_key?(:user) || @data[:proxy].has_key?(:password)
592
+ user, pass = Utils.unescape_form(@data[:proxy][:user].to_s), Utils.unescape_form(@data[:proxy][:password].to_s)
593
+ auth = ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
594
+ @data[:headers]['Proxy-Authorization'] = 'Basic ' + auth
557
595
  end
558
596
  end
559
597
  end
@@ -95,9 +95,12 @@ module Excon
95
95
  :ssl_verify_callback,
96
96
  :ssl_verify_peer,
97
97
  :ssl_verify_peer_host,
98
+ :ssl_verify_hostname,
98
99
  :ssl_version,
99
100
  :ssl_min_version,
100
101
  :ssl_max_version,
102
+ :ssl_security_level,
103
+ :ssl_proxy_headers,
101
104
  :ssl_uri_schemes,
102
105
  :tcp_nodelay,
103
106
  :thread_safe_sockets,
data/lib/excon/error.rb CHANGED
@@ -50,7 +50,17 @@ or:
50
50
  class InvalidHeaderValue < Error; end
51
51
  class Timeout < Error; end
52
52
  class ResponseParse < Error; end
53
- class ProxyConnectionError < Error; end
53
+
54
+ class ProxyConnectionError < Error
55
+ attr_reader :request, :response
56
+
57
+ def initialize(msg, request = nil, response = nil)
58
+ super(msg)
59
+ @request = request
60
+ @response = response
61
+ end
62
+ end
63
+
54
64
  class ProxyParse < Error; end
55
65
  class TooManyRedirects < Error; end
56
66
 
data/lib/excon/headers.rb CHANGED
@@ -22,6 +22,7 @@ module Excon
22
22
  alias_method :raw_values_at, :values_at
23
23
 
24
24
  def initialize
25
+ super
25
26
  @downcased = {}
26
27
  end
27
28
 
@@ -29,11 +30,11 @@ module Excon
29
30
  @downcased[key.to_s.downcase]
30
31
  end
31
32
 
32
- alias_method :[]=, :store
33
33
  def []=(key, value)
34
34
  raw_writer(key, value)
35
35
  @downcased[key.to_s.downcase] = value
36
36
  end
37
+ alias_method :store, :[]=
37
38
 
38
39
  if SENTINEL.respond_to? :assoc
39
40
  def assoc(obj)
@@ -54,11 +55,11 @@ module Excon
54
55
  end
55
56
  end
56
57
 
57
- alias_method :has_key?, :key?
58
- alias_method :has_key?, :member?
59
58
  def has_key?(key)
60
59
  raw_key?(key) || @downcased.has_key?(key.to_s.downcase)
61
60
  end
61
+ alias_method :key?, :has_key?
62
+ alias_method :member?, :has_key?
62
63
 
63
64
  def merge(other_hash)
64
65
  self.dup.merge!(other_hash)
@@ -3,7 +3,7 @@ require 'logger'
3
3
  module Excon
4
4
  class LoggingInstrumentor
5
5
 
6
- def self.instrument(name, params = {}, &block)
6
+ def self.instrument(name, params = {})
7
7
  params = params.dup
8
8
 
9
9
  logger = params[:logger] || Logger.new($stderr)
@@ -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
@@ -40,7 +40,7 @@ module Excon
40
40
  end
41
41
  end
42
42
 
43
- logger.log(Logger::INFO, info) if info
43
+ logger.info(info) if info
44
44
 
45
45
  yield if block_given?
46
46
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Excon
3
3
  class StandardInstrumentor
4
- def self.instrument(name, params = {}, &block)
4
+ def self.instrument(name, params = {})
5
5
  params = params.dup
6
6
 
7
7
  # reduce duplication/noise of output
@@ -8,7 +8,7 @@ module Excon
8
8
  end
9
9
 
10
10
  def get_header(datum, header)
11
- _, header_value = datum[:response][:headers].detect do |key, value|
11
+ _, header_value = datum[:response][:headers].detect do |key, _|
12
12
  key.casecmp(header) == 0
13
13
  end
14
14
  header_value
@@ -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'
@@ -15,12 +19,15 @@ module Excon
15
19
  def response_call(datum)
16
20
  body = datum[:response][:body]
17
21
  unless datum.has_key?(:response_block) || body.nil? || body.empty?
18
- if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 }
22
+ if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 })
19
23
  encodings = Utils.split_header_value(datum[:response][:headers][key])
20
- if encoding = encodings.last
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
@@ -12,7 +12,7 @@ module Excon
12
12
  if datum.has_key?(:expects) && ![*datum[:expects]].include?(datum[:response][:status])
13
13
  raise(
14
14
  Excon::Errors.status_error(
15
- datum.reject {|key,value| key == :response},
15
+ datum.reject {|key,_| key == :response},
16
16
  Excon::Response.new(datum[:response])
17
17
  )
18
18
  )
@@ -25,7 +25,7 @@ module Excon
25
25
  raise Excon::Errors::InvalidStub.new("Request body should be a string or an IO object. #{datum[:body].class} provided")
26
26
  end
27
27
 
28
- if stub = Excon.stub_for(datum)
28
+ if (stub = Excon.stub_for(datum))
29
29
  datum[:remote_ip] ||= '127.0.0.1'
30
30
  datum[:response] = {
31
31
  :body => '',
@@ -41,7 +41,7 @@ module Excon
41
41
  stub.last
42
42
  end
43
43
 
44
- datum[:response].merge!(stub_datum.reject {|key,value| key == :headers})
44
+ datum[:response].merge!(stub_datum.reject {|key,_| key == :headers})
45
45
  if stub_datum.has_key?(:headers)
46
46
  datum[:response][:headers].merge!(stub_datum[:headers])
47
47
  end
@@ -16,7 +16,7 @@ module Excon
16
16
  end
17
17
 
18
18
  def get_header(datum, header)
19
- _, header_value = datum[:response][:headers].detect do |key, value|
19
+ _, header_value = datum[:response][:headers].detect do |key, _|
20
20
  key.casecmp(header) == 0
21
21
  end
22
22
  header_value
@@ -59,10 +59,13 @@ module Excon
59
59
 
60
60
  def self.parse(socket, datum)
61
61
  # this will discard any trailing lines from the previous response if any.
62
- begin
62
+ line = nil
63
+ loop do
63
64
  line = socket.readline
64
- end until status = line[9, 3].to_i
65
+ break if line[9,3].to_i != 0
66
+ end
65
67
 
68
+ status = line[9, 3].to_i
66
69
  reason_phrase = line[13..-3] # -3 strips the trailing "\r\n"
67
70
 
68
71
  datum[:response] = {
@@ -90,7 +93,7 @@ module Excon
90
93
 
91
94
  unless (['HEAD', 'CONNECT'].include?(datum[:method].to_s.upcase)) || NO_ENTITY.include?(datum[:response][:status])
92
95
 
93
- if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Transfer-Encoding') == 0 }
96
+ if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Transfer-Encoding') == 0 })
94
97
  encodings = Utils.split_header_value(datum[:response][:headers][key])
95
98
  if (encoding = encodings.last) && encoding.casecmp('chunked') == 0
96
99
  transfer_encoding_chunked = true
@@ -103,7 +106,7 @@ module Excon
103
106
  end
104
107
 
105
108
  # use :response_block unless :expects would fail
106
- if response_block = datum[:response_block]
109
+ if (response_block = datum[:response_block])
107
110
  if datum[:middlewares].include?(Excon::Middleware::Expects) && datum[:expects] &&
108
111
  !Array(datum[:expects]).include?(datum[:response][:status])
109
112
  response_block = nil
@@ -140,11 +143,11 @@ module Excon
140
143
  end
141
144
  parse_headers(socket, datum) # merge trailers into headers
142
145
  else
143
- if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Length') == 0 }
146
+ if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Length') == 0 })
144
147
  content_length = datum[:response][:headers][key].to_i
145
148
  end
146
149
 
147
- if remaining = content_length
150
+ if (remaining = content_length)
148
151
  if response_block
149
152
  while remaining > 0
150
153
  chunk = socket.read([datum[:chunk_size], remaining].min) || raise(EOFError)
@@ -160,11 +163,11 @@ module Excon
160
163
  end
161
164
  else
162
165
  if response_block
163
- while chunk = socket.read(datum[:chunk_size])
166
+ while (chunk = socket.read(datum[:chunk_size]))
164
167
  response_block.call(chunk, nil, nil)
165
168
  end
166
169
  else
167
- while chunk = socket.read(datum[:chunk_size])
170
+ while (chunk = socket.read(datum[:chunk_size]))
168
171
  datum[:response][:body] << chunk
169
172
  end
170
173
  end
data/lib/excon/socket.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require 'resolv'
3
+
2
4
  module Excon
3
5
  class Socket
4
6
  include Utils
@@ -60,18 +62,8 @@ module Excon
60
62
  def readline
61
63
  return legacy_readline if RUBY_VERSION.to_f <= 1.8_7
62
64
  buffer = String.new
63
- begin
64
- buffer << @socket.read_nonblock(1) while buffer[-1] != "\n"
65
- buffer
66
- rescue *READ_RETRY_EXCEPTION_CLASSES
67
- select_with_timeout(@socket, :read) && retry
68
- rescue OpenSSL::SSL::SSLError => error
69
- if error.message == 'read would block'
70
- select_with_timeout(@socket, :read) && retry
71
- else
72
- raise(error)
73
- end
74
- end
65
+ buffer << (read_nonblock(1) || raise(EOFError)) while buffer[-1] != "\n"
66
+ buffer
75
67
  end
76
68
 
77
69
  def legacy_readline
@@ -105,20 +97,17 @@ module Excon
105
97
  def connect
106
98
  @socket = nil
107
99
  exception = nil
100
+ hostname = @data[:hostname]
101
+ port = @port
102
+ family = @data[:family]
108
103
 
109
104
  if @data[:proxy]
110
- family = @data[:proxy][:family] || ::Socket::Constants::AF_UNSPEC
111
- args = [@data[:proxy][:hostname], @data[:proxy][:port], family, ::Socket::Constants::SOCK_STREAM]
112
- else
113
- family = @data[:family] || ::Socket::Constants::AF_UNSPEC
114
- args = [@data[:hostname], @port, family, ::Socket::Constants::SOCK_STREAM]
105
+ hostname = @data[:proxy][:hostname]
106
+ port = @data[:proxy][:port]
107
+ family = @data[:proxy][:family]
115
108
  end
116
- if RUBY_VERSION >= '1.9.2' && defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
117
- args << nil << nil << false # no reverse lookup
118
- end
119
- addrinfo = ::Socket.getaddrinfo(*args)
120
109
 
121
- addrinfo.each do |_, port, _, ip, a_family, s_type|
110
+ Resolv.each_address(hostname) do |ip|
122
111
  # already succeeded on previous addrinfo
123
112
  if @socket
124
113
  break
@@ -130,8 +119,8 @@ module Excon
130
119
  # nonblocking connect
131
120
  begin
132
121
  sockaddr = ::Socket.sockaddr_in(port, ip)
133
-
134
- socket = ::Socket.new(a_family, s_type, 0)
122
+ addrinfo = Addrinfo.getaddrinfo(ip, port, family, :STREAM).first
123
+ socket = ::Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol)
135
124
 
136
125
  if @data[:reuseaddr]
137
126
  socket.setsockopt(::Socket::Constants::SOL_SOCKET, ::Socket::Constants::SO_REUSEADDR, true)
@@ -161,6 +150,8 @@ module Excon
161
150
  end
162
151
  end
163
152
 
153
+ exception ||= Resolv::ResolvError.new("no address for #{hostname}")
154
+
164
155
  # this will be our last encountered exception
165
156
  fail exception unless @socket
166
157