class HTTPI::Adapter::NetHTTP

HTTPI::Adapter::NetHTTP

Adapter for the Net::HTTP client. ruby-doc.org/stdlib/libdoc/net/http/rdoc/

Attributes

client[R]

Public Class Methods

new(request) click to toggle source
# File lib/httpi/adapter/net_http.rb, line 18
def initialize(request)
  check_net_ntlm_version! if request.auth.ntlm?
  @request = request
  @client = create_client
end

Public Instance Methods

request(method) click to toggle source

Executes arbitrary HTTP requests. @see HTTPI.request

# File lib/httpi/adapter/net_http.rb, line 28
def request(method)
  # Determine if Net::HTTP supports the method using reflection
  unless Net::HTTP.const_defined?(:"#{method.to_s.capitalize}") &&
      Net::HTTP.const_get(:"#{method.to_s.capitalize}").class == Class

    raise NotSupportedError, "Net::HTTP does not support "\
      "#{method.to_s.upcase}"
  end
  do_request(method) do |http, http_request|
    http_request.body = @request.body
    if @request.on_body then
      perform(http, http_request) do |res|
        res.read_body do |seg|
          @request.on_body.call(seg)
        end
      end
    else
      perform(http, http_request)
    end
  end
rescue OpenSSL::SSL::SSLError
  raise SSLError
rescue Errno::ECONNREFUSED   # connection refused
  $!.extend ConnectionError
  raise
end

Private Instance Methods

check_net_ntlm_version!() click to toggle source
# File lib/httpi/adapter/net_http.rb, line 60
def check_net_ntlm_version!
  begin
    require 'net/ntlm'
    require 'net/ntlm/version' unless Net::NTLM.const_defined?(:VERSION, false)
    unless ntlm_version >= '0.3.2'
      raise ArgumentError, 'Invalid version of rubyntlm. Please use v0.3.2+.'
    end
  rescue LoadError
  end
end
create_client() click to toggle source
# File lib/httpi/adapter/net_http.rb, line 75
def create_client
  proxy_url = @request.proxy || URI("")
  if URI(proxy_url).scheme == 'socks'
    proxy =Net::HTTP.SOCKSProxy(proxy_url.host, proxy_url.port)
  else
    proxy = Net::HTTP::Proxy(proxy_url.host, proxy_url.port, proxy_url.user, proxy_url.password)
  end
  proxy.new(@request.url.host, @request.url.port)
end
do_request(type, &requester) click to toggle source
# File lib/httpi/adapter/net_http.rb, line 85
def do_request(type, &requester)
  setup
  response = @client.start do |http|
    negotiate_ntlm_auth(http, &requester) if @request.auth.ntlm?
    requester.call(http, request_client(type))
  end
  respond_with(response)
end
negotiate_ntlm_auth(http, &requester) click to toggle source
# File lib/httpi/adapter/net_http.rb, line 99
def negotiate_ntlm_auth(http, &requester)
  unless Net.const_defined?(:NTLM)
    raise NotSupportedError, 'Net::NTLM is not available. Install via gem install rubyntlm.'
  end

  # first figure out if we should use NTLM or Negotiate
  nego_auth_response = respond_with(requester.call(http, request_client(:head)))
  if nego_auth_response.headers['www-authenticate'] && nego_auth_response.headers['www-authenticate'].include?('Negotiate')
    auth_method = 'Negotiate'
  elsif nego_auth_response.headers['www-authenticate'] && nego_auth_response.headers['www-authenticate'].include?('NTLM')
    auth_method = 'NTLM'
  else
    auth_method = 'NTLM'
    HTTPI.logger.debug 'Server does not support NTLM/Negotiate. Trying NTLM anyway'
  end

  # initiate a request is to authenticate (exchange secret and auth) using the method determined above...
  ntlm_message_type1 = Net::NTLM::Message::Type1.new
  %w(workstation domain).each do |a|
    ntlm_message_type1.send("#{a}=",'')
    ntlm_message_type1.enable(a.to_sym)
  end

  @request.headers["Authorization"] = "#{auth_method} #{ntlm_message_type1.encode64}"

  auth_response = respond_with(requester.call(http, request_client(:head)))

  # build an authentication request based on the token provided by the server
  if auth_response.headers["WWW-Authenticate"] =~ /(NTLM|Negotiate) (.+)/
    auth_token = $2
    ntlm_message = Net::NTLM::Message.decode64(auth_token)

    message_builder = {}
    # copy the username and password from the authorization parameters
    message_builder[:user] = @request.auth.ntlm[0]
    message_builder[:password] = @request.auth.ntlm[1]

    # we need to provide a domain in the packet if an only if it was provided by the user in the auth request
    if @request.auth.ntlm[2]
      message_builder[:domain] = @request.auth.ntlm[2].upcase
    else
      message_builder[:domain] = ''
    end

    ntlm_response = ntlm_message.response(message_builder ,
                                           {:ntlmv2 => true})
    # Finally add header of Authorization
    @request.headers["Authorization"] = "#{auth_method} #{ntlm_response.encode64}"
  end

  nil
