class Bundler::Persistent::Net::HTTP::Persistent

def self.detect_idle_timeout uri, max = 10

def self.detect_idle_timeout uri, max = 10
  uri = Bundler::URI uri unless Bundler::URI::Generic === uri
  uri += '/'
  req = Net::HTTP::Head.new uri.request_uri
  http = new 'net-http-persistent detect_idle_timeout'
  http.connection_for uri do |connection|
    sleep_time = 0
    http = connection.http
    loop do
      response = http.request req
      $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG
      unless Net::HTTPOK === response then
        raise Error, "bad response code #{response.code} detecting idle timeout"
      end
      break if sleep_time >= max
      sleep_time += 1
      $stderr.puts "sleeping #{sleep_time}" if $DEBUG
      sleep sleep_time
    end
  end
rescue
  # ignore StandardErrors, we've probably found the idle timeout.
ensure
  return sleep_time unless $!
end

def ca_file= file

def ca_file= file
  @ca_file = file
  reconnect_ssl
end

def ca_path= path

def ca_path= path
  @ca_path = path
  reconnect_ssl
end

def cert_store= store

def cert_store= store
  @cert_store = store
  reconnect_ssl
end

def certificate= certificate

def certificate= certificate
  @certificate = certificate
  reconnect_ssl
end

def ciphers= ciphers

def ciphers= ciphers
  @ciphers = ciphers
  reconnect_ssl
end

def connection_for uri

def connection_for uri
  use_ssl = uri.scheme.downcase == 'https'
  net_http_args = [uri.hostname, uri.port]
  # I'm unsure if uri.host or uri.hostname should be checked against
  # the proxy bypass list.
  if @proxy_uri and not proxy_bypass? uri.host, uri.port then
    net_http_args.concat @proxy_args
  else
    net_http_args.concat [nil, nil, nil, nil]
  end
  connection = @pool.checkout net_http_args
  http = connection.http
  connection.ressl @ssl_generation if
    connection.ssl_generation != @ssl_generation
  if not http.started? then
    ssl   http if use_ssl
    start http
  elsif expired? connection then
    reset connection
  end
  http.keep_alive_timeout = @idle_timeout  if @idle_timeout
  http.max_retries        = @max_retries   if http.respond_to?(:max_retries=)
  http.read_timeout       = @read_timeout  if @read_timeout
  http.write_timeout      = @write_timeout if
    @write_timeout && http.respond_to?(:write_timeout=)
  return yield connection
rescue Errno::ECONNREFUSED
  address = http.proxy_address || http.address
  port    = http.proxy_port    || http.port
  raise Error, "connection refused: #{address}:#{port}"
rescue Errno::EHOSTDOWN
  address = http.proxy_address || http.address
  port    = http.proxy_port    || http.port
  raise Error, "host down: #{address}:#{port}"
ensure
  @pool.checkin net_http_args
end

def escape str

def escape str
  CGI.escape str if str
end

def expired? connection

def expired? connection
  return true  if     @max_requests && connection.requests >= @max_requests
  return false unless @idle_timeout
  return true  if     @idle_timeout.zero?
  Time.now - connection.last_use > @idle_timeout
end

def finish connection

def finish connection
  connection.finish
  connection.http.instance_variable_set :@last_communicated, nil
  connection.http.instance_variable_set :@ssl_session, nil unless
    @reuse_ssl_sessions
end

def http_version uri

def http_version uri
  @http_versions["#{uri.hostname}:#{uri.port}"]
end

def initialize name: nil, proxy: nil, pool_size: DEFAULT_POOL_SIZE

