require 'net/protocol'
require 'net/http'

module Net
    
  class HTTP
    
    class << self
      def new_with_configuration (address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil)
        config_options = Configuration.current
        if config_options
          if Configuration.no_proxy?(address, config_options)
            p_addr = nil
            p_port = nil
            p_user = nil
            p_pass = nil
          elsif p_addr.nil? and config_options[:proxy_host]
            p_addr = config_options[:proxy_host]
            p_port = config_options[:proxy_port].to_i
            p_user = config_options[:proxy_user]
            p_pass = config_options[:proxy_password]
          end
        end
        
        http = HTTP.new_without_configuration(address, port, p_addr, p_port, p_user, p_pass)
      
        if config_options
          http.open_timeout = config_options[:open_timeout] if config_options[:open_timeout]
          http.read_timeout = config_options[:read_timeout] if config_options[:read_timeout]
        end
      
        return http
      end
      
      alias_method :new_without_configuration, :new
      alias_method :new, :new_with_configuration
      
    end
  
    # The Configuration class encapsulates a set of HTTP defaults. The configuration
    # can made either global or all requests, or it can be applied only within a block.
    # Configuration blocks can also set an additional set of options which take precedence
    # over the initialization options.
    #
    # Available options are :proxy_host, :proxy_port, :proxy_user, :proxy_password, :no_proxy,
    # :read_timeout, :open_timeout, and :proxy. This last value can either contain a proxy string
    # or the symbol :none for no proxy or :environment to use the values in the HTTP_PROXY/http_proxy
    # and NO_PROXY/no_proxy environment variables.
    #
    # If you specify a proxy, but don't want it to be used for certain hosts, specify the domain names
    # in the :no_proxy option. This can either be an array or a comma delimited string. A request to a
    # host name which ends with any of these values will not be proxied.
    #
    # The normal functionality for Net::HTTP is still available, so you can set proxies
    # and timeouts manually if needed. Because of the way in which https calls are made, you cannot
    # configure a special proxy just for https calls.
    class Configuration
    
      def initialize (options = {})
        @default_options = options.dup
        expand_proxy_config!(@default_options)
      end
    
      # Get the specified option for the configuration.
      def [] (name)
        @default_options[name]
      end
      
      # Apply the configuration to the block. If any options are provided, they will override the default options
      # for the configuration.
      def apply (options = {})
        options = @default_options.merge(options)
        expand_proxy_config!(options)
        Thread.current[:net_http_configuration] ||= []
        Thread.current[:net_http_configuration].push(options)
        begin
          return yield
        ensure
          Thread.current[:net_http_configuration].pop
        end
      end
      
      class << self
        def no_proxy? (host, options)
          return false unless options[:no_proxy].kind_of?(Array)
        
          host = host.downcase
          options[:no_proxy].each do |pattern|
            pattern = pattern.downcase
            return true if host[-pattern.length, pattern.length] == pattern
          end
        
          return false
        end
      
        # Set the options for a global configuration used for all HTTP requests. The global configuration can be cleared
        # by setting nil
        def set_global (options)
          if options
            @global = Configuration.new(options)
          else
            @global = nil
          end
        end
      
        def global
          @global
        end
      
        # Get the current configuration that is in scope.
        def current
          stack = Thread.current[:net_http_configuration]
          config = stack.last if stack
          config || global
        end
      end
      
      private
      
      def expand_proxy_config! (options)
        proxy_config = options[:proxy]
        if proxy_config
          options.delete(:proxy)
          if proxy_config == :environment
            parse_proxy!(ENV['HTTP_PROXY'] || ENV['http_proxy'], options)
            options[:no_proxy] = ENV['NO_PROXY'] || ENV['no_proxy']
          elsif proxy_config == :none
            options[:proxy_user] = nil
            options[:proxy_password] = nil
            options[:proxy_host] = nil
            options[:proxy_port] = nil
            options[:no_proxy] = nil
          else
            parse_proxy!(proxy_config, options)
          end
        end
        parse_no_proxy!(options[:no_proxy], options)
      end
      
      def parse_proxy! (proxy, options)
        return unless proxy and proxy.length > 0
        proxy_info = /(http:\/\/)?(([^:]+):([^@]+)@)?([^:]+)(:(\d+))?/i.match(proxy)
        if proxy_info
          options[:proxy_user] = proxy_info[3]
          options[:proxy_password] = proxy_info[4]
          options[:proxy_host] = proxy_info[5]
          options[:proxy_port] = proxy_info[7].to_i if proxy_info[7]
        end
      end
      
      def parse_no_proxy! (no_proxy, options)
        return unless no_proxy and no_proxy.length > 0
        if no_proxy.kind_of?(Array)
          options[:no_proxy] = no_proxy.dup
        else
          options[:no_proxy] = no_proxy.split(/\s*,\s*/)
        end
      end
    end
    
  end
  
end
      