excon 0.62.0 → 0.85.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.
Files changed (121) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.md +1 -1
  3. data/README.md +6 -5
  4. data/data/cacert.pem +939 -1720
  5. data/excon.gemspec +17 -2
  6. data/lib/excon.rb +25 -17
  7. data/lib/excon/connection.rb +206 -139
  8. data/lib/excon/constants.rb +38 -13
  9. data/lib/excon/error.rb +15 -0
  10. data/lib/excon/headers.rb +4 -3
  11. data/lib/excon/instrumentors/logging_instrumentor.rb +4 -15
  12. data/lib/excon/instrumentors/standard_instrumentor.rb +2 -9
  13. data/lib/excon/middlewares/base.rb +6 -0
  14. data/lib/excon/middlewares/capture_cookies.rb +1 -1
  15. data/lib/excon/middlewares/decompress.rb +2 -2
  16. data/lib/excon/middlewares/expects.rb +7 -1
  17. data/lib/excon/middlewares/idempotent.rb +20 -3
  18. data/lib/excon/middlewares/instrumentor.rb +8 -0
  19. data/lib/excon/middlewares/mock.rb +12 -3
  20. data/lib/excon/middlewares/redirect_follower.rb +25 -3
  21. data/lib/excon/middlewares/response_parser.rb +3 -0
  22. data/lib/excon/pretty_printer.rb +1 -8
  23. data/lib/excon/response.rb +12 -9
  24. data/lib/excon/socket.rb +51 -33
  25. data/lib/excon/ssl_socket.rb +33 -13
  26. data/lib/excon/test/plugin/server/exec.rb +2 -2
  27. data/lib/excon/test/server.rb +1 -1
  28. data/lib/excon/unix_socket.rb +1 -0
  29. data/lib/excon/utils.rb +58 -5
  30. data/lib/excon/version.rb +1 -1
  31. metadata +27 -98
  32. data/.document +0 -5
  33. data/.gitignore +0 -13
  34. data/.rspec +0 -3
  35. data/.travis.yml +0 -29
  36. data/Gemfile +0 -19
  37. data/Rakefile +0 -41
  38. data/benchmarks/class_vs_lambda.rb +0 -50
  39. data/benchmarks/concat_vs_insert.rb +0 -21
  40. data/benchmarks/concat_vs_interpolate.rb +0 -22
  41. data/benchmarks/cr_lf.rb +0 -21
  42. data/benchmarks/downcase-eq-eq_vs_casecmp.rb +0 -169
  43. data/benchmarks/excon.rb +0 -69
  44. data/benchmarks/excon_vs.rb +0 -165
  45. data/benchmarks/for_vs_array_each.rb +0 -27
  46. data/benchmarks/for_vs_hash_each.rb +0 -27
  47. data/benchmarks/has_key-vs-lookup.rb +0 -177
  48. data/benchmarks/headers_case_sensitivity.rb +0 -83
  49. data/benchmarks/headers_split_vs_match.rb +0 -34
  50. data/benchmarks/implicit_block-vs-explicit_block.rb +0 -98
  51. data/benchmarks/merging.rb +0 -21
  52. data/benchmarks/single_vs_double_quotes.rb +0 -21
  53. data/benchmarks/string_ranged_index.rb +0 -87
  54. data/benchmarks/strip_newline.rb +0 -115
  55. data/benchmarks/vs_stdlib.rb +0 -82
  56. data/changelog.txt +0 -1083
  57. data/spec/excon/error_spec.rb +0 -139
  58. data/spec/excon/test/server_spec.rb +0 -28
  59. data/spec/excon_spec.rb +0 -7
  60. data/spec/helpers/file_path_helpers.rb +0 -22
  61. data/spec/requests/basic_spec.rb +0 -40
  62. data/spec/requests/eof_requests_spec.rb +0 -36
  63. data/spec/requests/unix_socket_spec.rb +0 -46
  64. data/spec/spec_helper.rb +0 -24
  65. data/spec/support/shared_contexts/test_server_context.rb +0 -83
  66. data/spec/support/shared_examples/shared_example_for_clients.rb +0 -218
  67. data/spec/support/shared_examples/shared_example_for_streaming_clients.rb +0 -20
  68. data/spec/support/shared_examples/shared_example_for_test_servers.rb +0 -16
  69. data/tests/authorization_header_tests.rb +0 -29
  70. data/tests/bad_tests.rb +0 -47
  71. data/tests/basic_tests.rb +0 -351
  72. data/tests/batch_requests.rb +0 -133
  73. data/tests/complete_responses.rb +0 -31
  74. data/tests/data/127.0.0.1.cert.crt +0 -20
  75. data/tests/data/127.0.0.1.cert.key +0 -27
  76. data/tests/data/excon.cert.crt +0 -20
  77. data/tests/data/excon.cert.key +0 -27
  78. data/tests/data/xs +0 -1
  79. data/tests/error_tests.rb +0 -145
  80. data/tests/header_tests.rb +0 -119
  81. data/tests/middlewares/canned_response_tests.rb +0 -34
  82. data/tests/middlewares/capture_cookies_tests.rb +0 -34
  83. data/tests/middlewares/decompress_tests.rb +0 -157
  84. data/tests/middlewares/escape_path_tests.rb +0 -36
  85. data/tests/middlewares/idempotent_tests.rb +0 -206
  86. data/tests/middlewares/instrumentation_tests.rb +0 -315
  87. data/tests/middlewares/mock_tests.rb +0 -304
  88. data/tests/middlewares/redirect_follower_tests.rb +0 -112
  89. data/tests/pipeline_tests.rb +0 -40
  90. data/tests/proxy_tests.rb +0 -306
  91. data/tests/query_string_tests.rb +0 -87
  92. data/tests/rackups/basic.rb +0 -41
  93. data/tests/rackups/basic.ru +0 -3
  94. data/tests/rackups/basic_auth.ru +0 -14
  95. data/tests/rackups/deflater.ru +0 -4
  96. data/tests/rackups/proxy.ru +0 -18
  97. data/tests/rackups/query_string.ru +0 -13
  98. data/tests/rackups/redirecting.ru +0 -23
  99. data/tests/rackups/redirecting_with_cookie.ru +0 -40
  100. data/tests/rackups/request_headers.ru +0 -15
  101. data/tests/rackups/request_methods.ru +0 -21
  102. data/tests/rackups/response_header.ru +0 -18
  103. data/tests/rackups/ssl.ru +0 -16
  104. data/tests/rackups/ssl_mismatched_cn.ru +0 -15
  105. data/tests/rackups/ssl_verify_peer.ru +0 -16
  106. data/tests/rackups/streaming.ru +0 -30
  107. data/tests/rackups/thread_safety.ru +0 -17
  108. data/tests/rackups/timeout.ru +0 -14
  109. data/tests/rackups/webrick_patch.rb +0 -34
  110. data/tests/request_headers_tests.rb +0 -21
  111. data/tests/request_method_tests.rb +0 -47
  112. data/tests/request_tests.rb +0 -59
  113. data/tests/response_tests.rb +0 -197
  114. data/tests/servers/bad.rb +0 -20
  115. data/tests/servers/eof.rb +0 -17
  116. data/tests/servers/error.rb +0 -20
  117. data/tests/servers/good.rb +0 -350
  118. data/tests/test_helper.rb +0 -306
  119. data/tests/thread_safety_tests.rb +0 -39
  120. data/tests/timeout_tests.rb +0 -12
  121. data/tests/utils_tests.rb +0 -81
