require 'uri' require 'openssl' require 'puppet/network/http' module Puppet::Util::HttpProxy def self.proxy(uri) if self.no_proxy?(uri) proxy_class = Net::HTTP::Proxy(nil) else proxy_class = Net::HTTP::Proxy(self.http_proxy_host, self.http_proxy_port, self.http_proxy_user, self.http_proxy_password) end return proxy_class.new(uri.host, uri.port) end def self.http_proxy_env # Returns a URI object if proxy is set, or nil proxy_env = ENV["http_proxy"] || ENV["HTTP_PROXY"] begin return URI.parse(proxy_env) if proxy_env rescue URI::InvalidURIError return nil end return nil end # The documentation around the format of the no_proxy variable seems # inconsistent. Some suggests the use of the * as a way of matching any # hosts under a domain, e.g.: # *.example.com # Other documentation suggests that just a leading '.' indicates a domain # level exclusion, e.g.: # .example.com # We'll accommodate both here. def self.no_proxy?(dest) no_proxy = self.no_proxy unless no_proxy return false end unless dest.is_a? URI begin dest = URI.parse(dest) rescue URI::InvalidURIError return false end end no_proxy.split(/\s*,\s*/).each do |d| host, port = d.split(':') host = Regexp.escape(host).gsub('\*', '.*') #If the host of this no_proxy value starts with '.', this entry is #a domain level entry. Don't pin the regex to the beginning of the entry. #If it does not start with a '.' then it is a host specific entry and #should be matched to the destination starting at the beginning. unless host =~ /^\\\./ host = "^#{host}" end #If this no_proxy entry specifies a port, we want to match it against #the destination port. Otherwise just match hosts. if port no_proxy_regex = %r(#{host}:#{port}$) dest_string = "#{dest.host}:#{dest.port}" else no_proxy_regex = %r(#{host}$) dest_string = "#{dest.host}" end if no_proxy_regex.match(dest_string) return true end end return false end def self.http_proxy_host env = self.http_proxy_env if env and env.host return env.host end if Puppet.settings[:http_proxy_host] == 'none' return nil end return Puppet.settings[:http_proxy_host] end def self.http_proxy_port env = self.http_proxy_env if env and env.port return env.port end return Puppet.settings[:http_proxy_port] end def self.http_proxy_user env = self.http_proxy_env if env and env.user return env.user end if Puppet.settings[:http_proxy_user] == 'none' return nil end return Puppet.settings[:http_proxy_user] end def self.http_proxy_password env = self.http_proxy_env if env and env.password return env.password end if Puppet.settings[:http_proxy_user] == 'none' or Puppet.settings[:http_proxy_password] == 'none' return nil end return Puppet.settings[:http_proxy_password] end def self.no_proxy no_proxy_env = ENV["no_proxy"] || ENV["NO_PROXY"] if no_proxy_env return no_proxy_env end if Puppet.settings[:no_proxy] == 'none' return nil end return Puppet.settings[:no_proxy] end # Return a Net::HTTP::Proxy object. # # This method optionally configures SSL correctly if the URI scheme is # 'https', including setting up the root certificate store so remote server # SSL certificates can be validated. # # @param [URI] uri The URI that is to be accessed. # @return [Net::HTTP::Proxy] object constructed tailored for the passed URI def self.get_http_object(uri) proxy = proxy(uri) if uri.scheme == 'https' cert_store = OpenSSL::X509::Store.new cert_store.set_default_paths proxy.use_ssl = true proxy.verify_mode = OpenSSL::SSL::VERIFY_PEER proxy.cert_store = cert_store end if Puppet[:http_debug] proxy.set_debug_output($stderr) end proxy.open_timeout = Puppet[:http_connect_timeout] proxy.read_timeout = Puppet[:http_read_timeout] proxy end # Retrieve a document through HTTP(s), following redirects if necessary. The # returned response body may be compressed, and it is the caller's # responsibility to decompress it based on the 'content-encoding' header. # # Based on the the client implementation in the HTTP pool. # # @see Puppet::Network::HTTP::Connection#request_with_redirects # # @param [URI] uri The address of the resource to retrieve. # @param [symbol] method The name of the Net::HTTP method to use, typically :get, :head, :post etc. # @param [FixNum] redirect_limit The number of redirections that can be followed. # @return [Net::HTTPResponse] a response object def self.request_with_redirects(uri, method, redirect_limit = 10, &block) current_uri = uri response = nil 0.upto(redirect_limit) do |redirection| proxy = get_http_object(current_uri) headers = { 'Accept' => '*/*', 'User-Agent' => Puppet[:http_user_agent] } if Puppet.features.zlib? headers.merge!({"Accept-Encoding" => Puppet::Network::HTTP::Compression::ACCEPT_ENCODING}) end response = proxy.send(:head, current_uri.path, headers) Puppet.debug("HTTP HEAD request to #{current_uri} returned #{response.code} #{response.message}") if [301, 302, 307].include?(response.code.to_i) # handle the redirection current_uri = URI.parse(response['location']) next end if method != :head if block_given? response = proxy.send("request_#{method}".to_sym, current_uri.path, headers, &block) else response = proxy.send(method, current_uri.path, headers) end Puppet.debug("HTTP #{method.to_s.upcase} request to #{current_uri} returned #{response.code} #{response.message}") end return response end raise RedirectionLimitExceededException, _("Too many HTTP redirections for %{uri}") % { uri: uri } end end