excon 0.62.0 → 0.92.3

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 (125) hide show
  1. checksums.yaml +5 -5
  2. data/CONTRIBUTING.md +0 -1
  3. data/LICENSE.md +1 -1
  4. data/README.md +7 -6
  5. data/data/cacert.pem +1220 -1828
  6. data/excon.gemspec +19 -3
  7. data/lib/excon/connection.rb +216 -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 +5 -16
  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 +11 -4
  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 +59 -42
  25. data/lib/excon/ssl_socket.rb +37 -15
  26. data/lib/excon/test/plugin/server/exec.rb +5 -2
  27. data/lib/excon/test/plugin/server/puma.rb +4 -1
  28. data/lib/excon/test/plugin/server/unicorn.rb +5 -0
  29. data/lib/excon/test/plugin/server/webrick.rb +4 -1
  30. data/lib/excon/test/server.rb +1 -1
  31. data/lib/excon/unix_socket.rb +1 -0
  32. data/lib/excon/utils.rb +59 -5
  33. data/lib/excon/version.rb +1 -1
  34. data/lib/excon.rb +25 -17
  35. metadata +27 -98
  36. data/.document +0 -5
  37. data/.gitignore +0 -13
  38. data/.rspec +0 -3
  39. data/.travis.yml +0 -29
  40. data/Gemfile +0 -19
  41. data/Rakefile +0 -41
  42. data/benchmarks/class_vs_lambda.rb +0 -50
  43. data/benchmarks/concat_vs_insert.rb +0 -21
  44. data/benchmarks/concat_vs_interpolate.rb +0 -22
  45. data/benchmarks/cr_lf.rb +0 -21
  46. data/benchmarks/downcase-eq-eq_vs_casecmp.rb +0 -169
  47. data/benchmarks/excon.rb +0 -69
  48. data/benchmarks/excon_vs.rb +0 -165
  49. data/benchmarks/for_vs_array_each.rb +0 -27
  50. data/benchmarks/for_vs_hash_each.rb +0 -27
  51. data/benchmarks/has_key-vs-lookup.rb +0 -177
  52. data/benchmarks/headers_case_sensitivity.rb +0 -83
  53. data/benchmarks/headers_split_vs_match.rb +0 -34
  54. data/benchmarks/implicit_block-vs-explicit_block.rb +0 -98
  55. data/benchmarks/merging.rb +0 -21
  56. data/benchmarks/single_vs_double_quotes.rb +0 -21
  57. data/benchmarks/string_ranged_index.rb +0 -87
  58. data/benchmarks/strip_newline.rb +0 -115
  59. data/benchmarks/vs_stdlib.rb +0 -82
  60. data/changelog.txt +0 -1083
  61. data/spec/excon/error_spec.rb +0 -139
  62. data/spec/excon/test/server_spec.rb +0 -28
  63. data/spec/excon_spec.rb +0 -7
  64. data/spec/helpers/file_path_helpers.rb +0 -22
  65. data/spec/requests/basic_spec.rb +0 -40
  66. data/spec/requests/eof_requests_spec.rb +0 -36
  67. data/spec/requests/unix_socket_spec.rb +0 -46
  68. data/spec/spec_helper.rb +0 -24
  69. data/spec/support/shared_contexts/test_server_context.rb +0 -83
  70. data/spec/support/shared_examples/shared_example_for_clients.rb +0 -218
  71. data/spec/support/shared_examples/shared_example_for_streaming_clients.rb +0 -20
  72. data/spec/support/shared_examples/shared_example_for_test_servers.rb +0 -16
  73. data/tests/authorization_header_tests.rb +0 -29
  74. data/tests/bad_tests.rb +0 -47
  75. data/tests/basic_tests.rb +0 -351
  76. data/tests/batch_requests.rb +0 -133
  77. data/tests/complete_responses.rb +0 -31
  78. data/tests/data/127.0.0.1.cert.crt +0 -20
  79. data/tests/data/127.0.0.1.cert.key +0 -27
  80. data/tests/data/excon.cert.crt +0 -20
  81. data/tests/data/excon.cert.key +0 -27
  82. data/tests/data/xs +0 -1
  83. data/tests/error_tests.rb +0 -145
  84. data/tests/header_tests.rb +0 -119
  85. data/tests/middlewares/canned_response_tests.rb +0 -34
  86. data/tests/middlewares/capture_cookies_tests.rb +0 -34
  87. data/tests/middlewares/decompress_tests.rb +0 -157
  88. data/tests/middlewares/escape_path_tests.rb +0 -36
  89. data/tests/middlewares/idempotent_tests.rb +0 -206
  90. data/tests/middlewares/instrumentation_tests.rb +0 -315
  91. data/tests/middlewares/mock_tests.rb +0 -304
  92. data/tests/middlewares/redirect_follower_tests.rb +0 -112
  93. data/tests/pipeline_tests.rb +0 -40
  94. data/tests/proxy_tests.rb +0 -306
  95. data/tests/query_string_tests.rb +0 -87
  96. data/tests/rackups/basic.rb +0 -41
  97. data/tests/rackups/basic.ru +0 -3
  98. data/tests/rackups/basic_auth.ru +0 -14
  99. data/tests/rackups/deflater.ru +0 -4
  100. data/tests/rackups/proxy.ru +0 -18
  101. data/tests/rackups/query_string.ru +0 -13
  102. data/tests/rackups/redirecting.ru +0 -23
  103. data/tests/rackups/redirecting_with_cookie.ru +0 -40
  104. data/tests/rackups/request_headers.ru +0 -15
  105. data/tests/rackups/request_methods.ru +0 -21
  106. data/tests/rackups/response_header.ru +0 -18
  107. data/tests/rackups/ssl.ru +0 -16
  108. data/tests/rackups/ssl_mismatched_cn.ru +0 -15
  109. data/tests/rackups/ssl_verify_peer.ru +0 -16
  110. data/tests/rackups/streaming.ru +0 -30
  111. data/tests/rackups/thread_safety.ru +0 -17
  112. data/tests/rackups/timeout.ru +0 -14
  113. data/tests/rackups/webrick_patch.rb +0 -34
  114. data/tests/request_headers_tests.rb +0 -21
  115. data/tests/request_method_tests.rb +0 -47
  116. data/tests/request_tests.rb +0 -59
  117. data/tests/response_tests.rb +0 -197
  118. data/tests/servers/bad.rb +0 -20
  119. data/tests/servers/eof.rb +0 -17
  120. data/tests/servers/error.rb +0 -20
  121. data/tests/servers/good.rb +0 -350
  122. data/tests/test_helper.rb +0 -306
  123. data/tests/thread_safety_tests.rb +0 -39
  124. data/tests/timeout_tests.rb +0 -12
  125. data/tests/utils_tests.rb +0 -81
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
@@ -41,29 +60,18 @@ module Excon
41
60
  end