@@ -6,6 +6,9 @@ module Excon
6
6
  unless datum.has_key?(:response)
7
7
  datum = Excon::Response.parse(datum[:connection].send(:socket), datum)
8
8
  end
9
+ if datum.has_key?(:logger)
10
+ datum[:response][:logger] = datum[:logger]
11
+ end
9
12
  @stack.response_call(datum)
10
13
  end
11
14
  end
@@ -9,14 +9,7 @@ module Excon
9
9
  datum.delete(:connection)
10
10
  datum.delete(:stack)
11
11
 
12
- if datum.has_key?(:headers) && datum[:headers].has_key?('Authorization')
13
- datum[:headers] = datum[:headers].dup
14
- datum[:headers]['Authorization'] = REDACTED
15
- end
16
-
17
- if datum.has_key?(:password)
18
- datum[:password] = REDACTED
19
- end
12
+ datum = Utils.redact(datum)
20
13
  end
21
14
 
22
15
  indent += 2
@@ -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
@@ -222,7 +225,7 @@ module Excon
222
225
  end
223
226
 
224
227
  # Retrieve a specific header value. Header names are treated case-insensitively.
225
- # @param [String] name Header name
228
+ # @param [String] name Header name
226
229
  def get_header(name)
227
230
  headers[name]
228
231
  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
@@ -7,6 +9,23 @@ module Excon
7
9
 
8
10
  attr_accessor :data
