1require "test/unit"
2require "net/http"
3require "webrick"
4require "webrick/httpproxy"
5begin
6  require "webrick/ssl"
7  require "net/https"
8  require File.expand_path("../openssl/utils.rb", File.dirname(__FILE__))
9rescue LoadError
10  # test_connect will be skipped
11end
12require File.expand_path("utils.rb", File.dirname(__FILE__))
13
14class TestWEBrickHTTPProxy < Test::Unit::TestCase
15  def test_fake_proxy
16    assert_nil(WEBrick::FakeProxyURI.scheme)
17    assert_nil(WEBrick::FakeProxyURI.host)
18    assert_nil(WEBrick::FakeProxyURI.port)
19    assert_nil(WEBrick::FakeProxyURI.path)
20    assert_nil(WEBrick::FakeProxyURI.userinfo)
21    assert_raise(NoMethodError){ WEBrick::FakeProxyURI.foo }
22  end
23
24  def test_proxy
25    # Testing GET or POST to the proxy server
26    # Note that the proxy server works as the origin server.
27    #                    +------+
28    #                    V      |
29    #  client -------> proxy ---+
30    #        GET / POST     GET / POST
31    #
32    proxy_handler_called = request_handler_called = 0
33    config = {
34      :ServerName => "localhost.localdomain",
35      :ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1 },
36      :RequestCallback => Proc.new{|req, res| request_handler_called += 1 }
37    }
38    TestWEBrick.start_httpproxy(config){|server, addr, port, log|
39      server.mount_proc("/"){|req, res|
40        res.body = "#{req.request_method} #{req.path} #{req.body}"
41      }
42      http = Net::HTTP.new(addr, port, addr, port)
43
44      req = Net::HTTP::Get.new("/")
45      http.request(req){|res|
46        assert_equal("1.1 localhost.localdomain:#{port}", res["via"], log.call)
47        assert_equal("GET / ", res.body, log.call)
48      }
49      assert_equal(1, proxy_handler_called, log.call)
50      assert_equal(2, request_handler_called, log.call)
51
52      req = Net::HTTP::Head.new("/")
53      http.request(req){|res|
54        assert_equal("1.1 localhost.localdomain:#{port}", res["via"], log.call)
55        assert_nil(res.body, log.call)
56      }
57      assert_equal(2, proxy_handler_called, log.call)
58      assert_equal(4, request_handler_called, log.call)
59
60      req = Net::HTTP::Post.new("/")
61      req.body = "post-data"
62      http.request(req){|res|
63        assert_equal("1.1 localhost.localdomain:#{port}", res["via"], log.call)
64        assert_equal("POST / post-data", res.body, log.call)
65      }
66      assert_equal(3, proxy_handler_called, log.call)
67      assert_equal(6, request_handler_called, log.call)
68    }
69  end
70
71  def test_no_proxy
72    # Testing GET or POST to the proxy server without proxy request.
73    #
74    #  client -------> proxy
75    #        GET / POST
76    #
77    proxy_handler_called = request_handler_called = 0
78    config = {
79      :ServerName => "localhost.localdomain",
80      :ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1 },
81      :RequestCallback => Proc.new{|req, res| request_handler_called += 1 }
82    }
83    TestWEBrick.start_httpproxy(config){|server, addr, port, log|
84      server.mount_proc("/"){|req, res|
85        res.body = "#{req.request_method} #{req.path} #{req.body}"
86      }
87      http = Net::HTTP.new(addr, port)
88
89      req = Net::HTTP::Get.new("/")
90      http.request(req){|res|
91        assert_nil(res["via"], log.call)
92        assert_equal("GET / ", res.body, log.call)
93      }
94      assert_equal(0, proxy_handler_called, log.call)
95      assert_equal(1, request_handler_called, log.call)
96
97      req = Net::HTTP::Head.new("/")
98      http.request(req){|res|
99        assert_nil(res["via"], log.call)
100        assert_nil(res.body, log.call)
101      }
102      assert_equal(0, proxy_handler_called, log.call)
103      assert_equal(2, request_handler_called, log.call)
104
105      req = Net::HTTP::Post.new("/")
106      req.body = "post-data"
107      http.request(req){|res|
108        assert_nil(res["via"], log.call)
109        assert_equal("POST / post-data", res.body, log.call)
110      }
111      assert_equal(0, proxy_handler_called, log.call)
112      assert_equal(3, request_handler_called, log.call)
113    }
114  end
115
116  def make_certificate(key, cn)
117    subject = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=#{cn}")
118    exts = [
119      ["keyUsage", "keyEncipherment,digitalSignature", true],
120    ]
121    cert = OpenSSL::TestUtils.issue_cert(
122      subject, key, 1, Time.now, Time.now + 3600, exts,
123      nil, nil, OpenSSL::Digest::SHA1.new
124    )
125    return cert
126  end
127
128  def test_connect
129    # Testing CONNECT to proxy server
130    #
131    #  client -----------> proxy -----------> https
132    #    1.     CONNECT          establish TCP
133    #    2.   ---- establish SSL session --->
134    #    3.   ------- GET or POST ---------->
135    #
136    key = OpenSSL::TestUtils::TEST_KEY_RSA1024
137    cert = make_certificate(key, "127.0.0.1")
138    s_config = {
139      :SSLEnable =>true,
140      :ServerName => "localhost",
141      :SSLCertificate => cert,
142      :SSLPrivateKey => key,
143    }
144    config = {
145      :ServerName => "localhost.localdomain",
146      :RequestCallback => Proc.new{|req, res|
147        assert_equal("CONNECT", req.request_method)
148      },
149    }
150    TestWEBrick.start_httpserver(s_config){|s_server, s_addr, s_port, s_log|
151      s_server.mount_proc("/"){|req, res|
152        res.body = "SSL #{req.request_method} #{req.path} #{req.body}"
153      }
154      TestWEBrick.start_httpproxy(config){|server, addr, port, log|
155        http = Net::HTTP.new("127.0.0.1", s_port, addr, port)
156        http.use_ssl = true
157        http.verify_callback = Proc.new do |preverify_ok, store_ctx|
158          store_ctx.current_cert.to_der == cert.to_der
159        end
160
161        req = Net::HTTP::Get.new("/")
162        http.request(req){|res|
163          assert_equal("SSL GET / ", res.body, s_log.call + log.call)
164        }
165
166        req = Net::HTTP::Post.new("/")
167        req.body = "post-data"
168        http.request(req){|res|
169          assert_equal("SSL POST / post-data", res.body, s_log.call + log.call)
170        }
171      }
172    }
173  end if defined?(OpenSSL)
174
175  def test_upstream_proxy
176    # Testing GET or POST through the upstream proxy server
177    # Note that the upstream proxy server works as the origin server.
178    #                                   +------+
179    #                                   V      |
180    #  client -------> proxy -------> proxy ---+
181    #        GET / POST     GET / POST     GET / POST
182    #
183    up_proxy_handler_called = up_request_handler_called = 0
184    proxy_handler_called = request_handler_called = 0
185    up_config = {
186      :ServerName => "localhost.localdomain",
187      :ProxyContentHandler => Proc.new{|req, res| up_proxy_handler_called += 1},
188      :RequestCallback => Proc.new{|req, res| up_request_handler_called += 1}
189    }
190    TestWEBrick.start_httpproxy(up_config){|up_server, up_addr, up_port, up_log|
191      up_server.mount_proc("/"){|req, res|
192        res.body = "#{req.request_method} #{req.path} #{req.body}"
193      }
194      config = {
195        :ServerName => "localhost.localdomain",
196        :ProxyURI => URI.parse("http://localhost:#{up_port}"),
197        :ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1},
198        :RequestCallback => Proc.new{|req, res| request_handler_called += 1},
199      }
200      TestWEBrick.start_httpproxy(config){|server, addr, port, log|
201        http = Net::HTTP.new(up_addr, up_port, addr, port)
202
203        req = Net::HTTP::Get.new("/")
204        http.request(req){|res|
205          skip res.message unless res.code == '200'
206          via = res["via"].split(/,\s+/)
207          assert(via.include?("1.1 localhost.localdomain:#{up_port}"), up_log.call + log.call)
208          assert(via.include?("1.1 localhost.localdomain:#{port}"), up_log.call + log.call)
209          assert_equal("GET / ", res.body)
210        }
211        assert_equal(1, up_proxy_handler_called, up_log.call + log.call)
212        assert_equal(2, up_request_handler_called, up_log.call + log.call)
213        assert_equal(1, proxy_handler_called, up_log.call + log.call)
214        assert_equal(1, request_handler_called, up_log.call + log.call)
215
216        req = Net::HTTP::Head.new("/")
217        http.request(req){|res|
218          via = res["via"].split(/,\s+/)
219          assert(via.include?("1.1 localhost.localdomain:#{up_port}"), up_log.call + log.call)
220          assert(via.include?("1.1 localhost.localdomain:#{port}"), up_log.call + log.call)
221          assert_nil(res.body, up_log.call + log.call)
222        }
223        assert_equal(2, up_proxy_handler_called, up_log.call + log.call)
224        assert_equal(4, up_request_handler_called, up_log.call + log.call)
225        assert_equal(2, proxy_handler_called, up_log.call + log.call)
226        assert_equal(2, request_handler_called, up_log.call + log.call)
227
228        req = Net::HTTP::Post.new("/")
229        req.body = "post-data"
230        http.request(req){|res|
231          via = res["via"].split(/,\s+/)
232          assert(via.include?("1.1 localhost.localdomain:#{up_port}"), up_log.call + log.call)
233          assert(via.include?("1.1 localhost.localdomain:#{port}"), up_log.call + log.call)
234          assert_equal("POST / post-data", res.body, up_log.call + log.call)
235        }
236        assert_equal(3, up_proxy_handler_called, up_log.call + log.call)
237        assert_equal(6, up_request_handler_called, up_log.call + log.call)
238        assert_equal(3, proxy_handler_called, up_log.call + log.call)
239        assert_equal(3, request_handler_called, up_log.call + log.call)
240
241        if defined?(OpenSSL)
242          # Testing CONNECT to the upstream proxy server
243          #
244          #  client -------> proxy -------> proxy -------> https
245          #    1.   CONNECT        CONNECT      establish TCP
246          #    2.   -------- establish SSL session ------>
247          #    3.   ---------- GET or POST -------------->
248          #
249          key = OpenSSL::TestUtils::TEST_KEY_RSA1024
250          cert = make_certificate(key, "127.0.0.1")
251          s_config = {
252            :SSLEnable =>true,
253            :ServerName => "localhost",
254            :SSLCertificate => cert,
255            :SSLPrivateKey => key,
256          }
257          TestWEBrick.start_httpserver(s_config){|s_server, s_addr, s_port, s_log|
258            s_server.mount_proc("/"){|req2, res|
259              res.body = "SSL #{req2.request_method} #{req2.path} #{req2.body}"
260            }
261            http = Net::HTTP.new("127.0.0.1", s_port, addr, port, up_log.call + log.call + s_log.call)
262            http.use_ssl = true
263            http.verify_callback = Proc.new do |preverify_ok, store_ctx|
264              store_ctx.current_cert.to_der == cert.to_der
265            end
266
267            req2 = Net::HTTP::Get.new("/")
268            http.request(req2){|res|
269              assert_equal("SSL GET / ", res.body, up_log.call + log.call + s_log.call)
270            }
271
272            req2 = Net::HTTP::Post.new("/")
273            req2.body = "post-data"
274            http.request(req2){|res|
275              assert_equal("SSL POST / post-data", res.body, up_log.call + log.call + s_log.call)
276            }
277          }
278        end
279      }
280    }
281  end
282end
283