42
61
 
43
62
  def readline
44
- return legacy_readline if RUBY_VERSION.to_f <= 1.8_7
45
- buffer = String.new
46
- begin
47
- buffer << @socket.read_nonblock(1) while buffer[-1] != "\n"
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"
48
66
  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
58
- end
59
-
60
- def legacy_readline
61
- begin
62
- Timeout.timeout(@data[:read_timeout]) do
63
- @socket.readline
67
+ else # nonblock/legacy
68
+ begin
69
+ Timeout.timeout(@data[:read_timeout]) do
70
+ @socket.readline
71
+ end
72
+ rescue Timeout::Error
73
+ raise Excon::Errors::Timeout.new('read timeout reached')
64
74
  end
65
- rescue Timeout::Error
66
- raise Excon::Errors::Timeout.new('read timeout reached')
67
75
  end
68
76
  end
69
77
 
@@ -88,32 +96,30 @@ module Excon
88
96
  def connect
89
97
  @socket = nil
90
98
  exception = nil
99
+ hostname = @data[:hostname]
100
+ port = @port
101
+ family = @data[:family]
91
102
 
92
103
  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]
98
- end
99
- if RUBY_VERSION >= '1.9.2' && defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
100
- args << nil << nil << false # no reverse lookup
104
+ hostname = @data[:proxy][:hostname]
105
+ port = @data[:proxy][:port]
106
+ family = @data[:proxy][:family]
101
107
  end
102
- addrinfo = ::Socket.getaddrinfo(*args)
103
108
 
104
- addrinfo.each do |_, port, _, ip, a_family, s_type|
109
+ Resolv.each_address(hostname) do |ip|
105
110
  # already succeeded on previous addrinfo
106
111
  if @socket
107
112
  break
108
113
  end
109
-
114
+
110
115
  @remote_ip = ip
116
+ @data[:remote_ip] = ip
111
117
 
112
118
  # nonblocking connect
113
119
  begin
114
120
  sockaddr = ::Socket.sockaddr_in(port, ip)
115
-
116
- socket = ::Socket.new(a_family, s_type, 0)
121
+ addrinfo = Addrinfo.getaddrinfo(ip, port, family, :STREAM).first
122
+ socket = ::Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol)
117
123
 