9
11
 
12
+ # read/write drawn from https://github.com/ruby-amqp/bunny/commit/75d9dd79551b31a5dd3d1254c537bad471f108cf
13
+ CONNECT_RETRY_EXCEPTION_CLASSES = if defined?(IO::EINPROGRESSWaitWritable) # Ruby >= 2.1
14
+ [Errno::EINPROGRESS, IO::EINPROGRESSWaitWritable]
15
+ else # Ruby <= 2.0
16
+ [Errno::EINPROGRESS]
17
+ end
18
+ READ_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitReadable) # Ruby >= 2.1
19
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable, IO::EAGAINWaitReadable, IO::EWOULDBLOCKWaitReadable]
20
+ else # Ruby <= 2.0
21
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable]
22
+ end
23
+ WRITE_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitWritable) # Ruby >= 2.1
24
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable, IO::EAGAINWaitWritable, IO::EWOULDBLOCKWaitWritable]
25
+ else # Ruby <= 2.0
26
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable]
27
+ end
28
+
10
29
  def params
11
30
  Excon.display_warning('Excon::Socket#params is deprecated use Excon::Socket#data instead.')
12
31
  @data
@@ -43,18 +62,8 @@ module Excon
43
62
  def readline
44
63
  return legacy_readline if RUBY_VERSION.to_f <= 1.8_7
45
64
  buffer = String.new
46
- begin
47
- buffer << @socket.read_nonblock(1) while buffer[-1] != "\n"
48
- buffer
49
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
50
- select_with_timeout(@socket, :read) && retry
51
- rescue OpenSSL::SSL::SSLError => error
52
- if error.message == 'read would block'
53
- select_with_timeout(@socket, :read) && retry
54
- else
55
- raise(error)
56
- end
57
- end
65
+ buffer << (read_nonblock(1) || raise(EOFError)) while buffer[-1] != "\n"
66
+ buffer
58
67
  end
59
68
 
60
69
  def legacy_readline
@@ -88,32 +97,30 @@ module Excon
88
97
  def connect
89
98
  @socket = nil
90
99
  exception = nil
100
+ hostname = @data[:hostname]
101
+ port = @port
102
+ family = @data[:family]
91
103
 
92
104
  if @data[:proxy]
93
- family = @data[:proxy][:family] || ::Socket::Constants::AF_UNSPEC
94
- args = [@data[:proxy][:hostname], @data[:proxy][:port], family, ::Socket::Constants::SOCK_STREAM]
95
- else
96
- family = @data[:family] || ::Socket::Constants::AF_UNSPEC
97
- args = [@data[:hostname], @port, family, ::Socket::Constants::SOCK_STREAM]
105
+ hostname = @data[:proxy][:hostname]
106
+ port = @data[:proxy][:port]
107
+ family = @data[:proxy][:family]
98
108
  end
99
- if RUBY_VERSION >= '1.9.2' && defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
100
- args << nil << nil << false # no reverse lookup
101
- end
102
- addrinfo = ::Socket.getaddrinfo(*args)
103
109
 
104
- addrinfo.each do |_, port, _, ip, a_family, s_type|
110
+ Resolv.each_address(hostname) do |ip|
105
111
  # already succeeded on previous addrinfo
106
112
  if @socket
107
113
  break
108
114
  end
109
-
115
+
110
116
  @remote_ip = ip
117
+ @data[:remote_ip] = ip
111
118
 
112
119
  # nonblocking connect
113
120
  begin
114
121
  sockaddr = ::Socket.sockaddr_in(port, ip)
115
-
116
- 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)
117
124
 
118
125
  if @data[:reuseaddr]
119
126
  socket.setsockopt(::Socket::Constants::SOL_SOCKET, ::Socket::Constants::SO_REUSEADDR, true)
@@ -128,7 +135,7 @@ module Excon
128
135
  socket.connect(sockaddr)
129
136
  end
130
137
  @socket = socket
131
- rescue Errno::EINPROGRESS
138
+ rescue *CONNECT_RETRY_EXCEPTION_CLASSES
132
139
  select_with_timeout(socket, :connect_write)
133
140
  begin
134
141
  socket.connect_nonblock(sockaddr)
@@ -143,6 +150,8 @@ module Excon
143
150
  end
144
151
  end
145
152
 
153
+ exception ||= Resolv::ResolvError.new("no address for #{hostname}")
154
+
146
155
  # this will be our last encountered exception
147
156
  fail exception unless @socket
148
157
 
