excon 0.109.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.
- checksums.yaml +4 -4
- data/README.md +21 -4
- data/data/cacert.pem +337 -914
- data/excon.gemspec +24 -17
- data/lib/excon/connection.rb +33 -11
- data/lib/excon/constants.rb +139 -126
- data/lib/excon/error.rb +3 -2
- data/lib/excon/middlewares/decompress.rb +17 -22
- data/lib/excon/middlewares/redirect_follower.rb +19 -7
- data/lib/excon/resolver_factory.rb +18 -0
- data/lib/excon/response.rb +4 -4
- data/lib/excon/socket.rb +32 -48
- data/lib/excon/socks5.rb +194 -0
- data/lib/excon/socks5_socket.rb +36 -0
- data/lib/excon/socks5_ssl_socket.rb +44 -0
- data/lib/excon/ssl_socket.rb +4 -3
- data/lib/excon/test/plugin/server/puma.rb +1 -1
- data/lib/excon/test/plugin/server/unicorn.rb +5 -5
- data/lib/excon/test/plugin/server/webrick.rb +1 -1
- data/lib/excon/test/server.rb +2 -7
- data/lib/excon/unix_socket.rb +1 -1
- data/lib/excon/utils.rb +39 -36
- data/lib/excon/version.rb +1 -1
- data/lib/excon.rb +45 -51
- metadata +52 -23
- data/lib/excon/middlewares/capture_cookies.rb +0 -32
data/excon.gemspec
CHANGED
|
@@ -1,44 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
|
|
2
4
|
require 'excon/version'
|
|
3
5
|
|
|
4
6
|
Gem::Specification.new do |s|
|
|
5
7
|
s.name = 'excon'
|
|
6
8
|
s.version = Excon::VERSION
|
|
7
|
-
s.summary =
|
|
8
|
-
s.description =
|
|
9
|
-
s.authors = [
|
|
9
|
+
s.summary = 'speed, persistence, http(s)'
|
|
10
|
+
s.description = 'EXtended http(s) CONnections'
|
|
11
|
+
s.authors = ['dpiddy (Dan Peterson)', 'geemus (Wesley Beary)', 'nextmat (Matt Sanders)']
|
|
10
12
|
s.email = 'geemus@gmail.com'
|
|
11
13
|
s.homepage = 'https://github.com/excon/excon'
|
|
12
14
|
s.license = 'MIT'
|
|
13
|
-
s.rdoc_options = [
|
|
15
|
+
s.rdoc_options = ['--charset=UTF-8']
|
|
14
16
|
s.extra_rdoc_files = %w[README.md CONTRIBUTORS.md CONTRIBUTING.md]
|
|
15
17
|
s.files = `git ls-files -- data/* lib/*`.split("\n") + [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
'CONTRIBUTING.md',
|
|
19
|
+
'CONTRIBUTORS.md',
|
|
20
|
+
'LICENSE.md',
|
|
21
|
+
'README.md',
|
|
22
|
+
'excon.gemspec'
|
|
21
23
|
]
|
|
24
|
+
s.required_ruby_version = '>= 3.1.0'
|
|
25
|
+
|
|
26
|
+
s.add_dependency 'logger'
|
|
22
27
|
|
|
23
|
-
s.add_development_dependency('rspec', '>= 3.5.0')
|
|
24
28
|
s.add_development_dependency('activesupport')
|
|
25
29
|
s.add_development_dependency('delorean')
|
|
26
30
|
s.add_development_dependency('eventmachine', '>= 1.0.4')
|
|
31
|
+
s.add_development_dependency('json', '>= 1.8.5')
|
|
27
32
|
s.add_development_dependency('open4')
|
|
33
|
+
s.add_development_dependency('puma')
|
|
28
34
|
s.add_development_dependency('rake')
|
|
35
|
+
s.add_development_dependency('rdoc')
|
|
36
|
+
s.add_development_dependency('rspec', '>= 3.5.0')
|
|
29
37
|
s.add_development_dependency('shindo')
|
|
30
38
|
s.add_development_dependency('sinatra')
|
|
31
39
|
s.add_development_dependency('sinatra-contrib')
|
|
32
|
-
s.add_development_dependency('json', '>= 1.8.5')
|
|
33
|
-
s.add_development_dependency('puma')
|
|
34
40
|
s.add_development_dependency('webrick')
|
|
35
41
|
|
|
36
42
|
s.metadata = {
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'changelog_uri' => 'https://github.com/excon/excon/blob/master/changelog.txt',
|
|
43
|
+
'bug_tracker_uri' => 'https://github.com/excon/excon/issues',
|
|
44
|
+
'changelog_uri' => 'https://github.com/excon/excon/blob/master/changelog.txt',
|
|
40
45
|
'documentation_uri' => 'https://github.com/excon/excon/blob/master/README.md',
|
|
41
|
-
'
|
|
42
|
-
'
|
|
46
|
+
'funding_uri' => 'https://github.com/sponsors/geemus',
|
|
47
|
+
'homepage_uri' => 'https://github.com/excon/excon',
|
|
48
|
+
'source_code_uri' => 'https://github.com/excon/excon',
|
|
49
|
+
'wiki_uri' => 'https://github.com/excon/excon/wiki'
|
|
43
50
|
}
|
|
44
51
|
end
|
data/lib/excon/connection.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require 'ipaddr'
|
|
3
2
|
|
|
4
3
|
module Excon
|
|
5
4
|
class Connection
|
|
@@ -61,6 +60,8 @@ module Excon
|
|
|
61
60
|
# @option params [Fixnum] :retry_interval Set how long to wait between retries. (Default 0)
|
|
62
61
|
# @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
|
|
63
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'
|
|
64
65
|
def initialize(params = {})
|
|
65
66
|
@pid = Process.pid
|
|
66
67
|
@data = Excon.defaults.dup
|
|
@@ -101,6 +102,10 @@ module Excon
|
|
|
101
102
|
@socket_key = "#{@data[:scheme]}://#{@data[:socket]}"
|
|
102
103
|
end
|
|
103
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
|
|
104
109
|
@socket_key = "#{@data[:scheme]}://#{@data[:host]}#{port_string(@data)}"
|
|
105
110
|
end
|
|
106
111
|
reset
|
|
@@ -139,7 +144,7 @@ module Excon
|
|
|
139
144
|
|
|
140
145
|
# The HTTP spec isn't clear on it, but specifically, GET requests don't usually send bodies;
|
|
141
146
|
# if they don't, sending Content-Length:0 can cause issues.
|
|
142
|
-
unless datum[:method].to_s.casecmp('GET')
|
|
147
|
+
unless datum[:method].to_s.casecmp?('GET') && body.nil?
|
|
143
148
|
unless datum[:headers].has_key?('Content-Length')
|
|
144
149
|
datum[:headers]['Content-Length'] = detect_content_length(body)
|
|
145
150
|
end
|
|
@@ -160,7 +165,7 @@ module Excon
|
|
|
160
165
|
if chunk.length > 0
|
|
161
166
|
socket(datum).write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
|
|
162
167
|
else
|
|
163
|
-
socket(datum).write(
|
|
168
|
+
socket(datum).write("0#{CR_NL}#{CR_NL}")
|
|
164
169
|
break
|
|
165
170
|
end
|
|
166
171
|
end
|
|
@@ -195,7 +200,7 @@ module Excon
|
|
|
195
200
|
raise(error)
|
|
196
201
|
when Errno::EPIPE
|
|
197
202
|
# Read whatever remains in the pipe to aid in debugging
|
|
198
|
-
response = socket.read
|
|
203
|
+
response = socket.read rescue ""
|
|
199
204
|
error = Excon::Error.new(response + error.message)
|
|
200
205
|
raise_socket_error(error)
|
|
201
206
|
else
|
|
@@ -251,10 +256,14 @@ module Excon
|
|
|
251
256
|
datum[:headers]['Authorization'] ||= 'Basic ' + ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
|
|
252
257
|
end
|
|
253
258
|
|
|
254
|
-
host_key = datum[:headers].keys.detect {|k| k.casecmp('Host')
|
|
259
|
+
host_key = datum[:headers].keys.detect {|k| k.casecmp?('Host') } || 'Host'
|
|
255
260
|
if datum[:scheme] == UNIX
|
|
256
261
|
datum[:headers][host_key] ||= ''
|
|
257
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
|
|
258
267
|
datum[:headers][host_key] ||= datum[:host] + port_string(datum)
|
|
259
268
|
end
|
|
260
269
|
|
|
@@ -299,8 +308,8 @@ module Excon
|
|
|
299
308
|
@persistent_socket_reusable = true
|
|
300
309
|
|
|
301
310
|
if datum[:persistent]
|
|
302
|
-
if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection')
|
|
303
|
-
if datum[:response][:headers][key].casecmp('close')
|
|
311
|
+
if (key = datum[:response][:headers].keys.detect {|k| k.casecmp?('Connection') })
|
|
312
|
+
if datum[:response][:headers][key].casecmp?('close')
|
|
304
313
|
reset
|
|
305
314
|
end
|
|
306
315
|
end
|
|
@@ -330,7 +339,7 @@ module Excon
|
|
|
330
339
|
# @param pipeline_params [Array<Hash>] An array of one or more optional params, override defaults set in Connection.new, see #request for details
|
|
331
340
|
def requests(pipeline_params)
|
|
332
341
|
pipeline_params.each {|params| params.merge!(:pipeline => true, :persistent => true) }
|
|
333
|
-
pipeline_params.last
|
|
342
|
+
pipeline_params.last[:persistent] = @data[:persistent]
|
|
334
343
|
|
|
335
344
|
responses = pipeline_params.map do |params|
|
|
336
345
|
request(params)
|
|
@@ -339,8 +348,8 @@ module Excon
|
|
|
339
348
|
end
|
|
340
349
|
|
|
341
350
|
if @data[:persistent]
|
|
342
|
-
if (key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection')
|
|
343
|
-
if responses.last[:headers][key].casecmp('close')
|
|
351
|
+
if (key = responses.last[:headers].keys.detect {|k| k.casecmp?('Connection') })
|
|
352
|
+
if responses.last[:headers][key].casecmp?('close')
|
|
344
353
|
reset
|
|
345
354
|
end
|
|
346
355
|
end
|
|
@@ -447,6 +456,12 @@ module Excon
|
|
|
447
456
|
raise ArgumentError.new("Invalid validation type '#{validation}'")
|
|
448
457
|
end
|
|
449
458
|
|
|
459
|
+
if validation == :connection && params[:omit_default_port] != true
|
|
460
|
+
Excon.display_warning(
|
|
461
|
+
'The `omit_default_port` connection option is deprecated, please use `include_default_port` instead.'
|
|
462
|
+
)
|
|
463
|
+
end
|
|
464
|
+
|
|
450
465
|
invalid_keys = params.keys - valid_keys
|
|
451
466
|
unless invalid_keys.empty?
|
|
452
467
|
Excon.display_warning("Invalid Excon #{validation} keys: #{invalid_keys.map(&:inspect).join(', ')}")
|
|
@@ -478,6 +493,13 @@ module Excon
|
|
|
478
493
|
unix_proxy = datum[:proxy] ? datum[:proxy][:scheme] == UNIX : false
|
|
479
494
|
sockets[@socket_key] ||= if datum[:scheme] == UNIX || unix_proxy
|
|
480
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
|
|
481
503
|
elsif datum[:ssl_uri_schemes].include?(datum[:scheme])
|
|
482
504
|
Excon::SSLSocket.new(datum)
|
|
483
505
|
else
|
|
@@ -506,7 +528,7 @@ module Excon
|
|
|
506
528
|
end
|
|
507
529
|
|
|
508
530
|
def raise_socket_error(error)
|
|
509
|
-
if error.message
|
|
531
|
+
if error.message.include?('certificate verify failed')
|
|
510
532
|
raise(Excon::Errors::CertificateError.new(error))
|
|
511
533
|
else
|
|
512
534
|
raise(Excon::Errors::SocketError.new(error))
|
data/lib/excon/constants.rb
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module Excon
|
|
3
|
-
|
|
4
3
|
CR_NL = "\r\n"
|
|
5
4
|
|
|
6
|
-
DEFAULT_CA_FILE = File.expand_path(File.join(File.dirname(__FILE__),
|
|
5
|
+
DEFAULT_CA_FILE = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data', 'cacert.pem'))
|
|
7
6
|
|
|
8
|
-
DEFAULT_CHUNK_SIZE =
|
|
7
|
+
DEFAULT_CHUNK_SIZE = 1_048_576 # 1 megabyte
|
|
9
8
|
|
|
10
9
|
# avoid overwrite if somebody has redefined
|
|
11
|
-
unless const_defined?(:CHUNK_SIZE)
|
|
12
|
-
CHUNK_SIZE = DEFAULT_CHUNK_SIZE
|
|
13
|
-
end
|
|
10
|
+
CHUNK_SIZE = DEFAULT_CHUNK_SIZE unless const_defined?(:CHUNK_SIZE)
|
|
14
11
|
|
|
15
12
|
DEFAULT_REDIRECT_LIMIT = 10
|
|
16
13
|
|
|
17
14
|
DEFAULT_RETRY_LIMIT = 4
|
|
18
15
|
|
|
19
16
|
DEFAULT_RETRY_ERRORS = [
|
|
20
|
-
Excon::Error::
|
|
17
|
+
Excon::Error::RequestTimeout,
|
|
18
|
+
Excon::Error::Server,
|
|
21
19
|
Excon::Error::Socket,
|
|
22
|
-
Excon::Error::
|
|
23
|
-
|
|
20
|
+
Excon::Error::Timeout,
|
|
21
|
+
Excon::Error::TooManyRequests
|
|
22
|
+
].freeze
|
|
24
23
|
|
|
25
24
|
FORCE_ENC = CR_NL.respond_to?(:force_encoding)
|
|
26
25
|
|
|
27
26
|
HTTP_1_1 = " HTTP/1.1\r\n"
|
|
28
27
|
|
|
29
|
-
HTTP_VERBS = %w
|
|
28
|
+
HTTP_VERBS = %w[connect delete get head options patch post put trace].freeze
|
|
30
29
|
|
|
31
30
|
HTTPS = 'https'
|
|
32
31
|
|
|
@@ -36,93 +35,99 @@ module Excon
|
|
|
36
35
|
|
|
37
36
|
UNIX = 'unix'
|
|
38
37
|
|
|
39
|
-
USER_AGENT = "excon/#{VERSION}"
|
|
40
|
-
|
|
41
|
-
VERSIONS = "#{USER_AGENT} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
38
|
+
USER_AGENT = "excon/#{VERSION}".freeze
|
|
39
|
+
|
|
40
|
+
VERSIONS = "#{USER_AGENT} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}".freeze
|
|
41
|
+
|
|
42
|
+
# FIXME: should be frozen, but with a way to change them, similar to defaults
|
|
43
|
+
VALID_REQUEST_KEYS = %i[
|
|
44
|
+
allow_unstubbed_requests
|
|
45
|
+
body
|
|
46
|
+
chunk_size
|
|
47
|
+
debug_request
|
|
48
|
+
debug_response
|
|
49
|
+
dns_timeouts
|
|
50
|
+
headers
|
|
51
|
+
instrumentor
|
|
52
|
+
logger
|
|
53
|
+
method
|
|
54
|
+
middlewares
|
|
55
|
+
password
|
|
56
|
+
path
|
|
57
|
+
persistent
|
|
58
|
+
pipeline
|
|
59
|
+
query
|
|
60
|
+
read_timeout
|
|
61
|
+
request_block
|
|
62
|
+
resolv_resolver
|
|
63
|
+
resolver_factory
|
|
64
|
+
response_block
|
|
65
|
+
stubs
|
|
66
|
+
timeout
|
|
67
|
+
user
|
|
68
|
+
versions
|
|
69
|
+
write_timeout
|
|
69
70
|
]
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
72
|
+
# FIXME: should be frozen, but with a way to change them, similar to defaults
|
|
73
|
+
VALID_CONNECTION_KEYS = (VALID_REQUEST_KEYS + %i[
|
|
74
|
+
ciphers
|
|
75
|
+
client_key
|
|
76
|
+
client_key_data
|
|
77
|
+
client_key_pass
|
|
78
|
+
client_cert
|
|
79
|
+
client_cert_data
|
|
80
|
+
client_chain
|
|
81
|
+
client_chain_data
|
|
82
|
+
certificate
|
|
83
|
+
certificate_path
|
|
84
|
+
disable_proxy
|
|
85
|
+
private_key
|
|
86
|
+
private_key_path
|
|
87
|
+
connect_timeout
|
|
88
|
+
family
|
|
89
|
+
keepalive
|
|
90
|
+
host
|
|
91
|
+
hostname
|
|
92
|
+
ignore_unexpected_eof
|
|
93
|
+
include_default_port
|
|
94
|
+
omit_default_port
|
|
95
|
+
nonblock
|
|
96
|
+
reuseaddr
|
|
97
|
+
port
|
|
98
|
+
proxy
|
|
99
|
+
scheme
|
|
100
|
+
socket
|
|
101
|
+
socks5_proxy
|
|
102
|
+
ssl_ca_file
|
|
103
|
+
ssl_ca_path
|
|
104
|
+
ssl_cert_store
|
|
105
|
+
ssl_verify_callback
|
|
106
|
+
ssl_verify_peer
|
|
107
|
+
ssl_verify_peer_host
|
|
108
|
+
ssl_verify_hostname
|
|
109
|
+
ssl_version
|
|
110
|
+
ssl_min_version
|
|
111
|
+
ssl_max_version
|
|
112
|
+
ssl_security_level
|
|
113
|
+
ssl_proxy_headers
|
|
114
|
+
ssl_uri_schemes
|
|
115
|
+
tcp_nodelay
|
|
116
|
+
thread_safe_sockets
|
|
117
|
+
uri_parser
|
|
118
|
+
])
|
|
114
119
|
|
|
115
120
|
DEPRECATED_VALID_REQUEST_KEYS = {
|
|
116
|
-
:
|
|
117
|
-
:
|
|
118
|
-
:
|
|
119
|
-
:
|
|
120
|
-
:
|
|
121
|
-
:
|
|
122
|
-
:
|
|
123
|
-
:
|
|
124
|
-
:
|
|
125
|
-
}
|
|
121
|
+
captures: 'Mock',
|
|
122
|
+
expects: 'Expects',
|
|
123
|
+
idempotent: 'Idempotent',
|
|
124
|
+
instrumentor_name: 'Instrumentor',
|
|
125
|
+
mock: 'Mock',
|
|
126
|
+
retries_remaining: 'Idempotent', # referenced in Instrumentor, but only relevant with Idempotent
|
|
127
|
+
retry_errors: 'Idempotent',
|
|
128
|
+
retry_interval: 'Idempotent',
|
|
129
|
+
retry_limit: 'Idempotent' # referenced in Instrumentor, but only relevant with Idempotent
|
|
130
|
+
}.freeze
|
|
126
131
|
|
|
127
132
|
unless ::IO.const_defined?(:WaitReadable)
|
|
128
133
|
class ::IO
|
|
@@ -135,47 +140,55 @@ module Excon
|
|
|
135
140
|
module WaitWritable; end
|
|
136
141
|
end
|
|
137
142
|
end
|
|
143
|
+
|
|
138
144
|
# these come last as they rely on the above
|
|
139
|
-
|
|
140
|
-
:
|
|
145
|
+
base_defaults = {
|
|
146
|
+
chunk_size: CHUNK_SIZE || DEFAULT_CHUNK_SIZE,
|
|
141
147
|
# see https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
|
|
142
148
|
# list provided then had DES related things sorted to the end
|
|
143
|
-
:
|
|
144
|
-
:
|
|
145
|
-
:
|
|
146
|
-
:
|
|
147
|
-
:
|
|
148
|
-
|
|
149
|
-
:headers => {
|
|
149
|
+
ciphers: 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:DES-CBC3-SHA:!DSS',
|
|
150
|
+
connect_timeout: 60,
|
|
151
|
+
debug_request: false,
|
|
152
|
+
debug_response: false,
|
|
153
|
+
dns_timeouts: nil, # nil allows Resolv::DNS default timeout value (see https://ruby-doc.org/3.2.2/stdlibs/resolv/Resolv/DNS.html#method-i-timeouts-3D)
|
|
154
|
+
headers: {
|
|
150
155
|
'User-Agent' => USER_AGENT,
|
|
151
|
-
'Accept'
|
|
152
|
-
},
|
|
153
|
-
:
|
|
154
|
-
:
|
|
155
|
-
:
|
|
156
|
+
'Accept' => '*/*'
|
|
157
|
+
}.freeze,
|
|
158
|
+
idempotent: false,
|
|
159
|
+
instrumentor_name: 'excon',
|
|
160
|
+
middlewares: [
|
|
156
161
|
Excon::Middleware::ResponseParser,
|
|
162
|
+
Excon::Middleware::Decompress,
|
|
157
163
|
Excon::Middleware::Expects,
|
|
158
164
|
Excon::Middleware::Idempotent,
|
|
159
165
|
Excon::Middleware::Instrumentor,
|
|
160
166
|
Excon::Middleware::Mock
|
|
161
167
|
],
|
|
162
|
-
:
|
|
163
|
-
:
|
|
164
|
-
:
|
|
165
|
-
:
|
|
166
|
-
:
|
|
167
|
-
:
|
|
168
|
-
:
|
|
169
|
-
:
|
|
170
|
-
:
|
|
171
|
-
:
|
|
172
|
-
:
|
|
173
|
-
:
|
|
174
|
-
:
|
|
175
|
-
:
|
|
176
|
-
:
|
|
177
|
-
:
|
|
178
|
-
:
|
|
179
|
-
|
|
180
|
-
|
|
168
|
+
mock: false,
|
|
169
|
+
include_default_port: false,
|
|
170
|
+
nonblock: true,
|
|
171
|
+
omit_default_port: true,
|
|
172
|
+
persistent: false,
|
|
173
|
+
read_timeout: 60,
|
|
174
|
+
resolv_resolver: nil,
|
|
175
|
+
resolver_factory: Excon::ResolverFactory,
|
|
176
|
+
retry_errors: DEFAULT_RETRY_ERRORS,
|
|
177
|
+
retry_limit: DEFAULT_RETRY_LIMIT,
|
|
178
|
+
ssl_verify_peer: true,
|
|
179
|
+
ssl_uri_schemes: [HTTPS].freeze,
|
|
180
|
+
stubs: :global,
|
|
181
|
+
tcp_nodelay: false,
|
|
182
|
+
thread_safe_sockets: true,
|
|
183
|
+
timeout: nil,
|
|
184
|
+
uri_parser: URI,
|
|
185
|
+
versions: VERSIONS,
|
|
186
|
+
write_timeout: 60
|
|
187
|
+
}.freeze
|
|
188
|
+
|
|
189
|
+
DEFAULTS = if defined? TEST_SUITE_DEFAULTS
|
|
190
|
+
base_defaults.merge(TEST_SUITE_DEFAULTS).freeze
|
|
191
|
+
else
|
|
192
|
+
base_defaults.freeze
|
|
193
|
+
end
|
|
181
194
|
end
|
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
|
|
|
@@ -216,8 +217,8 @@ or:
|
|
|
216
217
|
|
|
217
218
|
klasses.each do |klass|
|
|
218
219
|
class_name = klass.to_s
|
|
219
|
-
unless class_name
|
|
220
|
-
class_name = klass.to_s + 'Error' if class_name
|
|
220
|
+
unless class_name.match?(/Error\Z/)
|
|
221
|
+
class_name = klass.to_s + 'Error' if class_name.match?(legacy_re)
|
|
221
222
|
end
|
|
222
223
|
Excon::Errors.const_set(class_name, Excon::Error.const_get(klass))
|
|
223
224
|
end
|
|
@@ -1,41 +1,36 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Excon
|
|
3
4
|
module Middleware
|
|
4
5
|
class Decompress < Excon::Middleware::Base
|
|
5
|
-
|
|
6
6
|
INFLATE_ZLIB_OR_GZIP = 47 # Zlib::MAX_WBITS + 32
|
|
7
7
|
INFLATE_RAW = -15 # Zlib::MAX_WBITS * -1
|
|
8
8
|
|
|
9
9
|
def request_call(datum)
|
|
10
|
-
unless datum.
|
|
11
|
-
key = datum[:headers].keys.detect {|k| k.to_s.casecmp('Accept-Encoding')
|
|
12
|
-
if datum[:headers][key].to_s.empty?
|
|
13
|
-
datum[:headers][key] = 'deflate, gzip'
|
|
14
|
-
end
|
|
10
|
+
unless datum.key?(:response_block)
|
|
11
|
+
key = datum[:headers].keys.detect { |k| k.to_s.casecmp?('Accept-Encoding') } || 'Accept-Encoding'
|
|
12
|
+
datum[:headers][key] = 'deflate, gzip' if datum[:headers][key].to_s.empty?
|
|
15
13
|
end
|
|
16
14
|
@stack.request_call(datum)
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
def response_call(datum)
|
|
20
18
|
body = datum[:response][:body]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Zlib::Inflate.new(INFLATE_RAW).inflate(body)
|
|
30
|
-
end
|
|
31
|
-
encodings.pop
|
|
32
|
-
elsif encoding.casecmp('gzip') == 0 || encoding.casecmp('x-gzip') == 0
|
|
33
|
-
datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(body)).read
|
|
34
|
-
encodings.pop
|
|
35
|
-
end
|
|
36
|
-
datum[:response][:headers][key] = encodings.join(', ')
|
|
19
|
+
if !(datum.key?(:response_block) || body.nil? || body.empty?) &&
|
|
20
|
+
(key = datum[:response][:headers].keys.detect { |k| k.casecmp?('Content-Encoding') })
|
|
21
|
+
encodings = Utils.split_header_value(datum[:response][:headers][key])
|
|
22
|
+
if encodings&.last&.casecmp?('deflate')
|
|
23
|
+
datum[:response][:body] = begin
|
|
24
|
+
Zlib::Inflate.new(INFLATE_ZLIB_OR_GZIP).inflate(body)
|
|
25
|
+
rescue Zlib::DataError # fallback to raw on error
|
|
26
|
+
Zlib::Inflate.new(INFLATE_RAW).inflate(body)
|
|
37
27
|
end
|
|
28
|
+
encodings.pop
|
|
29
|
+
elsif encodings&.last&.casecmp?('gzip') || encodings&.last&.casecmp?('x-gzip')
|
|
30
|
+
datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(body)).read
|
|
31
|
+
encodings.pop
|
|
38
32
|
end
|
|
33
|
+
datum[:response][:headers][key] = encodings.join(', ')
|
|
39
34
|
end
|
|
40
35
|
@stack.response_call(datum)
|
|
41
36
|
end
|
|
@@ -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,
|
|
@@ -17,7 +30,7 @@ module Excon
|
|
|
17
30
|
|
|
18
31
|
def get_header(datum, header)
|
|
19
32
|
_, header_value = datum[:response][:headers].detect do |key, _|
|
|
20
|
-
key.casecmp(header)
|
|
33
|
+
key.casecmp?(header)
|
|
21
34
|
end
|
|
22
35
|
header_value
|
|
23
36
|
end
|
|
@@ -54,10 +67,9 @@ module Excon
|
|
|
54
67
|
params[:headers].delete('Content-Length')
|
|
55
68
|
end
|
|
56
69
|
params[:headers] = datum[:headers].dup
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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],
|
|
@@ -67,8 +79,8 @@ module Excon
|
|
|
67
79
|
:query => uri.query
|
|
68
80
|
)
|
|
69
81
|
|
|
70
|
-
params
|
|
71
|
-
params
|
|
82
|
+
params[:user] = Utils.unescape_uri(uri.user) if uri.user
|
|
83
|
+
params[:password] = Utils.unescape_uri(uri.password) if uri.password
|
|
72
84
|
|
|
73
85
|
response = Excon::Connection.new(params).request
|
|
74
86
|
datum.merge!({
|