118
124
  if @data[:reuseaddr]
119
125
  socket.setsockopt(::Socket::Constants::SOL_SOCKET, ::Socket::Constants::SO_REUSEADDR, true)
@@ -128,7 +134,7 @@ module Excon
128
134
  socket.connect(sockaddr)
129
135
  end
130
136
  @socket = socket
131
- rescue Errno::EINPROGRESS
137
+ rescue *CONNECT_RETRY_EXCEPTION_CLASSES
132
138
  select_with_timeout(socket, :connect_write)
133
139
  begin
134
140
  socket.connect_nonblock(sockaddr)
@@ -143,6 +149,8 @@ module Excon
143
149
  end
144
150
  end
145
151
 
152
+ exception ||= Resolv::ResolvError.new("no address for #{hostname}")
153
+
146
154
  # this will be our last encountered exception
147
155
  fail exception unless @socket
148
156
 
@@ -151,6 +159,17 @@ module Excon
151
159
  ::Socket::TCP_NODELAY,
152
160
  true)
153
161
  end
162
+
163
+ if @data[:keepalive]
164
+ if [:SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| ::Socket.const_defined? c}
165
+ @socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true)
166
+ @socket.setsockopt(::Socket::SOL_TCP, ::Socket::TCP_KEEPIDLE, @data[:keepalive][:time])
167
+ @socket.setsockopt(::Socket::SOL_TCP, ::Socket::TCP_KEEPINTVL, @data[:keepalive][:intvl])
168
+ @socket.setsockopt(::Socket::SOL_TCP, ::Socket::TCP_KEEPCNT, @data[:keepalive][:probes])
169
+ else
170
+ Excon.display_warning('Excon::Socket keepalive was set, but is not supported by Ruby version.')
171
+ end
172
+ end
154
173
  end
155
174
 
156
175
  def read_nonblock(max_length)
@@ -170,7 +189,7 @@ module Excon
170
189
  else
171
190
  raise(error)
172
191
  end
173
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
192
+ rescue *READ_RETRY_EXCEPTION_CLASSES
174
193
  if @read_buffer.empty?
175
194
  # if we didn't read anything, try again...
176
195
  select_with_timeout(@socket, :read) && retry
@@ -199,7 +218,7 @@ module Excon
199
218
  else
200
219
  raise(error)
201
220
  end
202
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
221
+ rescue *READ_RETRY_EXCEPTION_CLASSES
203
222
  if @read_buffer.empty?
204
223
  select_with_timeout(@socket, :read) && retry
205
224
  end
@@ -208,9 +227,7 @@ module Excon
208
227
  end
209
228
 
210
229
  def write_nonblock(data)
211
- if FORCE_ENC
212
- data.force_encoding('BINARY')
213
- end
230
+ data = binary_encode(data)
214
231
  loop do
215
232
  written = nil
216
233
  begin
@@ -225,7 +242,7 @@ module Excon
225
242
  else
226
243
  raise error
227
244
  end
228
- rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
245
+ rescue OpenSSL::SSL::SSLError, *WRITE_RETRY_EXCEPTION_CLASSES => error
229
246
  if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
230
247
  raise error
231
248
  else
@@ -246,7 +263,7 @@ module Excon
246
263
 
247
264
  def write_block(data)
248
265
  @socket.write(data)
249
- rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
266
+ rescue OpenSSL::SSL::SSLError, *WRITE_RETRY_EXCEPTION_CLASSES => error
250
267
  if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
251
268
  raise error
252
269
  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,26 +32,35 @@ 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
 
45
- # no defaults, fallback to bundled
46
- unless ca_file || ca_path || cert_store
57
+ if cert_store.nil?
47
58
  ssl_context.cert_store = OpenSSL::X509::Store.new
48
59
  ssl_context.cert_store.set_default_paths
60
+ end
49
61
 
62
+ # no defaults, fallback to bundled
63
+ unless ca_file || ca_path || cert_store
50
64
  # workaround issue #257 (JRUBY-6970)
51
65
  ca_file = DEFAULT_CA_FILE
52
66
  ca_file = ca_file.gsub(/^jar:/, '') if ca_file =~ /^jar:file:\//
@@ -58,7 +72,7 @@ module Excon
58
72
  end
59
73
  end
60
74
 
61
- if verify_callback = @data[:ssl_verify_callback]
75
+ if (verify_callback = @data[:ssl_verify_callback])
62
76
  ssl_context.verify_callback = verify_callback
63
77
  end
64
78
  else
@@ -66,6 +80,9 @@ module Excon
66
80
  ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