@@ -151,6 +160,17 @@ module Excon
151
160
  ::Socket::TCP_NODELAY,
152
161
  true)
153
162
  end
163
+
164
+ if @data[:keepalive]
165
+ if [:SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| ::Socket.const_defined? c}
166
+ @socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true)
167
+ @socket.setsockopt(::Socket::SOL_TCP, ::Socket::TCP_KEEPIDLE, @data[:keepalive][:time])
168
+ @socket.setsockopt(::Socket::SOL_TCP, ::Socket::TCP_KEEPINTVL, @data[:keepalive][:intvl])
169
+ @socket.setsockopt(::Socket::SOL_TCP, ::Socket::TCP_KEEPCNT, @data[:keepalive][:probes])
170
+ else
171
+ Excon.display_warning('Excon::Socket keepalive was set, but is not supported by Ruby version.')
172
+ end
173
+ end
154
174
  end
155
175
 
156
176
  def read_nonblock(max_length)
@@ -170,7 +190,7 @@ module Excon
170
190
  else
171
191
  raise(error)
172
192
  end
173
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
193
+ rescue *READ_RETRY_EXCEPTION_CLASSES
174
194
  if @read_buffer.empty?
175
195
  # if we didn't read anything, try again...
176
196
  select_with_timeout(@socket, :read) && retry
@@ -199,7 +219,7 @@ module Excon
199
219
  else
200
220
  raise(error)
201
221
  end
202
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
222
+ rescue *READ_RETRY_EXCEPTION_CLASSES
203
223
  if @read_buffer.empty?
204
224
  select_with_timeout(@socket, :read) && retry
205
225
  end
@@ -208,9 +228,7 @@ module Excon
208
228
  end
209
229
 
210
230
  def write_nonblock(data)
211
- if FORCE_ENC
212
- data.force_encoding('BINARY')
213
- end
231
+ data = binary_encode(data)
214
232
  loop do
215
233
  written = nil
216
234
  begin
@@ -225,7 +243,7 @@ module Excon
225
243
  else
226
244
  raise error
227
245
  end
228
- rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
246
+ rescue OpenSSL::SSL::SSLError, *WRITE_RETRY_EXCEPTION_CLASSES => error
229
247
  if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
230
248
  raise error
231
249
  else
@@ -246,7 +264,7 @@ module Excon
246
264
 
247
265
  def write_block(data)
248
266
  @socket.write(data)
249
- rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
267
+ rescue OpenSSL::SSL::SSLError, *WRITE_RETRY_EXCEPTION_CLASSES => error
250
268
  if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
251
269
  raise error
252
270
  else
@@ -12,6 +12,11 @@ module Excon
12
12
  # create ssl context
13
13
  ssl_context = OpenSSL::SSL::SSLContext.new
14
14
 
15
+ # set the security level before setting other parameters affected by it
16
+ if @data[:ssl_security_level]
17
+ ssl_context.security_level = @data[:ssl_security_level]
18
+ end
19
+
15
20
  # disable less secure options, when supported
16
21
  ssl_context_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
17
22
  if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
@@ -27,18 +32,25 @@ module Excon
27
32
  if @data[:ssl_version]
28
33
  ssl_context.ssl_version = @data[:ssl_version]
29
34
  end
35
+ if @data[:ssl_min_version]
36
+ ssl_context.min_version = @data[:ssl_min_version]
37
+ end
38
+ if @data[:ssl_max_version]
39
+ ssl_context.max_version = @data[:ssl_max_version]
40
+ end
41
+
30
42
 
31
43
  if @data[:ssl_verify_peer]
32
44
  # turn verification on
33
45
  ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
34
46
 
35
- if ca_file = @data[:ssl_ca_file] || ENV['SSL_CERT_FILE']
47
+ if (ca_file = @data[:ssl_ca_file] || ENV['SSL_CERT_FILE'])
36
48
  ssl_context.ca_file = ca_file
37
49
  end
38
- if ca_path = @data[:ssl_ca_path] || ENV['SSL_CERT_DIR']
50
+ if (ca_path = @data[:ssl_ca_path] || ENV['SSL_CERT_DIR'])
39
51
  ssl_context.ca_path = ca_path
40
52
  end
41
- if cert_store = @data[:ssl_cert_store]
53
+ if (cert_store = @data[:ssl_cert_store])
42
54
  ssl_context.cert_store = cert_store
43
55
  end
44
56
 
@@ -58,7 +70,7 @@ module Excon
58
70
  end
59
71
  end
60
72
 
