excon 0.62.0 → 0.92.3

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