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

def self.detect_idle_timeout uri, max = 10

def self.detect_idle_timeout uri, max = 10
  uri = URI uri unless URI::Generic === uri
  uri += '/'
  req = Net::HTTP::Head.new uri.request_uri
  http = new 'net-http-persistent detect_idle_timeout'
  connection = http.connection_for uri
  sleep_time = 0
  loop do
    response = connection.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
rescue
  # ignore StandardErrors, we've probably found the idle timeout.
ensure
  http.shutdown
  return sleep_time unless $!
end

def ca_file= file

def ca_file= file
  @ca_file = file
  reconnect_ssl
end

def can_retry? req, retried_on_ruby_2 = false

def can_retry? req, retried_on_ruby_2 = false
  return @retry_change_requests && !idempotent?(req) if retried_on_ruby_2
  @retry_change_requests || idempotent?(req)
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 cleanup(generation, thread = Thread.current,

def cleanup(generation, thread = Thread.current,
            generation_key = @generation_key) # :nodoc:
  timeouts = thread[@timeout_key]
  (0...generation).each do |old_generation|
    next unless thread[generation_key]
    conns = thread[generation_key].delete old_generation
    conns.each_value do |conn|
      finish conn, thread
      timeouts.delete conn.object_id if timeouts
    end if conns
  end
end

def connection_close? header

def connection_close? header
  header.connection_close?
end

def connection_close? header

def connection_close? header
  header['connection'] =~ /close/ or header['proxy-connection'] =~ /close/
end

def connection_for uri

def connection_for uri
  Thread.current[@generation_key]     ||= Hash.new { |h,k| h[k] = {} }
  Thread.current[@ssl_generation_key] ||= Hash.new { |h,k| h[k] = {} }
  Thread.current[@request_key]        ||= Hash.new 0
  Thread.current[@timeout_key]        ||= Hash.new EPOCH
  use_ssl = uri.scheme.downcase == 'https'
  if use_ssl then
    raise Bundler::Persistent::Net::HTTP::Persistent::Error, 'OpenSSL is not available' unless
      HAVE_OPENSSL
    ssl_generation = @ssl_generation
    ssl_cleanup ssl_generation
    connections = Thread.current[@ssl_generation_key][ssl_generation]
  else
    generation = @generation
    cleanup generation
    connections = Thread.current[@generation_key][generation]
  end
  net_http_args = [uri.host, uri.port]
  connection_id = net_http_args.join ':'
  if @proxy_uri and not proxy_bypass? uri.host, uri.port then
    connection_id << @proxy_connection_id
    net_http_args.concat @proxy_args
  else
    net_http_args.concat [nil, nil, nil, nil]
  end
  connection = connections[connection_id]
  unless connection = connections[connection_id] then
    connections[connection_id] = http_class.new(*net_http_args)
    connection = connections[connection_id]
    ssl connection if use_ssl
  else
    reset connection if expired? connection
  end
  start connection unless connection.started?
  connection.read_timeout = @read_timeout if @read_timeout
  connection.keep_alive_timeout = @idle_timeout if @idle_timeout && connection.respond_to?(:keep_alive_timeout=)
  connection
rescue Errno::ECONNREFUSED
  address = connection.proxy_address || connection.address
  port    = connection.proxy_port    || connection.port
  raise Error, "connection refused: #{address}:#{port}"
rescue Errno::EHOSTDOWN
  address = connection.proxy_address || connection.address
  port    = connection.proxy_port    || connection.port
  raise Error, "host down: #{address}:#{port}"
end

def connection_keep_alive? header

def connection_keep_alive? header
  header.connection_keep_alive?
end

def connection_keep_alive? header

def connection_keep_alive? header
  header['connection'] =~ /keep-alive/ or
    header['proxy-connection'] =~ /keep-alive/
end

def error_message connection

def error_message connection
  requests = Thread.current[@request_key][connection.object_id] - 1 # fixup
  last_use = Thread.current[@timeout_key][connection.object_id]
  age = Time.now - last_use
  "after #{requests} requests on #{connection.object_id}, " \
    "last used #{age} seconds ago"
end

def escape str

def escape str
  CGI.escape str if str
end

def expired? connection

def expired? connection
  requests = Thread.current[@request_key][connection.object_id]
  return true  if     @max_requests && requests >= @max_requests
  return false unless @idle_timeout
  return true  if     @idle_timeout.zero?
  last_used = Thread.current[@timeout_key][connection.object_id]
  Time.now - last_used > @idle_timeout
end

def finish connection, thread = Thread.current

def finish connection, thread = Thread.current
  if requests = thread[@request_key] then
    requests.delete connection.object_id
  end
  connection.finish
rescue IOError
end

def http_class # :nodoc:

:nodoc:
def http_class # :nodoc:
  if RUBY_VERSION > '2.0' then
    Net::HTTP
  elsif [:Artifice, :FakeWeb, :WebMock].any? { |klass|
           Object.const_defined?(klass)
        } or not @reuse_ssl_sessions then
      Net::HTTP
  else
    Bundler::Persistent::Net::HTTP::Persistent::SSLReuse
  end
end

def http_version uri

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

def idempotent? req

def idempotent? req
  case req
  when Net::HTTP::Delete, Net::HTTP::Get, Net::HTTP::Head,
       Net::HTTP::Options, Net::HTTP::Put, Net::HTTP::Trace then
    true
  end
end

def initialize name = nil, proxy = nil

def initialize name = nil, proxy = nil
  @name = name
  @debug_output     = nil
  @proxy_uri        = nil
  @no_proxy         = []
  @headers          = {}
  @override_headers = {}
  @http_versions    = {}
  @keep_alive       = 30
  @open_timeout     = nil
  @read_timeout     = nil
  @idle_timeout     = 5
  @max_requests     = nil
  @socket_options   = []
  @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
    Socket.const_defined? :TCP_NODELAY
  key = ['net_http_persistent', name].compact
  @generation_key     = [key, 'generations'    ].join('_').intern
  @ssl_generation_key = [key, 'ssl_generations'].join('_').intern
  @request_key        = [key, 'requests'       ].join('_').intern
  @timeout_key        = [key, 'timeouts'       ].join('_').intern
  @certificate        = nil
  @ca_file            = nil
  @private_key        = nil
  @ssl_version        = nil
  @verify_callback    = nil
  @verify_mode        = nil
  @cert_store         = nil
  @generation         = 0 # incremented when proxy URI changes
  @ssl_generation     = 0 # incremented when SSL session variables change
  if HAVE_OPENSSL then
    @verify_mode        = OpenSSL::SSL::VERIFY_PEER
    @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session
  end
  @retry_change_requests = false
  @ruby_1 = RUBY_VERSION < '2'
  @retried_on_ruby_2 = !@ruby_1
  self.proxy = proxy if proxy
end

def max_age # :nodoc:

:nodoc:
def max_age # :nodoc:
  return Time.now + 1 unless @idle_timeout
  Time.now - @idle_timeout
end

def normalize_uri uri

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

def pipeline uri, requests, &block # :yields: responses

:yields: responses
def pipeline uri, requests, &block # :yields: responses
  connection = connection_for uri
  connection.pipeline requests, &block
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 URI::HTTP then proxy
               when nil       then # ignore
               else raise ArgumentError, 'proxy must be :ENV or a URI::HTTP'
               end
  @no_proxy.clear
  if @proxy_uri then
    @proxy_args = [
      @proxy_uri.host,
      @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 = 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
  retried      = false
  bad_response = false
  req = request_setup req || uri
  connection = connection_for uri
  connection_id = connection.object_id
  begin
    Thread.current[@request_key][connection_id] += 1
    response = connection.request req, &block
    if connection_close?(req) or
       (response.http_version <= '1.0' and
        not connection_keep_alive?(response)) or
       connection_close?(response) then
      connection.finish
    end
  rescue Net::HTTPBadResponse => e
    message = error_message connection
    finish connection
    raise Error, "too many bad responses #{message}" if
      bad_response or not can_retry? req
    bad_response = true
    retry
  rescue *RETRIED_EXCEPTIONS => e # retried on ruby 2
    request_failed e, req, connection if
      retried or not can_retry? req, @retried_on_ruby_2
    reset connection
    retried = true
    retry
  rescue Errno::EINVAL, Errno::ETIMEDOUT => e # not retried on ruby 2
    request_failed e, req, connection if retried or not can_retry? req
    reset connection
    retried = true
    retry
  rescue Exception => e
    finish connection
    raise
  ensure
    Thread.current[@timeout_key][connection_id] = Time.now
  end
  @http_versions["#{uri.host}:#{uri.port}"] ||= response.http_version
  response
end

def request_failed exception, req, connection # :nodoc:

:nodoc:
def request_failed exception, req, connection # :nodoc:
  due_to = "(due to #{exception.message} - #{exception.class})"
  message = "too many connection resets #{due_to} #{error_message connection}"
  finish connection
  raise Error, message, exception.backtrace
end

def request_setup req_or_uri # :nodoc:

:nodoc:
def request_setup req_or_uri # :nodoc:
  req = if URI === req_or_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
  Thread.current[@request_key].delete connection.object_id
  Thread.current[@timeout_key].delete connection.object_id
  finish connection
  start connection
rescue Errno::ECONNREFUSED
  e = Error.new "connection refused: #{connection.address}:#{connection.port}"
  e.set_backtrace $@
  raise e
rescue Errno::EHOSTDOWN
  e = Error.new "host down: #{connection.address}:#{connection.port}"
  e.set_backtrace $@
  raise e
end

def shutdown thread = Thread.current

def shutdown thread = Thread.current
  generation = reconnect
  cleanup generation, thread, @generation_key
  ssl_generation = reconnect_ssl
  cleanup ssl_generation, thread, @ssl_generation_key
  thread[@request_key] = nil
  thread[@timeout_key] = nil
end

def shutdown_in_all_threads

def shutdown_in_all_threads
  Thread.list.each do |thread|
    shutdown thread
  end
  nil
end

def ssl connection

def ssl connection
  connection.use_ssl = true
  connection.ssl_version = @ssl_version if @ssl_version
  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
  if @ca_file then
    connection.ca_file = @ca_file
    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_cleanup generation # :nodoc:

:nodoc:
def ssl_cleanup generation # :nodoc:
  cleanup generation, Thread.current, @ssl_generation_key
end

def ssl_version= ssl_version

def ssl_version= ssl_version
  @ssl_version = ssl_version
  reconnect_ssl
end if RUBY_VERSION > '1.9'

def start connection

def start connection
  connection.set_debug_output @debug_output if @debug_output
  connection.open_timeout = @open_timeout if @open_timeout
  connection.start
  socket = connection.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_mode= verify_mode

def verify_mode= verify_mode
  @verify_mode = verify_mode
  reconnect_ssl
end