61
- if verify_callback = @data[:ssl_verify_callback]
73
+ if (verify_callback = @data[:ssl_verify_callback])
62
74
  ssl_context.verify_callback = verify_callback
63
75
  end
64
76
  else
@@ -66,6 +78,9 @@ module Excon
66
78
  ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
67
79
  end
68
80
 
81
+ # Verify certificate hostname if supported (ruby >= 2.4.0)
82
+ ssl_context.verify_hostname = @data[:ssl_verify_hostname] if ssl_context.respond_to?(:verify_hostname=)
83
+
69
84
  if client_cert_data && client_key_data
70
85
  ssl_context.cert = OpenSSL::X509::Certificate.new client_cert_data
71
86
  if OpenSSL::PKey.respond_to? :read
@@ -94,13 +109,20 @@ module Excon
94
109
 
95
110
  request += "Proxy-Connection: Keep-Alive#{Excon::CR_NL}"
96
111
 
112
+ if @data[:ssl_proxy_headers]
113
+ request << Utils.headers_hash_to_s(@data[:ssl_proxy_headers])
114
+ end
115
+
97
116
  request += Excon::CR_NL
98
117
 
99
118
  # write out the proxy setup request
100
119
  @socket.write(request)
101
120
 
102
121
  # eat the proxy's connection response
103
- Excon::Response.parse(self, :expects => 200, :method => 'CONNECT')
122
+ response = Excon::Response.parse(self, :expects => 200, :method => 'CONNECT')
123
+ if response[:response][:status] != 200
124
+ raise(Excon::Errors::ProxyConnectionError.new("proxy connection could not be established", request, response))
125
+ end
104
126
  end
105
127
 
106
128
  # convert Socket to OpenSSL::SSL::SSLSocket
@@ -132,18 +154,16 @@ module Excon
132
154
  if @data[:ssl_verify_peer]
133
155
  @socket.post_connection_check(@data[:ssl_verify_peer_host] || @data[:host])
134
156
  end
135
-
136
- @socket
137
157
  end
138
158
 
139
159
  private
140
160
 
141
161
  def client_cert_data
142
- @client_cert_data ||= if ccd = @data[:client_cert_data]
162
+ @client_cert_data ||= if (ccd = @data[:client_cert_data])
143
163
  ccd
144
- elsif path = @data[:client_cert]
164
+ elsif (path = @data[:client_cert])
145
165
  File.read path
146
- elsif path = @data[:certificate_path]
166
+ elsif (path = @data[:certificate_path])
147
167
  warn ":certificate_path is no longer supported and will be deprecated. Please use :client_cert or :client_cert_data"
148
168
  File.read path
149
169
  end
@@ -156,11 +176,11 @@ module Excon
156
176
  end
157
177
 
158
178
  def client_key_data
159
- @client_key_data ||= if ckd = @data[:client_key_data]
179
+ @client_key_data ||= if (ckd = @data[:client_key_data])
160
180
  ckd
161
- elsif path = @data[:client_key]
181
+ elsif (path = @data[:client_key])
162
182
  File.read path
163
- elsif path = @data[:private_key_path]
183
+ elsif (path = @data[:private_key_path])
164
184
  warn ":private_key_path is no longer supported and will be deprecated. Please use :client_key or :client_key_data"
165
185
  File.read path
166
186
  end
@@ -5,12 +5,12 @@ module Excon
5
5
  module Exec
6
6
  def start(app_str = app)
7
7
  line = ''
8
- open_process(app)
8
+ open_process(app_str)
9
9
  until line =~ /\Aready\Z/
10
10
  line = error.gets
11
11
  fatal_time = elapsed_time > timeout
12
12
  if fatal_time
13
- msg = "executable #{app} has taken too long to start"
13
+ msg = "executable #{app_str} has taken too long to start"
14
14
  raise msg
15
15
  end
16
16
  end
@@ -72,7 +72,7 @@ module Excon
72
72
  end
73
73
  def dump_errors
74
74
  lines = error.read.split($/)
75
- while line = lines.shift
75
+ while (line = lines.shift)
76
76
  case line
77
77
  when /(ERROR|Error)/
78
78
  unless line =~ /(null cert chain|did not return a certificate|SSL_read:: internal error)/
@@ -20,6 +20,7 @@ module Excon
20
20
  begin
21
21
  @socket.connect_nonblock(sockaddr)
22
22
  rescue Errno::EISCONN
23
+ 0 # same return as connect_nonblock success
23
24
  end
24
25
  end
