Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support additional headers for TLS proxies #742

Merged
merged 8 commits into from Apr 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 19 additions & 26 deletions lib/excon/connection.rb
Expand Up @@ -115,7 +115,7 @@ def request_call(datum)
# we already have data from a middleware, so bail
return datum
else
socket.data = datum
socket(datum)
# start with "METHOD /path"
request = datum[:method].to_s.upcase + ' '
if datum[:proxy] && datum[:scheme] != HTTPS
Expand Down Expand Up @@ -144,35 +144,25 @@ def request_call(datum)
end

# add headers to request
datum[:headers].each do |key, values|
if key.to_s.match(/[\r\n]/)
raise Excon::Errors::InvalidHeaderKey.new(key.to_s.inspect + ' contains forbidden "\r" or "\n"')
end
[values].flatten.each do |value|
if value.to_s.match(/[\r\n]/)
raise Excon::Errors::InvalidHeaderValue.new(value.to_s.inspect + ' contains forbidden "\r" or "\n"')
end
request << key.to_s << ': ' << value.to_s << CR_NL
end
end
request << Utils.headers_hash_to_s(datum[:headers])

# add additional "\r\n" to indicate end of headers
request << CR_NL

if datum.has_key?(:request_block)
socket.write(request) # write out request + headers
socket(datum).write(request) # write out request + headers
while true # write out body with chunked encoding
chunk = datum[:request_block].call
chunk = binary_encode(chunk)
if chunk.length > 0
socket.write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
socket(datum).write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
else
socket.write(String.new("0#{CR_NL}#{CR_NL}"))
socket(datum).write(String.new("0#{CR_NL}#{CR_NL}"))
break
end
end
elsif body.nil?
socket.write(request) # write out request + headers
socket(datum).write(request) # write out request + headers
else # write out body
if body.respond_to?(:binmode) && !body.is_a?(StringIO)
body.binmode
Expand All @@ -186,13 +176,13 @@ def request_call(datum)
chunk = body.read([datum[:chunk_size] - request.length, 0].max)
if chunk
chunk = binary_encode(chunk)
socket.write(request << chunk)
socket(datum).write(request << chunk)
else
socket.write(request) # write out request + headers
socket(datum).write(request) # write out request + headers
end

while (chunk = body.read(datum[:chunk_size]))
socket.write(chunk)
socket(datum).write(chunk)
end
end
end
Expand Down Expand Up @@ -463,14 +453,14 @@ def response(datum={})
end
end

def socket
unix_proxy = @data[:proxy] ? @data[:proxy][:scheme] == UNIX : false
sockets[@socket_key] ||= if @data[:scheme] == UNIX || unix_proxy
Excon::UnixSocket.new(@data)
elsif @data[:ssl_uri_schemes].include?(@data[:scheme])
Excon::SSLSocket.new(@data)
def socket(datum = @data)
unix_proxy = datum[:proxy] ? datum[:proxy][:scheme] == UNIX : false
sockets[@socket_key] ||= if datum[:scheme] == UNIX || unix_proxy
Excon::UnixSocket.new(datum)
elsif datum[:ssl_uri_schemes].include?(datum[:scheme])
Excon::SSLSocket.new(datum)
else
Excon::Socket.new(@data)
Excon::Socket.new(datum)
end
end

Expand Down Expand Up @@ -573,6 +563,9 @@ def setup_proxy
if uri.user
@data[:proxy][:user] = uri.user
end
if @data[:ssl_proxy_headers] && !@data[:ssl_uri_schemes].include?(@data[:scheme])
raise ArgumentError, "The `:ssl_proxy_headers` parameter should only be used with HTTPS requests."
end
if @data[:proxy][:scheme] == UNIX
if @data[:proxy][:host]
raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
Expand Down
1 change: 1 addition & 0 deletions lib/excon/constants.rb
Expand Up @@ -99,6 +99,7 @@ module Excon
:ssl_version,
:ssl_min_version,
:ssl_max_version,
:ssl_proxy_headers,
:ssl_uri_schemes,
:tcp_nodelay,
:thread_safe_sockets,
Expand Down
4 changes: 4 additions & 0 deletions lib/excon/ssl_socket.rb
Expand Up @@ -104,6 +104,10 @@ def initialize(data = {})

request += "Proxy-Connection: Keep-Alive#{Excon::CR_NL}"

if @data[:ssl_proxy_headers]
request << Utils.headers_hash_to_s(@data[:ssl_proxy_headers])
end

request += Excon::CR_NL

# write out the proxy setup request
Expand Down
17 changes: 17 additions & 0 deletions lib/excon/utils.rb
Expand Up @@ -121,5 +121,22 @@ def unescape_form(str)
str.gsub!(/\+/, ' ')
str.gsub(ESCAPED) { $1.hex.chr }
end

# Performs validation on the passed header hash and returns a string representation of the headers
def headers_hash_to_s(headers)
headers_str = String.new
headers.each do |key, values|
if key.to_s.match(/[\r\n]/)
raise Excon::Errors::InvalidHeaderKey.new(key.to_s.inspect + ' contains forbidden "\r" or "\n"')
end
[values].flatten.each do |value|
if value.to_s.match(/[\r\n]/)
raise Excon::Errors::InvalidHeaderValue.new(value.to_s.inspect + ' contains forbidden "\r" or "\n"')
end
headers_str << key.to_s << ': ' << value.to_s << CR_NL
end
end
headers_str
end
end
end