httpx 0.19.7 → 0.20.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_19_8.md +5 -0
  3. data/doc/release_notes/0_20_0.md +36 -0
  4. data/doc/release_notes/0_20_1.md +5 -0
  5. data/lib/httpx/adapters/datadog.rb +112 -58
  6. data/lib/httpx/adapters/sentry.rb +102 -0
  7. data/lib/httpx/connection.rb +10 -2
  8. data/lib/httpx/io/ssl.rb +8 -3
  9. data/lib/httpx/options.rb +5 -0
  10. data/lib/httpx/plugins/authentication/basic.rb +24 -0
  11. data/lib/httpx/plugins/authentication/digest.rb +102 -0
  12. data/lib/httpx/plugins/authentication/ntlm.rb +37 -0
  13. data/lib/httpx/plugins/authentication/socks5.rb +24 -0
  14. data/lib/httpx/plugins/basic_authentication.rb +6 -6
  15. data/lib/httpx/plugins/digest_authentication.rb +15 -111
  16. data/lib/httpx/plugins/follow_redirects.rb +17 -5
  17. data/lib/httpx/plugins/ntlm_authentication.rb +8 -18
  18. data/lib/httpx/plugins/proxy/http.rb +76 -13
  19. data/lib/httpx/plugins/proxy/socks5.rb +6 -4
  20. data/lib/httpx/plugins/proxy.rb +30 -8
  21. data/lib/httpx/plugins/response_cache/store.rb +1 -0
  22. data/lib/httpx/pool.rb +4 -4
  23. data/lib/httpx/request.rb +3 -1
  24. data/lib/httpx/session.rb +7 -2
  25. data/lib/httpx/version.rb +1 -1
  26. data/sig/chainable.rbs +4 -4
  27. data/sig/plugins/authentication/basic.rbs +19 -0
  28. data/sig/plugins/authentication/digest.rbs +24 -0
  29. data/sig/plugins/authentication/ntlm.rbs +20 -0
  30. data/sig/plugins/authentication/socks5.rbs +18 -0
  31. data/sig/plugins/basic_authentication.rbs +2 -2
  32. data/sig/plugins/digest_authentication.rbs +3 -13
  33. data/sig/plugins/ntlm_authentication.rbs +3 -8
  34. data/sig/plugins/proxy/http.rbs +13 -3
  35. data/sig/plugins/proxy.rbs +5 -3
  36. data/sig/pool.rbs +2 -2
  37. data/sig/selector.rbs +1 -1
  38. data/sig/session.rbs +1 -1
  39. metadata +17 -2
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module HTTPX
6
+ module Plugins
7
+ module Authentication
8
+ class Socks5
9
+ def initialize(user, password, **)
10
+ @user = user
11
+ @password = password
12
+ end
13
+
14
+ def can_authenticate?(*)
15
+ @user && @password
16
+ end
17
+
18
+ def authenticate(*)
19
+ [0x01, @user.bytesize, @user, @password.bytesize, @password].pack("CCA*CA*")
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -7,10 +7,10 @@ module HTTPX
7
7
  #
8
8
  # https://gitlab.com/honeyryderchuck/httpx/wikis/Authentication#basic-authentication
9
9
  #
10
- module BasicAuthentication
10
+ module BasicAuth
11
11
  class << self
12
12
  def load_dependencies(_klass)
13
- require "base64"
13
+ require_relative "authentication/basic"
14
14
  end
15
15
 
16
16
  def configure(klass)
@@ -19,12 +19,12 @@ module HTTPX
19
19
  end
20
20
 
21
21
  module InstanceMethods
