class Pacproxy::Pacproxy

Pacproxy::Pacproxy represent http/https proxy server

Constants

HOP_BY_HOP

This method is mainly from WEBrick::HTTPProxyServer. proxy-authenticate can be transferred from a upstream proxy server to a client see: github.com/ruby/ruby/blob/trunk/lib/webrick/httpproxy.rb

SHOULD_NOT_TRANSFER

Public Class Methods

new(config = {}, default = WEBrick::Config::HTTP) click to toggle source
Calls superclass method
# File lib/pacproxy/pacproxy.rb, line 10
def initialize(config = {}, default = WEBrick::Config::HTTP)
  super({ Port: config['port'], Logger: general_logger }, default)
  @auth = config['auth']
  return unless config['pac_file'] && config['pac_file']['location']

  @pac = PacFile.new(config['pac_file']['location'],
                     config['pac_file']['update_interval'])
end

Public Instance Methods

create_proxy_uri(proxy, header) click to toggle source
# File lib/pacproxy/pacproxy.rb, line 33
def create_proxy_uri(proxy, header)
  return nil unless proxy
  return URI.parse("http://#{proxy}") unless
    @auth || header.key?('proxy-authorization')

  if @auth
    basic_auth = "#{@auth['user']}:#{@auth['password']}"
  elsif header.key?('proxy-authorization')
    auth = header['proxy-authorization'][0]
    pattern = /basic (\S+)/i
    basic_auth = pattern.match(auth)[1].unpack('m').first
    header.delete('proxy-authorization')
  end

  URI.parse("http://#{basic_auth}@#{proxy}")
end
do_CONNECT(req, res) click to toggle source

This method is mainly from WEBrick::HTTPProxyServer. To allow upstream proxy authentication, it operate 407 response from an upstream proxy. see: github.com/ruby/ruby/blob/trunk/lib/webrick/httpproxy.rb rubocop:disable all

# File lib/pacproxy/pacproxy.rb, line 55
def do_CONNECT(req, res)
  # Proxy Authentication
  proxy_auth(req, res)

  ua = Thread.current[:WEBrickSocket]  # User-Agent
  raise WEBrick::HTTPStatus::InternalServerError,
    "[BUG] cannot get socket" unless ua

  host, port = req.unparsed_uri.split(":", 2)
  # Proxy authentication for upstream proxy server
  if proxy = proxy_uri(req, res)
    proxy_request_line = "CONNECT #{host}:#{port} HTTP/1.0"
    if proxy.userinfo
      credentials = "Basic " + [proxy.userinfo].pack("m").delete("\n")
    end
    host, port = proxy.host, proxy.port
  end

  begin
    @logger.debug("CONNECT: upstream proxy is `#{host}:#{port}'.")
    os = TCPSocket.new(host, port)     # origin server

    if proxy
      @logger.debug("CONNECT: sending a Request-Line")
      os << proxy_request_line << WEBrick::CRLF
      @logger.debug("CONNECT: > #{proxy_request_line}")
      if credentials
        @logger.debug("CONNECT: sending a credentials")
        os << "Proxy-Authorization: " << credentials << WEBrick::CRLF
      end
      os << WEBrick::CRLF
      proxy_status_line = os.gets(WEBrick::LF)
      @logger.debug("CONNECT: read a Status-Line form the upstream server")
      @logger.debug("CONNECT: < #{proxy_status_line}")
      if /^HTTP\/\d+\.\d+\s+(?<st>200|407)\s*/ =~ proxy_status_line
        res.status = st.to_i
        while line = os.gets(WEBrick::LF)
          res.header['Proxy-Authenticate'] =
            line.split(':')[1] if /Proxy-Authenticate/i =~ line
          break if /\A(#{WEBrick::CRLF}|#{WEBrick::LF})\z/om =~ line
        end
      else
        raise WEBrick::HTTPStatus::BadGateway
      end
    end
    @logger.debug("CONNECT #{host}:#{port}: succeeded")
  rescue => ex
    @logger.debug("CONNECT #{host}:#{port}: failed `#{ex.message}'")
    res.set_error(ex)
    raise WEBrick::HTTPStatus::EOFError
  ensure
    if handler = @config[:ProxyContentHandler]
      handler.call(req, res)
    end
    res.send_response(ua)
    accesslog(req, res)

    # Should clear request-line not to send the response twice.
    # see: HTTPServer#run
    req.parse(WEBrick::NullReader) rescue nil
  end

  begin
    while fds = IO::select([ua, os])
      if fds[0].member?(ua)
        buf = ua.sysread(1024);
        @logger.debug("CONNECT: #{buf.bytesize} byte from User-Agent")
        os.syswrite(buf)
      elsif fds[0].member?(os)
        buf = os.sysread(1024);
        @logger.debug("CONNECT: #{buf.bytesize} byte from #{host}:#{port}")
        ua.syswrite(buf)
      end
    end
  rescue
    os.close
    @logger.debug("CONNECT #{host}:#{port}: closed")
  end

  raise WEBrick::HTTPStatus::EOFError
end
proxy_auth(req, res) click to toggle source

rubocop:enable all

# File lib/pacproxy/pacproxy.rb, line 138
def proxy_auth(req, res)
  @config[:ProxyAuthProc].call(req, res) if @config[:ProxyAuthProc]
end
proxy_uri(req, res) click to toggle source
Calls superclass method
# File lib/pacproxy/pacproxy.rb, line 24
def proxy_uri(req, res)
  super(req, res)
  return unless @pac

  proxy_line = @pac.find(request_uri(req))
  proxy = lookup_proxy_uri(proxy_line)
  create_proxy_uri(proxy, req.header)
end
shutdown() click to toggle source
Calls superclass method
# File lib/pacproxy/pacproxy.rb, line 19
def shutdown
  @pac.shutdown if @pac
  super
end

Private Instance Methods

choose_header(src, dst) click to toggle source
# File lib/pacproxy/pacproxy.rb, line 169
def choose_header(src, dst)
  connections = split_field(src['connection'])
  src.each do |key, value|
    key = key.downcase
    next if HOP_BY_HOP.member?(key)        || # RFC2616: 13.5.1
      connections.member?(key)             || # RFC2616: 14.10
      SHOULD_NOT_TRANSFER.member?(key)        # pragmatics

    dst[key] = value
  end
end
do_PUT(req, res) click to toggle source

allow PUT method on proxy server method names for webrick is indicated by rubocop rubocop:disable all

# File lib/pacproxy/pacproxy.rb, line 189
def do_PUT(req, res)
  perform_proxy_request(req, res) do |http, path, header|
    http.put(path, req.body || '', header)
  end
end
lookup_proxy_uri(proxy_line) click to toggle source
# File lib/pacproxy/pacproxy.rb, line 152
def lookup_proxy_uri(proxy_line)
  case proxy_line
  when /^DIRECT/
    nil
  when /PROXY/
    primary_proxy = proxy_line.split(';')[0]
    /PROXY (.*)/.match(primary_proxy)[1]
  end
end
perform_proxy_request(req, res) click to toggle source
Calls superclass method
# File lib/pacproxy/pacproxy.rb, line 181
def perform_proxy_request(req, res)
  super
  accesslog(req, res)
end
request_uri(request) click to toggle source
# File lib/pacproxy/pacproxy.rb, line 144
def request_uri(request)
  if 'CONNECT' == request.request_method
    "https://#{request.unparsed_uri}/"
  else
    request.unparsed_uri
  end
end