end
ntlm_version() click to toggle source
# File lib/httpi/adapter/net_http.rb, line 56
def ntlm_version
  Net::NTLM::VERSION::STRING
end
perform(http, http_request, &block) click to toggle source
# File lib/httpi/adapter/net_http.rb, line 71
def perform(http, http_request, &block)
  http.request http_request, &block
end
request_client(type) click to toggle source
# File lib/httpi/adapter/net_http.rb, line 197
def request_client(type)
  request_class = Net::HTTP.const_get(:"#{type.to_s.capitalize}")

  request_client = request_class.new @request.url.request_uri, @request.headers
  request_client.basic_auth(*@request.auth.credentials) if @request.auth.basic?

  if @request.auth.digest?
    raise NotSupportedError, "Net::HTTP does not support HTTP digest authentication"
  end

  request_client
end
respond_with(response) click to toggle source
# File lib/httpi/adapter/net_http.rb, line 210
def respond_with(response)
  headers = response.to_hash
  headers.each do |key, value|
    headers[key] = value[0] if value.size <= 1
  end
  body = (response.body.kind_of?(Net::ReadAdapter) ? "" : response.body)
  Response.new response.code, headers, body
end
setup() click to toggle source
# File lib/httpi/adapter/net_http.rb, line 94
def setup
  setup_client
  setup_ssl_auth if @request.auth.ssl? || @request.ssl?
end
setup_client() click to toggle source
# File lib/httpi/adapter/net_http.rb, line 152
def setup_client
  @client.use_ssl = @request.ssl?
  @client.open_timeout = @request.open_timeout if @request.open_timeout
  @client.read_timeout = @request.read_timeout if @request.read_timeout
  if @request.write_timeout
    if @client.respond_to?(:write_timeout=) # Expected to appear in Ruby 2.6
      @client.write_timeout = @request.write_timeout
    else
      raise NotSupportedError, "Net::HTTP supports write_timeout starting from Ruby 2.6"
    end
  end
end
setup_ssl_auth() click to toggle source
# File lib/httpi/adapter/net_http.rb, line 165
def setup_ssl_auth
  ssl = @request.auth.ssl

  if @request.auth.ssl?
    unless ssl.verify_mode == :none
      @client.ca_file = ssl.ca_cert_file if ssl.ca_cert_file
      @client.ca_path = ssl.ca_cert_path if ssl.ca_cert_path
      @client.cert_store = ssl_cert_store(ssl)
    end

    # Send client-side certificate regardless of state of SSL verify mode
    @client.key = ssl.cert_key
    @client.cert = ssl.cert
    @client.ciphers = ssl.ciphers if ssl.ciphers

    @client.verify_mode = ssl.openssl_verify_mode
  end

  @client.ssl_version = ssl.ssl_version if ssl.ssl_version
  @client.min_version = ssl.min_version if ssl.min_version
  @client.max_version = ssl.max_version if ssl.max_version
end
ssl_cert_store(ssl) click to toggle source
# File lib/httpi/adapter/net_http.rb, line 188
def ssl_cert_store(ssl)
  return ssl.cert_store if ssl.cert_store

  # Use the default cert store by default, i.e. system ca certs
  cert_store = OpenSSL::X509::Store.new
  cert_store.set_default_paths
  cert_store
end