excon 1.3.0 → 1.5.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.
@@ -60,6 +60,8 @@ module Excon
60
60
  # @option params [Fixnum] :retry_interval Set how long to wait between retries. (Default 0)
61
61
  # @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
62
62
  # @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon'
63
+ # @option params [Resolv, nil] :resolv_resolver A ready to use +Resolv+ resolver instance.
64
+ # @option params [Class] :resolver_factory Const of the resolver factory. Defaults to 'Excon::ResolverFactory'
63
65
  def initialize(params = {})
64
66
  @pid = Process.pid
65
67
  @data = Excon.defaults.dup
@@ -100,6 +102,10 @@ module Excon
100
102
  @socket_key = "#{@data[:scheme]}://#{@data[:socket]}"
101
103
  end
102
104
  else
105
+ if @data.key?(:host) && @data[:host].nil?
106
+ raise Excon::Error::InvalidParameter, "host is required for non-Unix connections; " \
107
+ "the URL may be malformed (e.g. missing \"//\" after the scheme)"
108
+ end
103
109
  @socket_key = "#{@data[:scheme]}://#{@data[:host]}#{port_string(@data)}"
104
110
  end
105
111
  reset
@@ -254,6 +260,10 @@ module Excon
254
260
  if datum[:scheme] == UNIX
255
261
  datum[:headers][host_key] ||= ''
256
262
  else
263
+ if datum[:host].nil?
264
+ raise Excon::Error::InvalidParameter, "host is required for non-Unix connections; " \
265
+ "the URL may be malformed (e.g. missing \"//\" after the scheme)"
266
+ end
257
267
  datum[:headers][host_key] ||= datum[:host] + port_string(datum)
258
268
  end
259
269
 
@@ -483,6 +493,13 @@ module Excon
483
493
  unix_proxy = datum[:proxy] ? datum[:proxy][:scheme] == UNIX : false
484
494
  sockets[@socket_key] ||= if datum[:scheme] == UNIX || unix_proxy
485
495
  Excon::UnixSocket.new(datum)
496
+ elsif datum[:socks5_proxy]
497
+ # SOCKS5 proxy - use appropriate socket based on target scheme
498
+ if datum[:ssl_uri_schemes].include?(datum[:scheme])
499
+ Excon::SOCKS5SSLSocket.new(datum)
500
+ else
501
+ Excon::SOCKS5Socket.new(datum)
502
+ end
486
503
  elsif datum[:ssl_uri_schemes].include?(datum[:scheme])
487
504
  Excon::SSLSocket.new(datum)
488
505
  else
@@ -60,6 +60,7 @@ module Excon
60
60
  read_timeout
61
61
  request_block
62
62
  resolv_resolver
63
+ resolver_factory
63
64
  response_block
64
65
  stubs
65
66
  timeout
@@ -97,6 +98,7 @@ module Excon
97
98
  proxy
98
99
  scheme
99
100
  socket
101
+ socks5_proxy
100
102
  ssl_ca_file
101
103
  ssl_ca_path
102
104
  ssl_cert_store
@@ -170,6 +172,7 @@ module Excon
170
172
  persistent: false,
171
173
  read_timeout: 60,
172
174
  resolv_resolver: nil,
175
+ resolver_factory: Excon::ResolverFactory,
173
176
  retry_errors: DEFAULT_RETRY_ERRORS,
174
177
  retry_limit: DEFAULT_RETRY_LIMIT,
175
178
  ssl_verify_peer: true,
data/lib/excon/error.rb CHANGED
@@ -48,6 +48,7 @@ or:
48
48
 
49
49
  class InvalidHeaderKey < Error; end
50
50
  class InvalidHeaderValue < Error; end
51
+ class InvalidParameter < Error; end
51
52
  class Timeout < Error; end
52
53
  class ResponseParse < Error; end
53
54
 
@@ -2,6 +2,19 @@
2
2
  module Excon
3
3
  module Middleware
4
4
  class RedirectFollower < Excon::Middleware::Base
5
+ REDACTION_HEADERS = %w[
6
+ Authorization
7
+ Cookie
8
+ Cookie2
9
+ Host
10
+ Proxy-Connection
11
+ Proxy-Authorization
12
+ X-API-Key
13
+ X-Auth-Token
14
+ X-CSRF-Token
15
+ X-Session-ID
16
+ ]
17
+
5
18
  def self.valid_parameter_keys