22
- def basic_authentication(user, password)
23
- authentication("Basic #{Base64.strict_encode64("#{user}:#{password}")}")
22
+ def basic_auth(user, password)
23
+ authentication(Authentication::Basic.new(user, password).authenticate)
24
24
  end
25
- alias_method :basic_auth, :basic_authentication
25
+ alias_method :basic_authentication, :basic_auth
26
26
  end
27
27
  end
28
- register_plugin :basic_authentication, BasicAuthentication
28
+ register_plugin :basic_authentication, BasicAuth
29
29
  end
30
30
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
4
-
5
3
  module HTTPX
6
4
  module Plugins
7
5
  #
@@ -9,9 +7,7 @@ module HTTPX
9
7
  #
10
8
  # https://gitlab.com/honeyryderchuck/httpx/wikis/Authentication#authentication
11
9
  #
12
- module DigestAuthentication
13
- using RegexpExtensions unless Regexp.method_defined?(:match?)
14
-
10
+ module DigestAuth
15
11
  DigestError = Class.new(Error)
16
12
 
17
13
  class << self
@@ -20,14 +16,13 @@ module HTTPX
20
16
  end
21
17
 
22
18
  def load_dependencies(*)
23
- require "securerandom"
24
- require "digest"
19
+ require_relative "authentication/digest"
25
20
  end
26
21
  end
27
22
 
28
23
  module OptionsMethods
29
24
  def option_digest(value)
30
- raise TypeError, ":digest must be a Digest" unless value.is_a?(Digest)
25
+ raise TypeError, ":digest must be a Digest" unless value.is_a?(Authentication::Digest)
31
26
 
32
27
  value
33
28
  end
@@ -35,7 +30,7 @@ module HTTPX
35
30
 
36
31
  module InstanceMethods
37
32
  def digest_authentication(user, password)
38
- with(digest: Digest.new(user, password))
33
+ with(digest: Authentication::Digest.new(user, password))
39
34
  end
40
35
 
41
36
  alias_method :digest_auth, :digest_authentication
@@ -44,116 +39,25 @@ module HTTPX
44
39
  requests.flat_map do |request|
45
40
  digest = request.options.digest
46
41
 
47
- if digest
48
- probe_response = wrap { super(request).first }
49
-
50
- if digest && !probe_response.is_a?(ErrorResponse) &&
51
- probe_response.status == 401 && probe_response.headers.key?("www-authenticate") &&
52
- /Digest .*/.match?(probe_response.headers["www-authenticate"])
53
-
54
- request.transition(:idle)
55
-
56
- token = digest.generate_header(request, probe_response)
57
- request.headers["authorization"] = "Digest #{token}"
58
-
59
- super(request)
60
- else
61
- probe_response
62
- end
63
- else
42
+ unless digest
64
43
  super(request)
44
+ next
65
45
  end
66
- end
67
- end
68
- end
69
-
70
- class Digest
71
- def initialize(user, password)
72
- @user = user
73
- @password = password
74
- @nonce = 0
75
- end
76
-
77
- def generate_header(request, response, _iis = false)
78
- meth = request.verb.to_s.upcase
79
- www = response.headers["www-authenticate"]
80
-
81
- # discard first token, it's Digest
82
- auth_info = www[/^(\w+) (.*)/, 2]
83
-
84
- uri = request.path
85
-
86
- params = Hash[auth_info.split(/ *, */)
87
- .map { |val| val.split("=") }
88
- .map { |k, v| [k, v.delete("\"")] }]
89
- nonce = params["nonce"]
90
- nc = next_nonce
91
46
 
92
- # verify qop
93
- qop = params["qop"]
47
+ probe_response = wrap { super(request).first }
94
48
 
95
- if params["algorithm"] =~ /(.*?)(-sess)?$/
96
- alg = Regexp.last_match(1)
97
- algorithm = ::Digest.const_get(alg)
98
- raise DigestError, "unknown algorithm \"#{alg}\"" unless algorithm
99
-
100
- sess = Regexp.last_match(2)
101
- params.delete("algorithm")
102
- else
103
- algorithm = ::Digest::MD5
104
- end
105
-
106
- if qop || sess
107
- cnonce = make_cnonce
108
- nc = format("%<nonce>08x", nonce: nc)
109
- end
110
-
111
- a1 = if sess
112
- [algorithm.hexdigest("#{@user}:#{params["realm"]}:#{@password}"),
113
- nonce,
114
- cnonce].join ":"
115
- else
116
- "#{@user}:#{params["realm"]}:#{@password}"
49
+ if probe_response.status == 401 && digest.can_authenticate?(probe_response.headers["www-authenticate"])
50
+ request.transition(:idle)
51
+ request.headers["authorization"] = digest.authenticate(request, probe_response.headers["www-authenticate"])
52
+ super(request)
53
+ else
54
+ probe_response
55
+ end
117
56
  end
118
-
119
- ha1 = algorithm.hexdigest(a1)
120
- ha2 = algorithm.hexdigest("#{meth}:#{uri}")
121
- request_digest = [ha1, nonce]
122
- request_digest.push(nc, cnonce, qop) if qop
123
- request_digest << ha2
124
- request_digest = request_digest.join(":")
125
-
126
- header = [
127
- %(username="#{@user}"),
128
- %(nonce="#{nonce}"),
129
- %(uri="#{uri}"),
130
- %(response="#{algorithm.hexdigest(request_digest)}"),
131
- ]
132
- header << %(realm="#{params["realm"]}") if params.key?("realm")
133
- header << %(algorithm=#{params["algorithm"]}") if params.key?("algorithm")
134
- header << %(opaque="#{params["opaque"]}") if params.key?("opaque")
135
- header << %(cnonce="#{cnonce}") if cnonce
136
- header << %(nc=#{nc})
137
- header << %(qop=#{qop}) if qop
138
- header.join ", "
139
- end
140
-
141
- private
142
-
143
- def make_cnonce
144
- ::Digest::MD5.hexdigest [
145
- Time.now.to_i,
146
- Process.pid,
147
- SecureRandom.random_number(2**32),
148
- ].join ":"
149
- end
150
-
151
- def next_nonce
152
- @nonce += 1
153
57
  end
154
58
  end
155
59
  end
156
60
 
157
- register_plugin :digest_authentication, DigestAuthentication
61
+ register_plugin :digest_authentication, DigestAuth
158
62
  end
159
63
  end
@@ -44,7 +44,7 @@ module HTTPX
44
44
 
45
45
  max_redirects = redirect_request.max_redirects
46
46
 
47
- return response unless REDIRECT_STATUS.include?(response.status)
47
+ return response unless REDIRECT_STATUS.include?(response.status) && response.headers.key?("location")
48
48
  return response unless max_redirects.positive?
49
49
 
50
50
  retry_request = build_redirect_request(redirect_request, response, options)
@@ -86,10 +86,22 @@ module HTTPX
86
86
  redirect_uri = __get_location_from_response(response)
87
87
  max_redirects = request.max_redirects
88
88
 
89
- # redirects are **ALWAYS** GET
90
- retry_options = options.merge(headers: request.headers,
91
- body: request.body,
92
- max_redirects: max_redirects - 1)
89
+ if response.status == 305 && options.respond_to?(:proxy)
90
+ # The requested resource MUST be accessed through the proxy given by
91
+ # the Location field. The Location field gives the URI of the proxy.
92
+ retry_options = options.merge(headers: request.headers,
93
+ proxy: { uri: redirect_uri },
94
+ body: request.body,
95
+ max_redirects: max_redirects - 1)
96
+ redirect_uri = request.url
97
+ else
98
+
99
+ # redirects are **ALWAYS** GET
100
+ retry_options = options.merge(headers: request.headers,
101
+ body: request.body,
102
+ max_redirects: max_redirects - 1)
103
+ end
104
+
93
105
  build_request(:get, redirect_uri, retry_options)
94
106
  end
95
107
 
@@ -5,13 +5,10 @@ module HTTPX
5
5
  #
6
6
  # https://gitlab.com/honeyryderchuck/httpx/wikis/Authentication#ntlm-authentication
7
7
  #
8
- module NTLMAuthentication
9
- NTLMParams = Struct.new(:user, :domain, :password)
10
-
8
+ module NTLMAuth
11
9
  class << self
12
10
  def load_dependencies(_klass)
13
- require "base64"
14
- require "ntlm"
11
+ require_relative "authentication/ntlm"
15
12
  end
16
13
 
17
14
  def extra_options(options)
@@ -21,7 +18,7 @@ module HTTPX
21
18
 
22
19
  module OptionsMethods
23
20
  def option_ntlm(value)
24
- raise TypeError, ":ntlm must be a #{NTLMParams}" unless value.is_a?(NTLMParams)
21
+ raise TypeError, ":ntlm must be a #{Authentication::Ntlm}" unless value.is_a?(Authentication::Ntlm)
25
22
 
26
23
  value
27
24
  end
@@ -29,7 +26,7 @@ module HTTPX
29
26
 
30
27
  module InstanceMethods
31
28
  def ntlm_authentication(user, password, domain = nil)
32
- with(ntlm: NTLMParams.new(user, domain, password))
29
+ with(ntlm: Authentication::Ntlm.new(user, password, domain: domain))
33
30
  end
34
31
 
35
32
  alias_method :ntlm_auth, :ntlm_authentication
@@ -39,19 +36,12 @@ module HTTPX
39
36
  ntlm = request.options.ntlm
40
37
 
41
38
  if ntlm
42
- request.headers["authorization"] = "NTLM #{NTLM.negotiate(domain: ntlm.domain).to_base64}"
39
+ request.headers["authorization"] = ntlm.negotiate
43
40
  probe_response = wrap { super(request).first }
44
41
 
45
- if !probe_response.is_a?(ErrorResponse) && probe_response.status == 401 &&
46
- probe_response.headers.key?("www-authenticate") &&
47
- (challenge = probe_response.headers["www-authenticate"][/NTLM (.*)/, 1])
48
-
49
- challenge = Base64.decode64(challenge)
50
- ntlm_challenge = NTLM.authenticate(challenge, ntlm.user, ntlm.domain, ntlm.password).to_base64
51
-
42
+ if probe_response.status == 401 && ntlm.can_authenticate?(probe_response.headers["www-authenticate"])
52
43
  request.transition(:idle)
53
-
54
- request.headers["authorization"] = "NTLM #{ntlm_challenge}"
44
+ request.headers["authorization"] = ntlm.authenticate(request, probe_response.headers["www-authenticate"])
55
45
  super(request)
56
46
  else
57
47
  probe_response
@@ -63,6 +53,6 @@ module HTTPX
63
53
  end
64
54
  end
65
55
  end
66
- register_plugin :ntlm_authentication, NTLMAuthentication
56
+ register_plugin :ntlm_authentication, NTLMAuth
67
57
  end
68
58
  end
@@ -6,6 +6,42 @@ module HTTPX
6
6
  module Plugins
7
7
  module Proxy
8
8
  module HTTP
9
+ module InstanceMethods
10
+ def with_proxy_basic_auth(opts)
11
+ with(proxy: opts.merge(scheme: "basic"))
12
+ end
13
+
14
+ def with_proxy_digest_auth(opts)
15
+ with(proxy: opts.merge(scheme: "digest"))
16
+ end
17
+
18
+ def with_proxy_ntlm_auth(opts)
19
+ with(proxy: opts.merge(scheme: "ntlm"))
20
+ end
21
+
22
+ def fetch_response(request, connections, options)
23
+ response = super
24
+
25
+ if response &&
26
+ response.status == 407 &&
27
+ !request.headers.key?("proxy-authorization") &&
28
+ response.headers.key?("proxy-authenticate")
29
+
30
+ connection = find_connection(request, connections, options)
31
+
32
+ if connection.options.proxy.can_authenticate?(response.headers["proxy-authenticate"])
33
+ request.transition(:idle)
34
+ request.headers["proxy-authorization"] =
35
+ connection.options.proxy.authenticate(request, response.headers["proxy-authenticate"])
36
+ connection.send(request)
37
+ return
38
+ end
39
+ end
40
+
41
+ response
42
+ end
43
+ end
44
+
9
45
  module ConnectionMethods
10
46
  def connecting?
11
47
  super || @state == :connecting || @state == :connected
@@ -23,11 +59,27 @@ module HTTPX
23
59
  @io.connect
24
60
  return unless @io.connected?
25
61
 
26
- @parser = registry(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
27
- @parser.extend(ProxyParser)
28
- @parser.once(:response, &method(:__http_on_connect))
29
- @parser.on(:close) { transition(:closing) }
30
- __http_proxy_connect
62
+ @parser || begin
63
+ @parser = registry(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
64
+ parser = @parser
65
+ parser.extend(ProxyParser)
66
+ parser.on(:response, &method(:__http_on_connect))
67
+ parser.on(:close) { transition(:closing) }
68
+ parser.on(:reset) do
69
+ if parser.empty?
70
+ reset
71
+ else
72
+ transition(:closing)
73
+ transition(:closed)
74
+ emit(:reset)
75
+
76
+ parser.reset if @parser
77
+ transition(:idle)
78
+ transition(:connecting)
79
+ end
80
+ end
81
+ __http_proxy_connect(parser)
82
+ end
31
83
  return if @state == :connected
32
84
  when :connected
33
85
  return unless @state == :idle || @state == :connecting
@@ -44,13 +96,13 @@ module HTTPX
44
96
  super
45
97
  end
46
98
 
47
- def __http_proxy_connect
99
+ def __http_proxy_connect(parser)
48
100
  req = @pending.first
49
- # if the first request after CONNECT is to an https address, it is assumed that
50
- # all requests in the queue are not only ALL HTTPS, but they also share the certificate,
51
- # and therefore, will share the connection.
52
- #
53
- if req.uri.scheme == "https"
101
+ if req && req.uri.scheme == "https"
102
+ # if the first request after CONNECT is to an https address, it is assumed that
103
+ # all requests in the queue are not only ALL HTTPS, but they also share the certificate,
104
+ # and therefore, will share the connection.
105
+ #
54
106
  connect_request = ConnectRequest.new(req.uri, @options)
55
107
  @inflight += 1
56
108
  parser.send(connect_request)
@@ -59,7 +111,7 @@ module HTTPX
59
111
  end
60
112
  end
61
113
 
62
- def __http_on_connect(_, response)
114
+ def __http_on_connect(request, response)
63
115
  @inflight -= 1
64
116
  if response.status == 200
65
117
  req = @pending.first
@@ -67,6 +119,14 @@ module HTTPX
67
119
  @io = ProxySSL.new(@io, request_uri, @options)
68
120
  transition(:connected)
69
121
  throw(:called)
122
+ elsif response.status == 407 &&
123
+ !request.headers.key?("proxy-authorization") &&
124
+ @options.proxy.can_authenticate?(response.headers["proxy-authenticate"])
125
+
126
+ request.transition(:idle)
127
+ request.headers["proxy-authorization"] = @options.proxy.authenticate(request, response.headers["proxy-authenticate"])
128
+ @parser.send(request)
129
+ @inflight += 1
70
130
  else
71
131
  pending = @pending + @parser.pending
72
132
  while (req = pending.shift)
@@ -88,7 +148,10 @@ module HTTPX
88
148
  extra_headers = super
89
149
 
90
150
  proxy_params = @options.proxy
91
- extra_headers["proxy-authorization"] = "Basic #{proxy_params.token_authentication}" if proxy_params.authenticated?
151
+ if proxy_params.scheme == "basic"
152
+ # opt for basic auth
153
+ extra_headers["proxy-authorization"] = proxy_params.authenticate(extra_headers)
154
+ end
92
155
  extra_headers["proxy-connection"] = extra_headers.delete("connection") if extra_headers.key?("connection")
93
156
  extra_headers
94
157
  end
@@ -18,6 +18,10 @@ module HTTPX
18
18
 
19
19
  Error = Socks5Error
20
20
 
21
+ def self.load_dependencies(*)
22
+ require_relative "../authentication/socks5"
23
+ end
24
+
21
25
  module ConnectionMethods
22
26
  def call
23
27
  super
@@ -156,16 +160,14 @@ module HTTPX
156
160
 
157
161
  def negotiate(parameters)
158
162
  methods = [NOAUTH]
159
- methods << PASSWD if parameters.authenticated?
163
+ methods << PASSWD if parameters.can_authenticate?
160
164
  methods.unshift(methods.size)
161
165
  methods.unshift(VERSION)
162
166
  methods.pack("C*")
163
167
  end
164
168
 
165
169
  def authenticate(parameters)
166
- user = parameters.username
167
- password = parameters.password
168
- [0x01, user.bytesize, user, password.bytesize, password].pack("CCA*CA*")
170
+ parameters.authenticate
169
171
  end
170
172
 
171
173
  def connect(uri)
@@ -19,22 +19,43 @@ module HTTPX
19
19
  PROXY_ERRORS = [TimeoutError, IOError, SystemCallError, Error].freeze
20
20
 
21
21
  class Parameters
22
- attr_reader :uri, :username, :password
22
+ attr_reader :uri, :username, :password, :scheme
23
23
 
24
- def initialize(uri:, username: nil, password: nil)
24
+ def initialize(uri:, scheme: nil, username: nil, password: nil, **extra)
25
25
  @uri = uri.is_a?(URI::Generic) ? uri : URI(uri)
26
26
  @username = username || @uri.user
27
27
  @password = password || @uri.password
28
+
29
+ return unless @username && @password
30
+
31
+ scheme ||= case @uri.scheme
32
+ when "socks5"
33
+ @uri.scheme
34
+ when "http", "https"
35
+ "basic"
36
+ else
37
+ return
38
+ end
39
+
40
+ @scheme = scheme
41
+
42
+ auth_scheme = scheme.to_s.capitalize
43
+
44
+ require_relative "authentication/#{scheme}" unless defined?(Authentication) && Authentication.const_defined?(auth_scheme, false)
45
+
46
+ @authenticator = Authentication.const_get(auth_scheme).new(@username, @password, **extra)
28
47
  end
29
48
 
30
- def authenticated?
31
- @username && @password
49
+ def can_authenticate?(*args)
50
+ return false unless @authenticator
51
+
52
+ @authenticator.can_authenticate?(*args)
32
53
  end
33
54
 
34
- def token_authentication
35
- return unless authenticated?
55
+ def authenticate(*args)
56
+ return unless @authenticator
36
57
 
37
- Base64.strict_encode64("#{@username}:#{@password}")
58
+ @authenticator.authenticate(*args)
38
59
  end
39
60
 
40
61
  def ==(other)
@@ -42,7 +63,8 @@ module HTTPX
42
63
  when Parameters
43
64
  @uri == other.uri &&
44
65
  @username == other.username &&
45
- @password == other.password
66
+ @password == other.password &&
67
+ @scheme == other.scheme
46
68
  when URI::Generic, String
47
69
  proxy_uri = @uri.dup
48
70
  proxy_uri.user = @username
@@ -37,6 +37,7 @@ module HTTPX::Plugins
37
37
  return unless request.headers.same_headers?(original_request.headers)
38
38
  else
39
39
  return unless vary.split(/ *, */).all? do |cache_field|
40
+ cache_field.downcase!
40
41
  !original_request.headers.key?(cache_field) || request.headers[cache_field] == original_request.headers[cache_field]
41
42
  end
42
43
  end
data/lib/httpx/pool.rb CHANGED
@@ -80,6 +80,9 @@ module HTTPX
80
80
  connection.on(:activate) do
81
81
  select_connection(connection)
82
82
  end
83
+ connection.on(:close) do
84
+ unregister_connection(connection)
85
+ end
83
86
  end
84
87
 
85
88
  def deactivate(connections)
@@ -143,8 +146,6 @@ module HTTPX
143
146
 
144
147
  def on_resolver_error(connection, error)
145
148
  connection.emit(:error, error)
146
- # must remove connection by hand, hasn't been started yet
147
- unregister_connection(connection)
148
149
  end
149
150
 
150
151
  def on_resolver_close(resolver)
@@ -171,8 +172,7 @@ module HTTPX
171
172
 
172
173
  def unregister_connection(connection)
173
174
  @connections.delete(connection)
174
- deselect_connection(connection)
175
- @connected_connections -= 1
175
+ @connected_connections -= 1 if deselect_connection(connection)
176
176
  end
177
177
 
178
178
  def select_connection(connection)
data/lib/httpx/request.rb CHANGED
@@ -49,7 +49,9 @@ module HTTPX
49
49
  origin = @options.origin
50
50
  raise(Error, "invalid URI: #{@uri}") unless origin
51
51
 
52
- @uri = origin.merge(@uri)
52
+ base_path = @options.base_path
53
+
54
+ @uri = origin.merge("#{base_path}#{@uri}")
53
55
  end
54
56
 
55
57
  raise(Error, "unknown method: #{verb}") unless METHODS.include?(@verb)
data/lib/httpx/session.rb CHANGED
@@ -106,16 +106,21 @@ module HTTPX
106
106
  end
107
107
 
108
108
  def build_altsvc_connection(existing_connection, connections, alt_origin, origin, alt_params, options)
109
+ # do not allow security downgrades on altsvc negotiation
110
+ return if existing_connection.origin.scheme == "https" && alt_origin.scheme != "https"
111
+
109
112
  altsvc = AltSvc.cached_altsvc_set(origin, alt_params.merge("origin" => alt_origin))
110
113
 
111
114
  # altsvc already exists, somehow it wasn't advertised, probably noop
112
115
  return unless altsvc
113
116
 
114
- connection = pool.find_connection(alt_origin, options) || build_connection(alt_origin, options)
117
+ alt_options = options.merge(ssl: options.ssl.merge(hostname: URI(origin).host))
118
+
119
+ connection = pool.find_connection(alt_origin, alt_options) || build_connection(alt_origin, alt_options)
115
120
  # advertised altsvc is the same origin being used, ignore
116
121
  return if connection == existing_connection
117
122
 
118
- set_connection_callbacks(connection, connections, options)
123
+ set_connection_callbacks(connection, connections, alt_options)
119
124
 
120
125
  log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
121
126
 
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.19.7"
4
+ VERSION = "0.20.1"
5
5
  end
data/sig/chainable.rbs CHANGED
@@ -13,9 +13,9 @@ module HTTPX
13
13
  | (options) { (Session) -> void } -> void
14
14
 
15
15
  def plugin: (:authentication, ?options) -> Plugins::sessionAuthentication
16
- | (:basic_authentication, ?options) -> Plugins::sessionBasicAuthentication
17
- | (:digest_authentication, ?options) -> Plugins::sessionDigestAuthentication
18
- | (:ntlm_authentication, ?options) -> Plugins::sessionNTLMAuthentication
16
+ | (:basic_authentication, ?options) -> Plugins::sessionBasicAuth
17
+ | (:digest_authentication, ?options) -> Plugins::sessionDigestAuth
18
+ | (:ntlm_authentication, ?options) -> Plugins::sessionNTLMAuth
19
19
  | (:aws_sdk_authentication, ?options) -> Plugins::sessionAwsSdkAuthentication
20
20
  | (:compression, ?options) -> Session
21
21
  | (:cookies, ?options) -> Plugins::sessionCookies
@@ -25,7 +25,7 @@ module HTTPX
25
25
  | (:h2c, ?options) -> Session
26
26
  | (:multipart, ?options) -> Session
27
27
  | (:persistent, ?options) -> Plugins::sessionPersistent
28
- | (:proxy, ?options) -> Plugins::sessionProxy
28
+ | (:proxy, ?options) -> (Plugins::sessionProxy & Plugins::httpProxy)
29
29
  | (:push_promise, ?options) -> Plugins::sessionPushPromise
30
30
  | (:retries, ?options) -> Plugins::sessionRetries
31
31
  | (:rate_limiter, ?options) -> Session
@@ -0,0 +1,19 @@
1
+ module HTTPX
2
+ module Plugins
3
+ module Authentication
4
+ class Basic
5
+ @user: String
6
+ @password: String
7
+
8
+ def can_authenticate?: (String? authenticate) -> boolish
9
+
10
+ def authenticate: (*untyped) -> String
11
+
12
+ private
13
+
14
+ def initialize: (string user, string password, *untyped) -> void
15
+
16
+ end
17
+ end
18
+ end
19
+ end