excon 0.62.0 → 0.91.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 (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 +1154 -1798
  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 +51 -33
  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
@@ -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,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.91.0'
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))