67
81
  end
68
82
 
83
+ # Verify certificate hostname if supported (ruby >= 2.4.0)
84
+ ssl_context.verify_hostname = @data[:ssl_verify_hostname] if ssl_context.respond_to?(:verify_hostname=)
85
+
69
86
  if client_cert_data && client_key_data
70
87
  ssl_context.cert = OpenSSL::X509::Certificate.new client_cert_data
71
88
  if OpenSSL::PKey.respond_to? :read
@@ -94,13 +111,20 @@ module Excon
94
111
 
95
112
  request += "Proxy-Connection: Keep-Alive#{Excon::CR_NL}"
96
113
 
114
+ if @data[:ssl_proxy_headers]
115
+ request << Utils.headers_hash_to_s(@data[:ssl_proxy_headers])
116
+ end
117
+
97
118
  request += Excon::CR_NL
98
119
 
99
120
  # write out the proxy setup request
100
121
  @socket.write(request)
101
122
 
102
123
  # eat the proxy's connection response
103
- Excon::Response.parse(self, :expects => 200, :method => 'CONNECT')
124
+ response = Excon::Response.parse(self, :expects => 200, :method => 'CONNECT')
125
+ if response[:response][:status] != 200
126
+ raise(Excon::Errors::ProxyConnectionError.new("proxy connection could not be established", request, response))
127
+ end
104
128
  end
105
129
 
106
130
  # convert Socket to OpenSSL::SSL::SSLSocket
@@ -132,18 +156,16 @@ module Excon
132
156
  if @data[:ssl_verify_peer]
133
157
  @socket.post_connection_check(@data[:ssl_verify_peer_host] || @data[:host])
134
158
  end
135
-
136
- @socket
137
159
  end
138
160
 
139
161
  private
140
162
 
141
163
  def client_cert_data
142
- @client_cert_data ||= if ccd = @data[:client_cert_data]
164
+ @client_cert_data ||= if (ccd = @data[:client_cert_data])
143
165
  ccd
144
- elsif path = @data[:client_cert]
166
+ elsif (path = @data[:client_cert])
145
167
  File.read path
146
- elsif path = @data[:certificate_path]
168
+ elsif (path = @data[:certificate_path])
147
169
  warn ":certificate_path is no longer supported and will be deprecated. Please use :client_cert or :client_cert_data"
148
170
  File.read path
149
171
  end
@@ -156,11 +178,11 @@ module Excon
156
178
  end
157
179
 
158
180
  def client_key_data
159
- @client_key_data ||= if ckd = @data[:client_key_data]
181
+ @client_key_data ||= if (ckd = @data[:client_key_data])
160
182
  ckd
161
- elsif path = @data[:client_key]
183
+ elsif (path = @data[:client_key])
162
184
  File.read path
163
- elsif path = @data[:private_key_path]
185
+ elsif (path = @data[:private_key_path])
164
186
  warn ":private_key_path is no longer supported and will be deprecated. Please use :client_key or :client_key_data"
165
187
  File.read path
166
188
  end
@@ -4,13 +4,16 @@ module Excon
4
4
  module Server
5
5
  module Exec
6
6
  def start(app_str = app)
7
+ open_process(app_str)
8
+ process_stderr = ""
7
9
  line = ''
8
- open_process(app)
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
- msg = "executable #{app} has taken too long to start"
16
+ msg = "executable #{app_str} has taken too long to start"
14
17
  raise msg
15
18
  end
16
19
  end
@@ -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
@@ -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,49 @@ 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
+ # 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"')
136
+ end
137
+ headers_str << key.to_s << ': ' << value.to_s << CR_NL
138
+ end
139
+ end
140
+ headers_str
141
+ end
88
142
  end
89
143
  end
data/lib/excon/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Excon
3
- VERSION = '0.62.0'
3
+ VERSION = '0.92.3'
4
4
  end
data/lib/excon.rb CHANGED
@@ -23,11 +23,11 @@ require 'excon/middlewares/instrumentor'
23
23
  require 'excon/middlewares/mock'
24
24
  require 'excon/middlewares/response_parser'
25
25
 
26
+ require 'excon/error'
26
27
  require 'excon/constants'
27
28
  require 'excon/utils'
28
29
 
29
30
  require 'excon/connection'
30
- require 'excon/error'
31
31
  require 'excon/headers'
32
32
  require 'excon/response'
33
33
  require 'excon/middlewares/decompress'
@@ -61,6 +61,14 @@ module Excon
61
61
  if $VERBOSE || ENV['EXCON_DEBUG']
