Class: Pacproxy::Pacproxy

Inherits:
WEBrick::HTTPProxyServer
  • Object
show all
Includes:
Loggable
Defined in:
lib/pacproxy/pacproxy.rb

Overview

Pacproxy::Pacproxy represent http/https proxy server

Instance Method Summary collapse

Methods included from Loggable

#access_logger, #accesslog, #debug, #error, #fatal, #general_logger, #info, #lwarn

Constructor Details

#initialize(config = {}, default = WEBrick::Config::HTTP) ⇒ Pacproxy

Returns a new instance of Pacproxy.



10
11
12
13
14
15
16
# File 'lib/pacproxy/pacproxy.rb', line 10

def initialize(config = {}, default = WEBrick::Config::HTTP)
  super({ Port: config['port'], Logger: general_logger }, default)
  return unless config['pac_file'] && config['pac_file']['location']

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

Instance Method Details

#create_proxy_uri(proxy, header) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/pacproxy/pacproxy.rb', line 27

def create_proxy_uri(proxy, header)
  return nil unless proxy
  return URI.parse("http://#{proxy}") unless
    header.key?('proxy-authorization')

  auth = header['proxy-authorization'][0]
  pattern = /basic (\S+)/i
  basic_auth = pattern.match(auth)[1]
  header.delete('proxy-authorization')

  return URI.parse("http://#{proxy}") unless basic_auth

  URI.parse("http://#{basic_auth.unpack('m').first}@#{proxy}")
end

#do_CONNECT(req, res) ⇒ Object

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

Raises:

  • (WEBrick::HTTPStatus::InternalServerError)


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/pacproxy/pacproxy.rb', line 47

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)
    access_log(@config, 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) ⇒ Object

rubocop:enable all



130
131
132
# File 'lib/pacproxy/pacproxy.rb', line 130

def proxy_auth(req, res)
  @config[:ProxyAuthProc].call(req, res) if @config[:ProxyAuthProc]
end

#proxy_uri(req, res) ⇒ Object



18
19
20
21
22
23
24
25
# File 'lib/pacproxy/pacproxy.rb', line 18

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