def initialize name: nil, proxy: nil, pool_size: DEFAULT_POOL_SIZE
  @name = name
  @debug_output     = nil
  @proxy_uri        = nil
  @no_proxy         = []
  @headers          = {}
  @override_headers = {}
  @http_versions    = {}
  @keep_alive       = 30
  @open_timeout     = nil
  @read_timeout     = nil
  @write_timeout    = nil
  @idle_timeout     = 5
  @max_requests     = nil
  @max_retries      = 1
  @socket_options   = []
  @ssl_generation   = 0 # incremented when SSL session variables change
  @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
    Socket.const_defined? :TCP_NODELAY
  @pool = Bundler::Persistent::Net::HTTP::Persistent::Pool.new size: pool_size do |http_args|
    Bundler::Persistent::Net::HTTP::Persistent::Connection.new Net::HTTP, http_args, @ssl_generation
  end
  @certificate        = nil
  @ca_file            = nil
  @ca_path            = nil
  @ciphers            = nil
  @private_key        = nil
  @ssl_timeout        = nil
  @ssl_version        = nil
  @min_version        = nil
  @max_version        = nil
  @verify_callback    = nil
  @verify_depth       = nil
  @verify_mode        = nil
  @cert_store         = nil
  @generation         = 0 # incremented when proxy Bundler::URI changes
  if HAVE_OPENSSL then
    @verify_mode        = OpenSSL::SSL::VERIFY_PEER
    @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session
  end
  self.proxy = proxy if proxy
end

def max_retries= retries

def max_retries= retries
  retries = retries.to_int
  raise ArgumentError, "max_retries must be positive" if retries < 0
  @max_retries = retries
  reconnect
end

def max_version= max_version

def max_version= max_version
  @max_version = max_version
  reconnect_ssl
end

def min_version= min_version

def min_version= min_version
  @min_version = min_version
  reconnect_ssl
end

def normalize_uri uri

def normalize_uri uri
  (uri =~ /^https?:/) ? uri : "http://#{uri}"
end

def private_key= key

def private_key= key
  @private_key = key
  reconnect_ssl
end

def proxy= proxy

def proxy= proxy
  @proxy_uri = case proxy
               when :ENV      then proxy_from_env
               when Bundler::URI::HTTP then proxy
               when nil       then # ignore
               else raise ArgumentError, 'proxy must be :ENV or a Bundler::URI::HTTP'
               end
  @no_proxy.clear
  if @proxy_uri then
    @proxy_args = [
      @proxy_uri.hostname,
      @proxy_uri.port,
      unescape(@proxy_uri.user),
      unescape(@proxy_uri.password),
    ]
    @proxy_connection_id = [nil, *@proxy_args].join ':'
    if @proxy_uri.query then
      @no_proxy = CGI.parse(@proxy_uri.query)['no_proxy'].join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? }
    end
  end
  reconnect
  reconnect_ssl
end

def proxy_bypass? host, port

def proxy_bypass? host, port
  host = host.downcase
  host_port = [host, port].join ':'
  @no_proxy.each do |name|
    return true if host[-name.length, name.length] == name or
       host_port[-name.length, name.length] == name
  end
  false
end

def proxy_from_env

def proxy_from_env
  env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
  return nil if env_proxy.nil? or env_proxy.empty?
  uri = Bundler::URI normalize_uri env_proxy
  env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
  # '*' is special case for always bypass
  return nil if env_no_proxy == '*'
  if env_no_proxy then
    uri.query = "no_proxy=#{escape(env_no_proxy)}"
  end
  unless uri.user or uri.password then
    uri.user     = escape ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']
    uri.password = escape ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']
  end
  uri
end

def reconnect

def reconnect
  @generation += 1
end

def reconnect_ssl

def reconnect_ssl
  @ssl_generation += 1
end

def request uri, req = nil, &block

def request uri, req = nil, &block
  uri      = Bundler::URI uri
  req      = request_setup req || uri
  response = nil
  connection_for uri do |connection|
    http = connection.http
    begin
      connection.requests += 1
      response = http.request req, &block
      if req.connection_close? or
        (response.http_version <= '1.0' and
          not response.connection_keep_alive?) or
          response.connection_close? then
        finish connection
      end
    rescue Exception # make sure to close the connection when it was interrupted
      finish connection
      raise
    ensure
      connection.last_use = Time.now
    end
  end
  @http_versions["#{uri.hostname}:#{uri.port}"] ||= response.http_version
  response