62
62
  $stderr.puts "[excon][WARNING] #{warning}\n#{ caller.join("\n") }"
63
63
  end
64
+
65
+ if @raise_on_warnings
66
+ raise Error::Warning.new(warning)
67
+ end
68
+ end
69
+
70
+ def set_raise_on_warnings!(should_raise)
71
+ @raise_on_warnings = should_raise
64
72
  end
65
73
 
66
74
  # Status of mocking
@@ -105,9 +113,9 @@ module Excon
105
113
 
106
114
  # @see Connection#initialize
107
115
  # Initializes a new keep-alive session for a given remote host
108
- # @param [String] url The destination URL
109
- # @param [Hash<Symbol, >] params One or more option params to set on the Connection instance
110
- # @return [Connection] A new Excon::Connection instance
116
+ # @param [String] url The destination URL
117
+ # @param [Hash<Symbol, >] params One or more option params to set on the Connection instance
118
+ # @return [Connection] A new Excon::Connection instance
111
119
  def new(url, params = {})
112
120
  uri_parser = params[:uri_parser] || defaults[:uri_parser]
113
121
  uri = uri_parser.parse(url)
@@ -135,13 +143,13 @@ module Excon
135
143
  end
136
144
 
137
145
  # push an additional stub onto the list to check for mock requests
138
- # @param [Hash<Symbol, >] request params to match against, omitted params match all
139
- # @param [Hash<Symbol, >] response params to return from matched request or block to call with params
140
- def stub(request_params = {}, response_params = nil)
141
- if method = request_params.delete(:method)
146
+ # @param request_params [Hash<Symbol, >] request params to match against, omitted params match all
147
+ # @param response_params [Hash<Symbol, >] response params to return from matched request or block to call with params
148
+ def stub(request_params = {}, response_params = nil, &block)
149
+ if (method = request_params.delete(:method))
142
150
  request_params[:method] = method.to_s.downcase.to_sym
143
151
  end
144
- if url = request_params.delete(:url)
152
+ if (url = request_params.delete(:url))
145
153
  uri = URI.parse(url)
146
154
  request_params = {
147
155
  :host => uri.host,
@@ -167,7 +175,7 @@ module Excon
167
175
  if response_params
168
176
  raise(ArgumentError.new("stub requires either response_params OR a block"))
169
177
  else
170
- stub = [request_params, Proc.new]
178
+ stub = [request_params, block]
171
179
  end
172
180
  elsif response_params
173
181
  stub = [request_params, response_params]
@@ -179,10 +187,10 @@ module Excon
179
187
  end
180
188
 
181
189
  # get a stub matching params or nil
182
- # @param [Hash<Symbol, >] request params to match against, omitted params match all
183
- # @return [Hash<Symbol, >] response params to return from matched request or block to call with params
190
+ # @param request_params [Hash<Symbol, >] request params to match against, omitted params match all
191
+ # @return [Hash<Symbol, >] response params to return from matched request or block to call with params
184
192
  def stub_for(request_params={})
185
- if method = request_params.delete(:method)
193
+ if (method = request_params.delete(:method))
186
194
  request_params[:method] = method.to_s.downcase.to_sym
187
195
  end
188
196
  Excon.stubs.each do |stub, response_params|
@@ -190,7 +198,7 @@ module Excon
190
198
  headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
191
199
  case value = stub[:headers][key]
192
200
  when Regexp
193
- if match = value.match(request_params[:headers][key])
201
+ if (match = value.match(request_params[:headers][key]))
194
202
  captures[:headers][key] = match.captures
195
203
  end
196
204
  match
@@ -201,7 +209,7 @@ module Excon
201
209
  non_headers_match = (stub.keys - [:headers]).all? do |key|
202
210
  case value = stub[key]
203
211
  when Regexp
204
- if match = value.match(request_params[key])
212
+ if (match = value.match(request_params[key]))
205
213
  captures[key] = match.captures
206
214
  end
207
215
  match
@@ -228,8 +236,8 @@ module Excon
228
236
  end
229
237
 
230
238
  # remove first/oldest stub matching request_params
231
- # @param [Hash<Symbol, >] request params to match against, omitted params match all
232
- # @return [Hash<Symbol, >] response params from deleted stub
239
+ # @param request_params [Hash<Symbol, >] request params to match against, omitted params match all
240
+ # @return [Hash<Symbol, >] response params from deleted stub
233
241
  def unstub(request_params = {})
234
242
  stub = stub_for(request_params)
235
243
  Excon.stubs.delete_at(Excon.stubs.index(stub))