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
17
# 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

Instance Method Details

#create_proxy_uri(proxy, header) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 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) ⇒ 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)


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
128
129
130
131
132
133
134
135
# 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)
    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



138
139
140
# 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) ⇒ Object



24
25
26
27
28
29
30
31
# 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

#shutdownObject



19
20
21
22
# File 'lib/pacproxy/pacproxy.rb', line 19

def shutdown
  @pac.shutdown
  super
end