6
19
  [
7
20
  :redirects_remaining,
@@ -54,10 +67,9 @@ module Excon
54
67
  params[:headers].delete('Content-Length')
55
68
  end
56
69
  params[:headers] = datum[:headers].dup
57
- params[:headers].delete('Authorization')
58
- params[:headers].delete('Proxy-Connection')
59
- params[:headers].delete('Proxy-Authorization')
60
- params[:headers].delete('Host')
70
+ REDACTION_HEADERS.each do |header|
71
+ params[:headers].delete(header)
72
+ end
61
73
  params.merge!(
62
74
  :scheme => uri.scheme || datum[:scheme],
63
75
  :host => uri.host || datum[:host],
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Excon
3
+ # This factory produces new +resolv+ gem resolver instances. Users who wants
4
+ # to configure a custom resolver (varying settings for varying resolvers) can
5
+ # provide a custom resolver factory class and configure it globally on the
6
+ # Excon defaults:
7
+ #
8
+ # Excon.defaults[:resolver_factory] = MyCustomResolverFactory
9
+ #
10
+ # Then you just need to provide a static method called +.create_resolver+
11
+ # which returns a new +Resolv+ instance. This allows the customization.
12
+ class ResolverFactory
13
+ # @return [Resolv] the new resolver instance
14
+ def self.create_resolver
15
+ Resolv.new
16
+ end
17
+ end
18
+ end
data/lib/excon/socket.rb CHANGED
@@ -132,7 +132,7 @@ module Excon
132
132
  family = @data[:proxy][:family]
133
133
  end
134
134
 
135
- resolver = @data[:resolv_resolver] || Resolv.new
135
+ resolver = @data[:resolv_resolver] || @data[:resolver_factory].create_resolver
136
136
 
137
137
  # Deprecated
138
138
  if @data[:dns_timeouts]
@@ -252,15 +252,11 @@ module Excon
252
252
  end
253
253
  end
254
254
  end
255
- rescue OpenSSL::SSL::SSLError => error
256
- if error.message == 'read would block'
257
- if @read_buffer.empty?
258
- select_with_timeout(@socket, :read) && retry
259
- end
260
- else
261
- raise(error)
262
- end
263
- rescue *READ_RETRY_EXCEPTION_CLASSES
255
+ rescue OpenSSL::SSL::SSLError => e
256
+ raise(e) unless e.message == 'read would block'
257
+
258
+ select_with_timeout(@socket, :read) && retry if @read_buffer.empty?
259
+ rescue *READ_RETRY_EXCEPTION_CLASSES => e
264
260
  if @read_buffer.empty?
265
261
  # if we didn't read anything, try again...
266
262
  select_with_timeout(@socket, :read) && retry
@@ -299,12 +295,10 @@ module Excon
299
295
 
300
296
  def read_block(max_length)
301
297
  @socket.read(max_length)
302
- rescue OpenSSL::SSL::SSLError => error
303
- if error.message == 'read would block'
304
- select_with_timeout(@socket, :read) && retry
305
- else
306
- raise(error)
307
- end
298
+ rescue OpenSSL::SSL::SSLError => e
299
+ select_with_timeout(@socket, :read) && retry if e.message == 'read would block'
300
+
301
+ raise(error)
308
302
  rescue *READ_RETRY_EXCEPTION_CLASSES
309
303
  select_with_timeout(@socket, :read) && retry
310
304
  rescue EOFError
@@ -327,12 +321,10 @@ module Excon
327
321
  else
328
322
  raise error
329
323
  end
330
- rescue OpenSSL::SSL::SSLError, *WRITE_RETRY_EXCEPTION_CLASSES => error
331
- if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
332
- raise error
333
- else
334
- select_with_timeout(@socket, :write) && retry
335
- end
324
+ rescue OpenSSL::SSL::SSLError, *WRITE_RETRY_EXCEPTION_CLASSES => e
325
+ raise e if error.is_a?(OpenSSL::SSL::SSLError) && e.message != 'write would block'
326
+
327
+ select_with_timeout(@socket, :write) && retry
336
328
  end
337
329
 
338
330
  # Fast, common case.
@@ -348,12 +340,10 @@ module Excon
348
340
 
349
341
  def write_block(data)
350
342
  @socket.write(data)
351
- rescue OpenSSL::SSL::SSLError, *WRITE_RETRY_EXCEPTION_CLASSES => error
352
- if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
353
- raise error
354
- else
355
- select_with_timeout(@socket, :write) && retry
356
- end
343
+ rescue OpenSSL::SSL::SSLError, *WRITE_RETRY_EXCEPTION_CLASSES => e
344
+ raise e if e.is_a?(OpenSSL::SSL::SSLError) && e.message != 'write would block'
345
+
346
+ select_with_timeout(@socket, :write) && retry
357
347
  end
358
348
 
359
349
  def select_with_timeout(socket, type)
@@ -373,25 +363,19 @@ module Excon
373
363
  end
374
364
 
375
365
  select = case type
376
- when :connect_read
377
- IO.select([socket], nil, nil, timeout)
378
- when :connect_write
379
- IO.select(nil, [socket], nil, timeout)
380
- when :read
381
- IO.select([socket], nil, nil, timeout)
382
- when :write
383
- IO.select(nil, [socket], nil, timeout)
384
- end
366
+ when :connect_read, :read
367
+ IO.select([socket], nil, nil, timeout)
368
+ when :connect_write, :write
369
+ IO.select(nil, [socket], nil, timeout)
370
+ end
385
371
 
386
- select || raise(Excon::Errors::Timeout.new("#{timeout_kind} timeout reached"))
372
+ select || raise(Excon::Errors::Timeout.new, "#{timeout_kind} timeout reached")
387
373
  end
388
374
 
389
375
  def unpacked_sockaddr
390
376
  @unpacked_sockaddr ||= ::Socket.unpack_sockaddr_in(@socket.to_io.getsockname)
391
377
  rescue ArgumentError => e
392
- unless e.message == 'not an AF_INET/AF_INET6 sockaddr'
393
- raise
394
- end
378
+ raise unless e.message == 'not an AF_INET/AF_INET6 sockaddr'
395
379
  end
396
380
 
397
381
  # Returns the remaining time in seconds until we reach the deadline for the request timeout.
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Excon
4
+ # SOCKS5 protocol implementation (RFC 1928, RFC 1929)
5
+ # Shared module for SOCKS5Socket and SOCKS5SSLSocket
6
+ module SOCKS5
7
+ SOCKS5_VERSION = 0x05
8
+ SOCKS5_RESERVED = 0x00
9
+
10
+ # Authentication methods
11
+ SOCKS5_NO_AUTH = 0x00
12
+ SOCKS5_AUTH_USERNAME_PASSWORD = 0x02
13
+ SOCKS5_NO_ACCEPTABLE_AUTH = 0xFF
14
+
15
+ # Commands
16
+ SOCKS5_CMD_CONNECT = 0x01
17
+
18
+ # Address types
19
+ SOCKS5_ATYP_IPV4 = 0x01
20
+ SOCKS5_ATYP_DOMAIN = 0x03
21
+ SOCKS5_ATYP_IPV6 = 0x04
22
+
23
+ # Reply codes
24
+ SOCKS5_SUCCESS = 0x00
25
+ SOCKS5_ERRORS = {
26
+ 0x01 => 'General SOCKS server failure',
27
+ 0x02 => 'Connection not allowed by ruleset',
28
+ 0x03 => 'Network unreachable',
29
+ 0x04 => 'Host unreachable',
30
+ 0x05 => 'Connection refused',
31
+ 0x06 => 'TTL expired',
32
+ 0x07 => 'Command not supported',
33
+ 0x08 => 'Address type not supported'
34
+ }.freeze
35
+
36
+ # Maximum hostname length per RFC 1928
37
+ MAX_HOSTNAME_LENGTH = 255
38
+
39
+ private
40
+
41
+ # Parse SOCKS5 proxy string into components
42
+ # @param proxy_string [String] Proxy specification in various formats
43
+ # @return [Array<String, String, String, String>] host, port, user, pass
44
+ def parse_socks5_proxy(proxy_string)
45
+ # Support formats:
46
+ # host:port
47
+ # user:pass@host:port
48
+ # socks5://host:port
49
+ # socks5://user:pass@host:port
50
+ proxy_string = proxy_string.to_s.sub(%r{^socks5://}, '')
51
+
52
+ user = nil
53
+ pass = nil
54
+
55
+ if proxy_string.include?('@')
56
+ auth, host_port = proxy_string.split('@', 2)
57
+ user, pass = auth.split(':', 2)
58
+ else
59
+ host_port = proxy_string
60
+ end
61
+
62
+ host, port = host_port.split(':', 2)
63
+ port ||= '1080'
64
+
65
+ [host, port, user, pass]
66
+ end
67
+
68
+ # Perform SOCKS5 authentication handshake
69
+ def socks5_authenticate
70
+ auth_methods = if @proxy_user && @proxy_pass
71
+ [SOCKS5_NO_AUTH, SOCKS5_AUTH_USERNAME_PASSWORD]
72
+ else
73
+ [SOCKS5_NO_AUTH]
74
+ end
75
+
76
+ greeting = [SOCKS5_VERSION, auth_methods.length, *auth_methods].pack('C*')
77
+ @socket.write(greeting)
78
+
79
+ response = socks5_read_exactly(2)
80
+ version, chosen_method = response.unpack('CC')
81
+
82
+ if version != SOCKS5_VERSION
83
+ raise Excon::Error::Socket.new(Exception.new("SOCKS5 proxy returned invalid version: #{version}"))
84
+ end
85
+
86
+ case chosen_method
87
+ when SOCKS5_NO_AUTH
88
+ # No authentication required
89
+ when SOCKS5_AUTH_USERNAME_PASSWORD
90
+ unless @proxy_user && @proxy_pass
91
+ raise Excon::Error::Socket.new(Exception.new('SOCKS5 proxy requires authentication but no credentials provided'))
92
+ end
93
+ socks5_username_password_auth
94
+ when SOCKS5_NO_ACCEPTABLE_AUTH
95
+ raise Excon::Error::Socket.new(Exception.new('SOCKS5 proxy: no acceptable authentication methods'))
96
+ else
97
+ raise Excon::Error::Socket.new(Exception.new("SOCKS5 proxy: unsupported authentication method #{chosen_method}"))
98
+ end
99
+ end
100
+
101
+ # RFC 1929: Username/Password Authentication
102
+ def socks5_username_password_auth
103
+ auth_request = [
104
+ 0x01, # auth protocol version
105
+ @proxy_user.bytesize,
106
+ @proxy_user,
107
+ @proxy_pass.bytesize,
108
+ @proxy_pass
109
+ ].pack('CCA*CA*')
110
+
111
+ @socket.write(auth_request)
112
+
113
+ response = socks5_read_exactly(2)
114
+ _, status = response.unpack('CC')
115
+
116
+ unless status == 0x00
117
+ raise Excon::Error::Socket.new(Exception.new('SOCKS5 proxy authentication failed'))
118
+ end
119
+ end
120
+
121
+ # Request connection to target through SOCKS5 proxy
122
+ def socks5_connect(host, port)
123
+ if host.bytesize > MAX_HOSTNAME_LENGTH
124
+ raise Excon::Error::Socket.new(Exception.new("SOCKS5: hostname exceeds maximum length of #{MAX_HOSTNAME_LENGTH} bytes"))
125
+ end
126
+
127
+ # Build CONNECT request with domain name (let proxy resolve DNS)
128
+ request = [SOCKS5_VERSION, SOCKS5_CMD_CONNECT, SOCKS5_RESERVED].pack('CCC')
129
+ request += [SOCKS5_ATYP_DOMAIN, host.bytesize, host].pack('CCA*')
130
+ request += [port.to_i].pack('n')
131
+
132
+ @socket.write(request)
133
+
134
+ response = socks5_read_exactly(4)
135
+ version, reply, _, atyp = response.unpack('CCCC')
136
+
137
+ if version != SOCKS5_VERSION
138
+ raise Excon::Error::Socket.new(Exception.new("SOCKS5 proxy returned invalid version: #{version}"))
139
+ end
140
+
141
+ unless reply == SOCKS5_SUCCESS
142
+ error_msg = SOCKS5_ERRORS[reply] || "Unknown error (#{reply})"
143
+ raise Excon::Error::Socket.new(Exception.new("SOCKS5 proxy connect failed: #{error_msg}"))
144
+ end
145
+
146
+ # Read and discard bound address (not needed for CONNECT)
147
+ socks5_read_bound_address(atyp)
148
+ end
149
+
150
+ def socks5_read_bound_address(atyp)
151
+ case atyp
152
+ when SOCKS5_ATYP_IPV4
153
+ socks5_read_exactly(4 + 2) # 4 bytes IP + 2 bytes port
154
+ when SOCKS5_ATYP_DOMAIN
155
+ domain_len = socks5_read_exactly(1).unpack1('C')
156
+ socks5_read_exactly(domain_len + 2)
157
+ when SOCKS5_ATYP_IPV6
158
+ socks5_read_exactly(16 + 2) # 16 bytes IP + 2 bytes port
159
+ else
160
+ raise Excon::Error::Socket.new(Exception.new("SOCKS5 proxy returned unknown address type: #{atyp}"))
161
+ end
162
+ end
163
+
164
+ # Read exact number of bytes with timeout support
165
+ def socks5_read_exactly(nbytes)
166
+ data = ''.dup
167
+ deadline = @data[:read_timeout] ? Time.now + @data[:read_timeout] : nil
168
+
169
+ while data.bytesize < nbytes
170
+ if deadline
171
+ remaining = deadline - Time.now
172
+ if remaining <= 0
173
+ raise Excon::Error::Timeout.new('SOCKS5 read timeout')
174
+ end
175
+ ready = IO.select([@socket], nil, nil, remaining)
176
+ unless ready
177
+ raise Excon::Error::Timeout.new('SOCKS5 read timeout')
178
+ end
179
+ end
180
+
181
+ chunk = @socket.read_nonblock(nbytes - data.bytesize, exception: false)
182
+ case chunk
183
+ when :wait_readable
184
+ IO.select([@socket], nil, nil, deadline ? [deadline - Time.now, 0].max : nil)
185
+ when nil, ''
186
+ raise Excon::Error::Socket.new(Exception.new('SOCKS5 proxy connection closed unexpectedly'))
187
+ else
188
+ data << chunk
189
+ end
190
+ end
191
+ data
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Excon
4
+ class SOCKS5Socket < Socket
5
+ include SOCKS5
6
+
7
+ def initialize(data = {})
8
+ @socks5_proxy = data[:socks5_proxy]
9
+ @proxy_host, @proxy_port, @proxy_user, @proxy_pass = parse_socks5_proxy(@socks5_proxy)
10
+ super(data)
11
+ end
12
+
13
+ private
14
+
15
+ # Proxy-swap pattern: temporarily set @data[:proxy] to the SOCKS5 proxy
16
+ # so that Socket#connect routes the TCP connection there (inheriting DNS
17
+ # resolution, nonblock, retry, keepalive, reuseaddr, remote_ip tracking).
18
+ # After TCP is up, clear :proxy and run the SOCKS5 handshake.
19
+ def connect
20
+ @data[:proxy] = {
21
+ host: @proxy_host,
22
+ hostname: @proxy_host,
23
+ port: @proxy_port.to_i
24
+ }
25
+
26
+ begin
27
+ super
28
+ ensure
29
+ @data.delete(:proxy)
30
+ end
31
+
32
+ socks5_authenticate
33
+ socks5_connect(@data[:host], @data[:port])
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Excon
4
+ class SOCKS5SSLSocket < SSLSocket
5
+ include SOCKS5
6
+
7
+ def initialize(data = {})
8
+ @socks5_proxy = data[:socks5_proxy]
9
+ @proxy_host, @proxy_port, @proxy_user, @proxy_pass = parse_socks5_proxy(@socks5_proxy)
10
+ super(data)
11
+ end
12
+
13
+ private
14
+
15
+ # Proxy-swap pattern (same as SOCKS5Socket#connect).
16
+ #
17
+ # Call chain:
18
+ # SOCKS5SSLSocket#initialize -> super (SSLSocket#initialize)
19
+ # -> super (Socket#initialize) -> connect
20
+ # -> SOCKS5SSLSocket#connect (this method)
21
+ # -> super -> SSLSocket#connect -> Socket#connect (TCP to proxy)
22
+ # -> SOCKS5 handshake on raw TCP socket
23
+ # <- returns to SSLSocket#initialize
24
+ # -> @data[:proxy] is nil, so HTTP CONNECT is skipped (line 111)
25
+ # -> SSL wrapping on the SOCKS5-tunneled socket
26
+ def connect
27
+ @data[:proxy] = {
28
+ host: @proxy_host,
29
+ hostname: @proxy_host,
30
+ port: @proxy_port.to_i
31
+ }
32
+
33
+ begin
34
+ super
35
+ ensure
36
+ # Clear :proxy so SSLSocket#initialize skips HTTP CONNECT (line 111)
37
+ @data.delete(:proxy)
38
+ end
39
+
40
+ socks5_authenticate
41
+ socks5_connect(@data[:host], @data[:port])
42
+ end
43
+ end
44
+ end
@@ -10,7 +10,7 @@ module Excon
10
10
  open_process(RbConfig.ruby, '-S', 'rackup', '-s', 'webrick', '--host', host, '--port', port, app_str)
11
11
  process_stderr = ""
12
12
  line = ''
13
- until line.include?('HTTPServer#start')
13
+ until line.include?('Server#start')
14
14
  line = error.gets
15
15
  raise process_stderr if line.nil?
16
16
  process_stderr << line
data/lib/excon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Excon
4
- VERSION = '1.3.0'
4
+ VERSION = '1.5.0'
5
5
  end
data/lib/excon.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  $:.unshift(File.dirname(__FILE__)) unless
4
4
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
5
5
 
6
- require 'cgi'
6
+ require 'cgi/escape'
7
7
  require 'forwardable'
8
8
  require 'ipaddr'
9
9
  require 'openssl'
@@ -27,6 +27,7 @@ require 'excon/middlewares/mock'
27
27
  require 'excon/middlewares/response_parser'
28
28
 
29
29
  require 'excon/error'
30
+ require 'excon/resolver_factory'
30
31
  require 'excon/constants'
31
32
  require 'excon/utils'
32
33
 
@@ -35,10 +36,12 @@ require 'excon/headers'
35
36
  require 'excon/response'
36
37
  require 'excon/middlewares/escape_path'
37
38
  require 'excon/middlewares/redirect_follower'
38
- require 'excon/middlewares/capture_cookies'
39
39
  require 'excon/pretty_printer'
40
40
  require 'excon/socket'
41
41
  require 'excon/ssl_socket'
42
+ require 'excon/socks5'
43
+ require 'excon/socks5_socket'
44
+ require 'excon/socks5_ssl_socket'
42
45
  require 'excon/instrumentors/standard_instrumentor'
43
46
  require 'excon/instrumentors/logging_instrumentor'
44
47
  require 'excon/unix_socket'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: excon
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dpiddy (Dan Peterson)
@@ -231,7 +231,6 @@ files:
231
231
  - lib/excon/instrumentors/logging_instrumentor.rb
232
232
  - lib/excon/instrumentors/standard_instrumentor.rb
233
233
  - lib/excon/middlewares/base.rb
234
- - lib/excon/middlewares/capture_cookies.rb
235
234
  - lib/excon/middlewares/decompress.rb
236
235
  - lib/excon/middlewares/escape_path.rb
237
236
  - lib/excon/middlewares/expects.rb
@@ -241,8 +240,12 @@ files:
241
240
  - lib/excon/middlewares/redirect_follower.rb
242
241
  - lib/excon/middlewares/response_parser.rb
243
242
  - lib/excon/pretty_printer.rb
243
+ - lib/excon/resolver_factory.rb
244
244
  - lib/excon/response.rb
245
245
  - lib/excon/socket.rb
246
+ - lib/excon/socks5.rb
247
+ - lib/excon/socks5_socket.rb
248
+ - lib/excon/socks5_ssl_socket.rb
246
249
  - lib/excon/ssl_socket.rb
247
250
  - lib/excon/test/plugin/server/exec.rb
248
251
  - lib/excon/test/plugin/server/puma.rb
@@ -278,7 +281,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
278
281
  - !ruby/object:Gem::Version
279
282
  version: '0'
280
283
  requirements: []
281
- rubygems_version: 3.6.9
284
+ rubygems_version: 4.0.3
282
285
  specification_version: 4
283
286
  summary: speed, persistence, http(s)
284
287
  test_files: []
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
- module Excon
3
- module Middleware
4
- class CaptureCookies < Excon::Middleware::Base
5
-
6
- def extract_cookies_from_set_cookie(set_cookie)
7
- set_cookie.split(',').map { |full| full.split(';').first.strip }.join('; ')
8
- end
9
-
10
- def get_header(datum, header)
11
- _, header_value = datum[:response][:headers].detect do |key, _|
12
- key.casecmp?(header)
13
- end
14
- header_value
15
- end
16
-
17
- def response_call(datum)
18
- cookie = get_header(datum, 'Set-Cookie')
19
- if cookie
20
- cookie = extract_cookies_from_set_cookie(cookie)
21
- unless datum[:headers].key?("Cookie")
22
- datum[:headers]["Cookie"] = cookie
23
- else
24
- original_cookies = datum[:headers]["Cookie"]
25
- datum[:headers]["Cookie"] = original_cookies.empty? ? cookie : [original_cookies,cookie].join('; ')
26
- end
27
- end
28
- super(datum)
29
- end
30
- end
31
- end
32
- end