25
26
  else
data/lib/excon/utils.rb CHANGED
@@ -10,6 +10,18 @@ module Excon
10
10
  UNESCAPED = /([#{ Regexp.escape(CONTROL + ' ' + DELIMS + UNWISE + NONASCII) }])/
11
11
  ESCAPED = /%([0-9a-fA-F]{2})/
12
12
 
13
+ def binary_encode(string)
14
+ if FORCE_ENC && string.encoding != Encoding::ASCII_8BIT
15
+ if string.frozen?
16
+ string.dup.force_encoding('BINARY')
17
+ else
18
+ string.force_encoding('BINARY')
19
+ end
20
+ else
21
+ string
22
+ end
23
+ end
24
+
13
25
  def connection_uri(datum = @data)
14
26
  unless datum
15
27
  raise ArgumentError, '`datum` must be given unless called on a Connection'
@@ -21,6 +33,30 @@ module Excon
21
33
  end
22
34
  end
23
35
 
36
+ # Redact sensitive info from provided data
37
+ def redact(datum)
38
+ datum = datum.dup
39
+ if datum.has_key?(:headers)
40
+ if datum[:headers].has_key?('Authorization') || datum[:headers].has_key?('Proxy-Authorization')
41
+ datum[:headers] = datum[:headers].dup
42
+ end
43
+ if datum[:headers].has_key?('Authorization')
44
+ datum[:headers]['Authorization'] = REDACTED
45
+ end
46
+ if datum[:headers].has_key?('Proxy-Authorization')
47
+ datum[:headers]['Proxy-Authorization'] = REDACTED
48
+ end
49
+ end
50
+ if datum.has_key?(:password)
51
+ datum[:password] = REDACTED
52
+ end
53
+ if datum.has_key?(:proxy) && datum[:proxy] && datum[:proxy].has_key?(:password)
54
+ datum[:proxy] = datum[:proxy].dup
55
+ datum[:proxy][:password] = REDACTED
56
+ end
57
+ datum
58
+ end
59
+
24
60
  def request_uri(datum)
25
61
  connection_uri(datum) + datum[:path] + query_string(datum)
26
62
  end
@@ -59,31 +95,48 @@ module Excon
59
95
  def split_header_value(str)
60
96
  return [] if str.nil?
61
97
  str = str.dup.strip
62
- str.force_encoding('BINARY') if FORCE_ENC
63
- str.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]+)+)
98
+ str = binary_encode(str)
99
+ str.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",])+)
64
100
  (?:,\s*|\Z)'xn).flatten
65
101
  end
66
102
 
67
103
  # Escapes HTTP reserved and unwise characters in +str+
68
104
  def escape_uri(str)
69
105
  str = str.dup
70
- str.force_encoding('BINARY') if FORCE_ENC
106
+ str = binary_encode(str)
71
107
  str.gsub(UNESCAPED) { "%%%02X" % $1[0].ord }
72
108
  end
73
109
 
74
110
  # Unescapes HTTP reserved and unwise characters in +str+
75
111
  def unescape_uri(str)
76
112
  str = str.dup
77
- str.force_encoding('BINARY') if FORCE_ENC
113
+ str = binary_encode(str)
78
114
  str.gsub(ESCAPED) { $1.hex.chr }
79
115
  end
80
116
 
81
117
  # Unescape form encoded values in +str+
82
118
  def unescape_form(str)
83
119
  str = str.dup
84
- str.force_encoding('BINARY') if FORCE_ENC
120
+ str = binary_encode(str)
85
121
  str.gsub!(/\+/, ' ')
86
122
  str.gsub(ESCAPED) { $1.hex.chr }
87
123
  end
124
+
125
+ # Performs validation on the passed header hash and returns a string representation of the headers
126
+ def headers_hash_to_s(headers)
127
+ headers_str = String.new
128
+ headers.each do |key, values|
129
+ if key.to_s.match(/[\r\n]/)
130
+ raise Excon::Errors::InvalidHeaderKey.new(key.to_s.inspect + ' contains forbidden "\r" or "\n"')
131
+ end
132
+ [values].flatten.each do |value|
133
+ if value.to_s.match(/[\r\n]/)
134
+ raise Excon::Errors::InvalidHeaderValue.new(value.to_s.inspect + ' contains forbidden "\r" or "\n"')
135
+ end
136
+ headers_str << key.to_s << ': ' << value.to_s << CR_NL
137
+ end
138
+ end
139
+ headers_str
140
+ end
88
141
  end
89
142
  end