end

def request_setup req_or_uri # :nodoc:

:nodoc:
def request_setup req_or_uri # :nodoc:
  req = if req_or_uri.respond_to? 'request_uri' then
          Net::HTTP::Get.new req_or_uri.request_uri
        else
          req_or_uri
        end
  @headers.each do |pair|
    req.add_field(*pair)
  end
  @override_headers.each do |name, value|
    req[name] = value
  end
  unless req['Connection'] then
    req.add_field 'Connection', 'keep-alive'
    req.add_field 'Keep-Alive', @keep_alive
  end
  req
end

def reset connection

def reset connection
  http = connection.http
  finish connection
  start http
rescue Errno::ECONNREFUSED
  e = Error.new "connection refused: #{http.address}:#{http.port}"
  e.set_backtrace $@
  raise e
rescue Errno::EHOSTDOWN
  e = Error.new "host down: #{http.address}:#{http.port}"
  e.set_backtrace $@
  raise e
end

def shutdown

def shutdown
  @pool.shutdown { |http| http.finish }
end

def ssl connection

def ssl connection
  connection.use_ssl = true
  connection.ciphers     = @ciphers     if @ciphers
  connection.ssl_timeout = @ssl_timeout if @ssl_timeout
  connection.ssl_version = @ssl_version if @ssl_version
  connection.min_version = @min_version if @min_version
  connection.max_version = @max_version if @max_version
  connection.verify_depth = @verify_depth
  connection.verify_mode  = @verify_mode
  if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE and
     not Object.const_defined?(:I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG) then
    warn <<-WARNING
                           !!!SECURITY WARNING!!!
e SSL HTTP connection to:
#{connection.address}:#{connection.port}
                         !!!MAY NOT BE VERIFIED!!!
 your platform your OpenSSL implementation is broken.
ere is no difference between the values of VERIFY_NONE and VERIFY_PEER.
is means that attempting to verify the security of SSL connections may not
rk.  This exposes you to man-in-the-middle exploits, snooping on the
ntents of your connection and other dangers to the security of your data.
 disable this warning define the following constant at top-level in your
plication:
I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG = nil
    WARNING
  end
  connection.ca_file = @ca_file if @ca_file
  connection.ca_path = @ca_path if @ca_path
  if @ca_file or @ca_path then
    connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
    connection.verify_callback = @verify_callback if @verify_callback
  end
  if @certificate and @private_key then
    connection.cert = @certificate
    connection.key  = @private_key
  end
  connection.cert_store = if @cert_store then
                            @cert_store
                          else
                            store = OpenSSL::X509::Store.new
                            store.set_default_paths
                            store
                          end
end

def ssl_timeout= ssl_timeout

def ssl_timeout= ssl_timeout
  @ssl_timeout = ssl_timeout
  reconnect_ssl
end

def ssl_version= ssl_version

def ssl_version= ssl_version
  @ssl_version = ssl_version
  reconnect_ssl
end

def start http

def start http
  http.set_debug_output @debug_output if @debug_output
  http.open_timeout = @open_timeout if @open_timeout
  http.start
  socket = http.instance_variable_get :@socket
  if socket then # for fakeweb
    @socket_options.each do |option|
      socket.io.setsockopt(*option)
    end
  end
end

def unescape str

def unescape str
  CGI.unescape str if str
end

def verify_callback= callback

def verify_callback= callback
  @verify_callback = callback
  reconnect_ssl
end

def verify_depth= verify_depth

def verify_depth= verify_depth
  @verify_depth = verify_depth
  reconnect_ssl
end

def verify_mode= verify_mode

def verify_mode= verify_mode
  @verify_mode = verify_mode
  reconnect_ssl
end