mitmproxy/test/mitmproxy/test_server.py

1073 lines
35 KiB
Python
Raw Normal View History

import os
2015-05-30 00:03:28 +00:00
import socket
import time
2016-07-08 06:50:55 +00:00
import types
2015-07-15 21:19:01 +00:00
from OpenSSL import SSL
2015-09-16 16:45:22 +00:00
from netlib.exceptions import HttpReadDisconnect, HttpException
2015-08-27 15:35:53 +00:00
from netlib.tcp import Address
2015-07-15 21:19:01 +00:00
2015-08-01 08:40:19 +00:00
import netlib.tutils
2015-07-15 21:19:01 +00:00
from netlib import tcp, http, socks
2014-09-04 17:08:54 +00:00
from netlib.certutils import SSLCert
2016-03-26 08:00:51 +00:00
from netlib.http import authentication, http1
2015-09-16 16:45:22 +00:00
from netlib.tutils import raises
2016-02-16 19:59:33 +00:00
from pathod import pathoc, pathod
2015-07-15 21:19:01 +00:00
from mitmproxy.builtins import script
from mitmproxy import controller
from mitmproxy.proxy.config import HostMatcher, parse_server_spec
2016-05-11 17:13:57 +00:00
from mitmproxy.models import Error, HTTPResponse, HTTPFlow
2015-08-30 13:27:29 +00:00
2016-02-02 12:25:31 +00:00
from . import tutils, tservers
2011-03-05 22:21:31 +00:00
"""
Note that the choice of response code in these tests matters more than you
might think. libcurl treats a 304 response code differently from, say, a
200 response code - it will correctly terminate a 304 response with no
content-length header, whereas it will block forever waiting for content
for a 200 response.
"""
2015-05-30 00:03:28 +00:00
2013-02-28 20:05:39 +00:00
class CommonMixin:
2016-01-27 09:12:18 +00:00
def test_large(self):
2015-05-30 00:03:28 +00:00
assert len(self.pathod("200:b@50k").content) == 1024 * 50
2014-10-18 23:26:08 +00:00
@staticmethod
def wait_until_not_live(flow):
"""
Race condition: We don't want to replay the flow while it is still live.
"""
s = time.time()
while flow.live:
time.sleep(0.001)
if time.time() - s > 5:
raise RuntimeError("Flow is live for too long.")
def test_replay(self):
assert self.pathod("304").status_code == 304
2016-02-16 20:15:50 +00:00
if isinstance(self, tservers.HTTPUpstreamProxyTest) and self.ssl:
assert len(self.master.state.view) == 2
else:
assert len(self.master.state.view) == 1
l = self.master.state.view[-1]
assert l.response.status_code == 304
l.request.path = "/p/305"
2014-10-18 23:26:08 +00:00
self.wait_until_not_live(l)
rt = self.master.replay_request(l, block=True)
assert l.response.status_code == 305
# Disconnect error
l.request.path = "/p/305:d0"
rt = self.master.replay_request(l, block=True)
assert not rt
2016-02-16 20:15:50 +00:00
if isinstance(self, tservers.HTTPUpstreamProxyTest):
assert l.response.status_code == 502
else:
assert l.error
# Port error
l.request.port = 1
# In upstream mode, we get a 502 response from the upstream proxy server.
2015-05-30 00:03:28 +00:00
# In upstream mode with ssl, the replay will fail as we cannot establish
# SSL with the upstream proxy.
rt = self.master.replay_request(l, block=True)
assert not rt
2016-02-16 20:15:50 +00:00
if isinstance(self, tservers.HTTPUpstreamProxyTest):
assert l.response.status_code == 502
else:
assert l.error
2013-02-28 20:05:39 +00:00
def test_http(self):
f = self.pathod("304")
assert f.status_code == 304
2011-03-05 22:21:31 +00:00
2015-05-30 00:03:28 +00:00
# In Upstream mode with SSL, we may already have a previous CONNECT
# request.
l = self.master.state.view[-1]
assert l.client_conn.address
2013-02-28 20:05:39 +00:00
assert "host" in l.request.headers
assert l.response.status_code == 304
def test_invalid_http(self):
t = tcp.TCPClient(("127.0.0.1", self.proxy.port))
t.connect()
2016-06-22 19:04:38 +00:00
t.wfile.write(b"invalid\r\n\r\n")
t.wfile.flush()
line = t.rfile.readline()
2016-06-22 19:04:38 +00:00
assert (b"Bad Request" in line) or (b"Bad Gateway" in line)
2013-02-28 20:05:39 +00:00
def test_sni(self):
if not self.ssl:
return
2016-07-07 04:03:17 +00:00
f = self.pathod("304", sni="testserver.com")
assert f.status_code == 304
log = self.server.last_log()
2016-07-07 04:03:17 +00:00
assert log["request"]["sni"] == "testserver.com"
2015-05-30 00:03:28 +00:00
2014-09-04 17:08:54 +00:00
class TcpMixin:
2016-01-27 09:12:18 +00:00
2014-09-04 17:08:54 +00:00
def _ignore_on(self):
2014-10-18 16:29:35 +00:00
assert not hasattr(self, "_ignore_backup")
self._ignore_backup = self.config.check_ignore
2015-05-30 00:03:28 +00:00
self.config.check_ignore = HostMatcher(
[".+:%s" % self.server.port] + self.config.check_ignore.patterns)
2014-09-04 17:08:54 +00:00
def _ignore_off(self):
2014-10-18 16:29:35 +00:00
assert hasattr(self, "_ignore_backup")
self.config.check_ignore = self._ignore_backup
del self._ignore_backup
2014-09-04 17:08:54 +00:00
def test_ignore(self):
n = self.pathod("304")
2014-09-04 17:08:54 +00:00
self._ignore_on()
i = self.pathod("305")
i2 = self.pathod("306")
2014-09-04 17:08:54 +00:00
self._ignore_off()
self.master.event_queue.join()
2015-09-21 16:31:30 +00:00
assert n.status_code == 304
assert i.status_code == 305
assert i2.status_code == 306
assert any(f.response.status_code == 304 for f in self.master.state.flows)
assert not any(f.response.status_code == 305 for f in self.master.state.flows)
assert not any(f.response.status_code == 306 for f in self.master.state.flows)
2014-09-04 17:08:54 +00:00
# Test that we get the original SSL cert
if self.ssl:
i_cert = SSLCert(i.sslinfo.certchain[0])
i2_cert = SSLCert(i2.sslinfo.certchain[0])
n_cert = SSLCert(n.sslinfo.certchain[0])
assert i_cert == i2_cert
assert i_cert != n_cert
# Test Non-HTTP traffic
spec = "200:i0,@100:d0" # this results in just 100 random bytes
2015-05-30 00:03:28 +00:00
# mitmproxy responds with bad gateway
assert self.pathod(spec).status_code == 502
2014-09-04 17:08:54 +00:00
self._ignore_on()
2015-09-16 16:45:22 +00:00
with raises(HttpException):
self.pathod(spec) # pathoc tries to parse answer as HTTP
2014-09-04 17:08:54 +00:00
self._ignore_off()
2013-02-28 20:05:39 +00:00
2014-10-18 16:29:35 +00:00
def _tcpproxy_on(self):
assert not hasattr(self, "_tcpproxy_backup")
self._tcpproxy_backup = self.config.check_tcp
2015-05-30 00:03:28 +00:00
self.config.check_tcp = HostMatcher(
[".+:%s" % self.server.port] + self.config.check_tcp.patterns)
2014-10-18 16:29:35 +00:00
def _tcpproxy_off(self):
assert hasattr(self, "_tcpproxy_backup")
self.config.check_tcp = self._tcpproxy_backup
2014-10-18 16:29:35 +00:00
del self._tcpproxy_backup
def test_tcp(self):
n = self.pathod("304")
2014-10-18 16:29:35 +00:00
self._tcpproxy_on()
i = self.pathod("305")
i2 = self.pathod("306")
2014-10-18 16:29:35 +00:00
self._tcpproxy_off()
self.master.event_queue.join()
2015-09-21 16:31:30 +00:00
assert n.status_code == 304
assert i.status_code == 305
assert i2.status_code == 306
2016-05-11 17:13:57 +00:00
assert any(f.response.status_code == 304 for f in self.master.state.flows if isinstance(f, HTTPFlow))
assert not any(f.response.status_code == 305 for f in self.master.state.flows if isinstance(f, HTTPFlow))
assert not any(f.response.status_code == 306 for f in self.master.state.flows if isinstance(f, HTTPFlow))
2014-10-18 16:29:35 +00:00
# Test that we get the original SSL cert
if self.ssl:
i_cert = SSLCert(i.sslinfo.certchain[0])
i2_cert = SSLCert(i2.sslinfo.certchain[0])
n_cert = SSLCert(n.sslinfo.certchain[0])
assert i_cert == i2_cert == n_cert
# Make sure that TCP messages are in the event log.
2016-07-02 00:17:16 +00:00
# Re-enable and fix this when we start keeping TCPFlows in the state.
# assert any("305" in m for m in self.master.tlog)
# assert any("306" in m for m in self.master.tlog)
2013-03-24 20:20:26 +00:00
2015-05-30 00:03:28 +00:00
2013-03-24 20:20:26 +00:00
class AppMixin:
2016-01-27 09:12:18 +00:00
2013-03-24 20:20:26 +00:00
def test_app(self):
ret = self.app("/")
assert ret.status_code == 200
2016-06-22 19:04:38 +00:00
assert b"mitmproxy" in ret.content
2013-03-24 20:20:26 +00:00
2016-02-16 20:15:50 +00:00
class TestHTTP(tservers.HTTPProxyTest, CommonMixin, AppMixin):
2016-01-27 09:12:18 +00:00
def test_app_err(self):
p = self.pathoc()
ret = p.request("get:'http://errapp/'")
2013-02-28 20:05:39 +00:00
assert ret.status_code == 500
2016-06-22 19:04:38 +00:00
assert b"ValueError" in ret.content
def test_invalid_connect(self):
t = tcp.TCPClient(("127.0.0.1", self.proxy.port))
t.connect()
2016-06-22 19:04:38 +00:00
t.wfile.write(b"CONNECT invalid\n\n")
t.wfile.flush()
2016-06-22 19:04:38 +00:00
assert b"Bad Request" in t.rfile.readline()
def test_upstream_ssl_error(self):
p = self.pathoc()
2015-05-30 00:03:28 +00:00
ret = p.request("get:'https://localhost:%s/'" % self.server.port)
2013-02-28 20:05:39 +00:00
assert ret.status_code == 400
def test_connection_close(self):
# Add a body, so we have a content-length header, which combined with
# HTTP1.1 means the connection is kept alive.
2015-05-30 00:03:28 +00:00
response = '%s/p/200:b@1' % self.server.urlbase
# Lets sanity check that the connection does indeed stay open by
# issuing two requests over the same connection
p = self.pathoc()
2015-05-30 00:03:28 +00:00
assert p.request("get:'%s'" % response)
assert p.request("get:'%s'" % response)
# Now check that the connection is closed as the client specifies
p = self.pathoc()
2015-05-30 00:03:28 +00:00
assert p.request("get:'%s':h'Connection'='close'" % response)
# There's a race here, which means we can get any of a number of errors.
# Rather than introduce yet another sleep into the test suite, we just
# relax the Exception specification.
with raises(Exception):
p.request("get:'%s'" % response)
def test_reconnect(self):
2015-05-30 00:03:28 +00:00
req = "get:'%s/p/200:b@1:da'" % self.server.urlbase
p = self.pathoc()
assert p.request(req)
# Server has disconnected. Mitmproxy should detect this, and reconnect.
assert p.request(req)
assert p.request(req)
def test_get_connection_switching(self):
def switched(l):
for i in l:
if "serverdisconnect" in i:
return True
2015-07-03 00:47:12 +00:00
req = "get:'%s/p/200:b@1'"
p = self.pathoc()
2015-05-30 00:03:28 +00:00
assert p.request(req % self.server.urlbase)
assert p.request(req % self.server2.urlbase)
assert switched(self.proxy.tlog)
def test_blank_leading_line(self):
p = self.pathoc()
req = "get:'%s/p/201':i0,'\r\n'"
2015-05-30 00:03:28 +00:00
assert p.request(req % self.server.urlbase).status_code == 201
def test_invalid_headers(self):
p = self.pathoc()
2015-09-16 16:45:22 +00:00
resp = p.request("get:'http://foo':h':foo'='bar'")
assert resp.status_code == 400
2014-09-06 11:30:00 +00:00
def test_stream(self):
self.master.set_stream_large_bodies(1024 * 2)
self.pathod("200:b@1k")
assert not self.master.state.view[-1].response.stream
assert len(self.master.state.view[-1].response.content) == 1024 * 1
self.pathod("200:b@3k")
assert self.master.state.view[-1].response.stream
2016-03-26 08:00:51 +00:00
assert self.master.state.view[-1].response.content is None
2014-09-06 11:30:00 +00:00
self.master.set_stream_large_bodies(None)
2015-02-27 14:24:27 +00:00
def test_stream_modify(self):
s = script.Script(
tutils.test_data.path("data/addonscripts/stream_modify.py")
)
self.master.addons.add(s)
2015-02-27 14:24:27 +00:00
d = self.pathod('200:b"foo"')
assert d.content == b"bar"
self.master.addons.remove(s)
2015-02-27 14:24:27 +00:00
2015-05-30 00:03:28 +00:00
2016-02-16 20:15:50 +00:00
class TestHTTPAuth(tservers.HTTPProxyTest):
2013-03-02 22:04:33 +00:00
def test_auth(self):
2016-07-19 02:51:12 +00:00
self.master.options.auth_singleuser = "test:test"
2013-03-02 22:04:33 +00:00
assert self.pathod("202").status_code == 407
p = self.pathoc()
ret = p.request("""
get
'http://localhost:%s/p/202'
h'%s'='%s'
2015-05-30 00:03:28 +00:00
""" % (
2013-03-02 22:04:33 +00:00
self.server.port,
2015-07-15 21:19:01 +00:00
http.authentication.BasicProxyAuth.AUTH_HEADER,
authentication.assemble_http_basic_auth("basic", "test", "test")
2013-03-02 22:04:33 +00:00
))
assert ret.status_code == 202
2016-02-16 20:15:50 +00:00
class TestHTTPS(tservers.HTTPProxyTest, CommonMixin, TcpMixin):
ssl = True
ssloptions = pathod.SSLOptions(request_client_cert=True)
def test_clientcert_file(self):
try:
self.config.clientcerts = os.path.join(
tutils.test_data.path("data/clientcert"), "client.pem")
f = self.pathod("304")
assert f.status_code == 304
assert self.server.last_log()["request"]["clientcert"]["keyinfo"]
finally:
self.config.clientcerts = None
2015-12-29 16:57:14 +00:00
def test_clientcert_dir(self):
try:
self.config.clientcerts = tutils.test_data.path("data/clientcert")
f = self.pathod("304")
assert f.status_code == 304
assert self.server.last_log()["request"]["clientcert"]["keyinfo"]
finally:
self.config.clientcerts = None
2015-12-29 16:57:14 +00:00
def test_error_post_connect(self):
p = self.pathoc()
assert p.request("get:/:i0,'invalid\r\n\r\n'").status_code == 400
2016-02-16 20:15:50 +00:00
class TestHTTPSCertfile(tservers.HTTPProxyTest, CommonMixin):
ssl = True
certfile = True
2015-05-30 00:03:28 +00:00
def test_certfile(self):
assert self.pathod("304")
2014-03-02 02:14:22 +00:00
2016-02-16 20:15:50 +00:00
class TestHTTPSUpstreamServerVerificationWTrustedCert(tservers.HTTPProxyTest):
2016-01-27 09:12:18 +00:00
"""
Test upstream server certificate verification with a trusted server cert.
"""
ssl = True
ssloptions = pathod.SSLOptions(
2016-06-22 19:04:38 +00:00
cn=b"trusted-cert",
2015-07-03 00:47:12 +00:00
certs=[
("trusted-cert", tutils.test_data.path("data/trusted-server.crt"))
])
def test_verification_w_cadir(self):
self.config.options.update(
ssl_verify_upstream_cert = True,
ssl_verify_upstream_trusted_cadir = tutils.test_data.path(
"data/trusted-cadir/"
)
)
self.pathoc()
def test_verification_w_pemfile(self):
self.config.openssl_verification_mode_server = SSL.VERIFY_PEER
self.config.options.ssl_verify_upstream_trusted_ca = tutils.test_data.path(
"data/trusted-cadir/trusted-ca.pem")
self.pathoc()
2016-02-16 20:15:50 +00:00
class TestHTTPSUpstreamServerVerificationWBadCert(tservers.HTTPProxyTest):
2016-01-27 09:12:18 +00:00
"""
Test upstream server certificate verification with an untrusted server cert.
"""
ssl = True
ssloptions = pathod.SSLOptions(
2016-06-22 19:04:38 +00:00
cn=b"untrusted-cert",
2015-07-03 00:47:12 +00:00
certs=[
("untrusted-cert", tutils.test_data.path("data/untrusted-server.crt"))
])
2015-08-24 14:52:03 +00:00
def _request(self):
p = self.pathoc()
# We need to make an actual request because the upstream connection is lazy-loaded.
return p.request("get:/p/242")
def test_default_verification_w_bad_cert(self):
"""Should use no verification."""
self.config.options.update(
ssl_verify_upstream_trusted_ca = tutils.test_data.path(
"data/trusted-cadir/trusted-ca.pem"
)
)
2015-08-24 14:52:03 +00:00
assert self._request().status_code == 242
def test_no_verification_w_bad_cert(self):
self.config.options.update(
ssl_verify_upstream_cert = False,
ssl_verify_upstream_trusted_ca = tutils.test_data.path(
"data/trusted-cadir/trusted-ca.pem"
)
)
2015-08-24 14:52:03 +00:00
assert self._request().status_code == 242
def test_verification_w_bad_cert(self):
self.config.options.update(
ssl_verify_upstream_cert = True,
ssl_verify_upstream_trusted_ca = tutils.test_data.path(
"data/trusted-cadir/trusted-ca.pem"
)
)
2015-08-24 14:52:03 +00:00
assert self._request().status_code == 502
2016-02-16 20:15:50 +00:00
class TestHTTPSNoCommonName(tservers.HTTPProxyTest):
2016-01-27 09:12:18 +00:00
"""
Test what happens if we get a cert without common name back.
"""
ssl = True
2015-05-30 00:03:28 +00:00
ssloptions = pathod.SSLOptions(
2015-07-03 00:47:12 +00:00
certs=[
2016-06-22 19:04:38 +00:00
(b"*", tutils.test_data.path("data/no_common_name.pem"))
2015-05-30 00:03:28 +00:00
]
)
2014-03-02 02:14:22 +00:00
def test_http(self):
f = self.pathod("202")
assert f.sslinfo.certchain[0].get_subject().CN == "127.0.0.1"
2016-02-16 20:15:50 +00:00
class TestReverse(tservers.ReverseProxyTest, CommonMixin, TcpMixin):
2012-06-09 08:41:28 +00:00
reverse = True
2015-07-03 00:47:12 +00:00
class TestSocks5(tservers.SocksModeTest):
2016-01-27 09:12:18 +00:00
2015-07-03 00:47:12 +00:00
def test_simple(self):
p = self.pathoc()
p.socks_connect(("localhost", self.server.port))
f = p.request("get:/p/200")
assert f.status_code == 200
def test_with_authentication_only(self):
p = self.pathoc()
f = p.request("get:/p/200")
assert f.status_code == 502
2016-06-22 19:04:38 +00:00
assert b"SOCKS5 mode failure" in f.content
2015-07-03 00:47:12 +00:00
def test_no_connect(self):
"""
mitmproxy doesn't support UDP or BIND SOCKS CMDs
"""
p = self.pathoc()
socks.ClientGreeting(
socks.VERSION.SOCKS5,
[socks.METHOD.NO_AUTHENTICATION_REQUIRED]
).to_file(p.wfile)
socks.Message(
socks.VERSION.SOCKS5,
socks.CMD.BIND,
socks.ATYP.DOMAINNAME,
("example.com", 8080)
).to_file(p.wfile)
p.wfile.flush()
p.rfile.read(2) # read server greeting
f = p.request("get:/p/200") # the request doesn't matter, error response from handshake will be read anyway.
assert f.status_code == 502
2016-06-22 19:04:38 +00:00
assert b"SOCKS5 mode failure" in f.content
2015-07-03 00:47:12 +00:00
2016-02-16 20:15:50 +00:00
class TestHttps2Http(tservers.ReverseProxyTest):
2016-01-27 09:12:18 +00:00
2015-02-27 08:17:41 +00:00
@classmethod
def get_options(cls):
opts = super(TestHttps2Http, cls).get_options()
s = parse_server_spec(opts.upstream_server)
opts.upstream_server = "http://%s" % s.address
return opts
2015-02-27 08:17:41 +00:00
def pathoc(self, ssl, sni=None):
"""
Returns a connected Pathoc instance.
"""
2015-07-15 21:19:01 +00:00
p = pathoc.Pathoc(
2015-08-30 11:40:23 +00:00
("localhost", self.proxy.port), ssl=True, sni=sni, fp=None
)
2015-02-27 08:17:41 +00:00
p.connect()
return p
def test_all(self):
p = self.pathoc(ssl=True)
assert p.request("get:'/p/200'").status_code == 200
def test_sni(self):
2016-07-07 04:03:17 +00:00
p = self.pathoc(ssl=True, sni="example.com")
2015-02-27 08:17:41 +00:00
assert p.request("get:'/p/200'").status_code == 200
assert all("Error in handle_sni" not in msg for msg in self.proxy.tlog)
2015-02-27 08:17:41 +00:00
2015-08-30 11:40:23 +00:00
def test_http(self):
p = self.pathoc(ssl=False)
assert p.request("get:'/p/200'").status_code == 200
2015-02-27 08:17:41 +00:00
2016-02-16 20:15:50 +00:00
class TestTransparent(tservers.TransparentProxyTest, CommonMixin, TcpMixin):
2013-02-28 20:05:39 +00:00
ssl = False
2016-01-02 15:00:27 +00:00
def test_tcp_stream_modify(self):
s = script.Script(
tutils.test_data.path("data/addonscripts/tcp_stream_modify.py")
)
self.master.addons.add(s)
2016-01-02 15:00:27 +00:00
self._tcpproxy_on()
d = self.pathod('200:b"foo"')
self._tcpproxy_off()
assert d.content == b"bar"
self.master.addons.remove(s)
2016-01-02 15:00:27 +00:00
2013-02-28 20:05:39 +00:00
2016-02-16 20:15:50 +00:00
class TestTransparentSSL(tservers.TransparentProxyTest, CommonMixin, TcpMixin):
2013-02-28 20:05:39 +00:00
ssl = True
2013-03-02 02:06:49 +00:00
def test_sslerr(self):
p = pathoc.Pathoc(("localhost", self.proxy.port), fp=None)
p.connect()
r = p.request("get:/")
2015-08-29 18:53:25 +00:00
assert r.status_code == 502
2016-02-16 20:15:50 +00:00
class TestProxy(tservers.HTTPProxyTest):
2016-01-27 09:12:18 +00:00
2011-03-05 22:21:31 +00:00
def test_http(self):
f = self.pathod("304")
assert f.status_code == 304
2014-02-04 04:02:17 +00:00
f = self.master.state.view[0]
assert f.client_conn.address
assert "host" in f.request.headers
assert f.response.status_code == 304
2015-11-24 18:05:37 +00:00
@tutils.skip_appveyor
def test_response_timestamps(self):
2015-09-12 15:50:24 +00:00
# test that we notice at least 1 sec delay between timestamps
# in response object
f = self.pathod("304:b@1k:p50,1")
assert f.status_code == 304
response = self.master.state.view[0].response
2015-11-14 21:14:18 +00:00
# timestamp_start might fire a bit late, so we play safe and only require 300ms.
assert 0.3 <= response.timestamp_end - response.timestamp_start
2015-11-24 18:05:37 +00:00
@tutils.skip_appveyor
def test_request_timestamps(self):
# test that we notice a delay between timestamps in request object
connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connection.connect(("127.0.0.1", self.proxy.port))
# call pathod server, wait a second to complete the request
2015-05-30 00:03:28 +00:00
connection.send(
2016-06-22 19:04:38 +00:00
b"GET http://localhost:%d/p/304:b@1k HTTP/1.1\r\n" %
2015-05-30 00:03:28 +00:00
self.server.port)
2013-06-17 22:54:07 +00:00
time.sleep(1)
2016-06-22 19:04:38 +00:00
connection.send(b"\r\n")
connection.recv(50000)
connection.close()
2015-05-30 00:03:28 +00:00
request, response = self.master.state.view[
2016-01-27 09:12:18 +00:00
0].request, self.master.state.view[0].response
assert response.status_code == 304 # sanity test for our low level request
2015-11-14 21:14:18 +00:00
# timestamp_start might fire a bit late, so we play safe and only require 300ms.
2015-11-24 18:05:37 +00:00
assert 0.3 <= request.timestamp_end - request.timestamp_start
2013-02-23 03:34:59 +00:00
def test_request_tcp_setup_timestamp_presence(self):
2014-02-04 04:02:17 +00:00
# tests that the client_conn a tcp connection has a tcp_setup_timestamp
connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connection.connect(("localhost", self.proxy.port))
2015-05-30 00:03:28 +00:00
connection.send(
2016-06-22 19:04:38 +00:00
b"GET http://localhost:%d/p/200:b@1k HTTP/1.1\r\n" %
2015-05-30 00:03:28 +00:00
self.server.port)
2016-06-22 19:04:38 +00:00
connection.send(b"\r\n")
2015-09-16 16:45:22 +00:00
# a bit hacky: make sure that we don't just read the headers only.
recvd = 0
while recvd < 1024:
recvd += len(connection.recv(5000))
2015-05-30 00:03:28 +00:00
connection.send(
2016-06-22 19:04:38 +00:00
b"GET http://localhost:%d/p/200:b@1k HTTP/1.1\r\n" %
2015-05-30 00:03:28 +00:00
self.server.port)
2016-06-22 19:04:38 +00:00
connection.send(b"\r\nb")
2015-09-16 16:45:22 +00:00
recvd = 0
while recvd < 1024:
recvd += len(connection.recv(5000))
connection.close()
2014-02-04 04:02:17 +00:00
first_flow = self.master.state.view[0]
second_flow = self.master.state.view[1]
assert first_flow.server_conn.timestamp_tcp_setup
assert first_flow.server_conn.timestamp_ssl_setup is None
assert second_flow.server_conn.timestamp_tcp_setup
assert first_flow.server_conn.timestamp_tcp_setup == second_flow.server_conn.timestamp_tcp_setup
def test_request_ip(self):
f = self.pathod("200:b@100")
assert f.status_code == 200
2014-02-04 04:02:17 +00:00
f = self.master.state.view[0]
2014-09-04 14:37:50 +00:00
assert f.server_conn.address == ("127.0.0.1", self.server.port)
2013-02-23 03:34:59 +00:00
2015-05-30 00:03:28 +00:00
2016-02-16 20:15:50 +00:00
class TestProxySSL(tservers.HTTPProxyTest):
2015-05-30 00:03:28 +00:00
ssl = True
def test_request_ssl_setup_timestamp_presence(self):
# tests that the ssl timestamp is present when ssl is used
f = self.pathod("304:b@10k")
assert f.status_code == 304
first_flow = self.master.state.view[0]
assert first_flow.server_conn.timestamp_ssl_setup
class MasterRedirectRequest(tservers.TestMaster):
redirect_port = None # Set by TestRedirectRequest
@controller.handler
def request(self, f):
2015-08-27 15:35:53 +00:00
if f.request.path == "/p/201":
2015-08-31 15:05:52 +00:00
# This part should have no impact, but it should also not cause any exceptions.
2015-08-27 15:35:53 +00:00
addr = f.live.server_conn.address
addr2 = Address(("127.0.0.1", self.redirect_port))
f.live.set_server(addr2)
f.live.set_server(addr)
# This is the actual redirection.
f.request.port = self.redirect_port
super(MasterRedirectRequest, self).request(f)
@controller.handler
def response(self, f):
2016-07-01 21:10:48 +00:00
f.response.content = bytes(f.client_conn.address.port)
2015-09-05 18:45:58 +00:00
f.response.headers["server-conn-id"] = str(f.server_conn.source_address.port)
super(MasterRedirectRequest, self).response(f)
2016-02-16 20:15:50 +00:00
class TestRedirectRequest(tservers.HTTPProxyTest):
masterclass = MasterRedirectRequest
2015-08-27 15:35:53 +00:00
ssl = True
def test_redirect(self):
2015-08-27 15:35:53 +00:00
"""
Imagine a single HTTPS connection with three requests:
1. First request should pass through unmodified
2. Second request will be redirected to a different host by an inline script
3. Third request should pass through unmodified
This test verifies that the original destination is restored for the third request.
"""
self.master.redirect_port = self.server2.port
p = self.pathoc()
self.server.clear_log()
self.server2.clear_log()
2015-08-27 15:35:53 +00:00
r1 = p.request("get:'/p/200'")
assert r1.status_code == 200
assert self.server.last_log()
assert not self.server2.last_log()
self.server.clear_log()
self.server2.clear_log()
2015-08-27 15:35:53 +00:00
r2 = p.request("get:'/p/201'")
assert r2.status_code == 201
assert not self.server.last_log()
assert self.server2.last_log()
self.server.clear_log()
self.server2.clear_log()
2015-08-27 15:35:53 +00:00
r3 = p.request("get:'/p/202'")
assert r3.status_code == 202
assert self.server.last_log()
assert not self.server2.last_log()
2014-09-04 14:37:50 +00:00
assert r1.content == r2.content == r3.content
2015-05-30 00:03:28 +00:00
class MasterStreamRequest(tservers.TestMaster):
2016-01-27 09:12:18 +00:00
"""
Enables the stream flag on the flow for all requests
"""
@controller.handler
def responseheaders(self, f):
f.response.stream = True
2015-05-30 00:03:28 +00:00
2016-02-16 20:15:50 +00:00
class TestStreamRequest(tservers.HTTPProxyTest):
masterclass = MasterStreamRequest
def test_stream_simple(self):
p = self.pathoc()
# a request with 100k of data but without content-length
2015-05-30 00:03:28 +00:00
r1 = p.request("get:'%s/p/200:r:b@100k:d102400'" % self.server.urlbase)
assert r1.status_code == 200
assert len(r1.content) > 100000
def test_stream_multiple(self):
p = self.pathoc()
# simple request with streaming turned on
2015-05-30 00:03:28 +00:00
r1 = p.request("get:'%s/p/200'" % self.server.urlbase)
assert r1.status_code == 200
# now send back 100k of data, streamed but not chunked
r1 = p.request("get:'%s/p/201:b@100k'" % self.server.urlbase)
assert r1.status_code == 201
def test_stream_chunked(self):
connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connection.connect(("127.0.0.1", self.proxy.port))
2016-07-01 21:10:48 +00:00
fconn = connection.makefile("rb")
2015-09-10 23:18:17 +00:00
spec = '200:h"Transfer-Encoding"="chunked":r:b"4\\r\\nthis\\r\\n11\\r\\nisatest__reachhex\\r\\n0\\r\\n\\r\\n"'
2015-05-30 00:03:28 +00:00
connection.send(
2016-06-22 19:04:38 +00:00
b"GET %s/p/%s HTTP/1.1\r\n" %
(self.server.urlbase.encode(), spec.encode()))
connection.send(b"\r\n")
2015-09-16 16:45:22 +00:00
resp = http1.read_response_head(fconn)
2015-09-05 18:45:58 +00:00
assert resp.headers["Transfer-Encoding"] == 'chunked'
assert resp.status_code == 200
2015-09-16 16:45:22 +00:00
chunks = list(http1.read_body(fconn, None))
2016-07-01 21:10:48 +00:00
assert chunks == [b"this", b"isatest__reachhex"]
connection.close()
2013-02-23 03:34:59 +00:00
class MasterFakeResponse(tservers.TestMaster):
@controller.handler
def request(self, f):
2015-08-30 13:27:29 +00:00
resp = HTTPResponse.wrap(netlib.tutils.tresp())
f.reply.send(resp)
2013-02-23 03:34:59 +00:00
2016-02-16 20:15:50 +00:00
class TestFakeResponse(tservers.HTTPProxyTest):
2013-02-23 03:34:59 +00:00
masterclass = MasterFakeResponse
2015-05-30 00:03:28 +00:00
def test_fake(self):
2013-02-23 03:34:59 +00:00
f = self.pathod("200")
2015-09-16 16:45:22 +00:00
assert "header-response" in f.headers
2013-02-23 03:34:59 +00:00
2016-02-16 20:15:50 +00:00
class TestServerConnect(tservers.HTTPProxyTest):
masterclass = MasterFakeResponse
ssl = True
2016-01-27 09:12:18 +00:00
@classmethod
def get_options(cls):
opts = tservers.HTTPProxyTest.get_options()
opts.no_upstream_cert = True
return opts
def test_unnecessary_serverconnect(self):
"""A replayed/fake response with no_upstream_cert should not connect to an upstream server"""
assert self.pathod("200").status_code == 200
for msg in self.proxy.tmaster.tlog:
assert "serverconnect" not in msg
class MasterKillRequest(tservers.TestMaster):
2016-01-27 09:12:18 +00:00
@controller.handler
def request(self, f):
2016-06-07 22:14:34 +00:00
f.reply.kill()
2016-02-16 20:15:50 +00:00
class TestKillRequest(tservers.HTTPProxyTest):
masterclass = MasterKillRequest
2015-05-30 00:03:28 +00:00
def test_kill(self):
2015-09-16 16:45:22 +00:00
with raises(HttpReadDisconnect):
self.pathod("200")
# Nothing should have hit the server
assert not self.server.last_log()
class MasterKillResponse(tservers.TestMaster):
2016-01-27 09:12:18 +00:00
@controller.handler
def response(self, f):
2016-06-07 22:14:34 +00:00
f.reply.kill()
2016-02-16 20:15:50 +00:00
class TestKillResponse(tservers.HTTPProxyTest):
masterclass = MasterKillResponse
2015-05-30 00:03:28 +00:00
def test_kill(self):
2015-09-16 16:45:22 +00:00
with raises(HttpReadDisconnect):
self.pathod("200")
# The server should have seen a request
assert self.server.last_log()
class EResolver(tservers.TResolver):
2016-01-27 09:12:18 +00:00
def original_addr(self, sock):
2014-08-10 14:20:04 +00:00
raise RuntimeError("Could not resolve original destination.")
2016-02-16 20:15:50 +00:00
class TestTransparentResolveError(tservers.TransparentProxyTest):
resolver = EResolver
2015-05-30 00:03:28 +00:00
def test_resolve_error(self):
assert self.pathod("304").status_code == 502
class MasterIncomplete(tservers.TestMaster):
2016-01-27 09:12:18 +00:00
@controller.handler
def request(self, f):
2015-08-30 13:27:29 +00:00
resp = HTTPResponse.wrap(netlib.tutils.tresp())
2016-03-26 08:00:51 +00:00
resp.content = None
f.reply.send(resp)
2016-02-16 20:15:50 +00:00
class TestIncompleteResponse(tservers.HTTPProxyTest):
masterclass = MasterIncomplete
2015-05-30 00:03:28 +00:00
def test_incomplete(self):
assert self.pathod("200").status_code == 502
2014-03-11 00:02:10 +00:00
2016-02-16 20:15:50 +00:00
class TestUpstreamProxy(tservers.HTTPUpstreamProxyTest, CommonMixin, AppMixin):
ssl = False
def test_order(self):
self.proxy.tmaster.options.replacements = [
("~q", "foo", "bar"),
("~q", "bar", "baz"),
("~q", "foo", "oh noes!"),
("~s", "baz", "ORLY")
]
p = self.pathoc()
req = p.request("get:'%s/p/418:b\"foo\"'" % self.server.urlbase)
2016-07-01 21:10:48 +00:00
assert req.content == b"ORLY"
assert req.status_code == 418
2015-05-30 00:03:28 +00:00
class TestUpstreamProxySSL(
2016-02-16 20:15:50 +00:00
tservers.HTTPUpstreamProxyTest,
2016-01-27 09:12:18 +00:00
CommonMixin,
TcpMixin):
ssl = True
2014-10-18 16:29:35 +00:00
def _host_pattern_on(self, attr):
"""
Updates config.check_tcp or check_ignore, depending on attr.
"""
assert not hasattr(self, "_ignore_%s_backup" % attr)
backup = []
for proxy in self.chain:
2015-05-30 00:03:28 +00:00
old_matcher = getattr(
proxy.tmaster.server.config,
"check_%s" %
attr)
2014-10-18 16:29:35 +00:00
backup.append(old_matcher)
setattr(
proxy.tmaster.server.config,
"check_%s" % attr,
HostMatcher([".+:%s" % self.server.port] + old_matcher.patterns)
)
setattr(self, "_ignore_%s_backup" % attr, backup)
def _host_pattern_off(self, attr):
backup = getattr(self, "_ignore_%s_backup" % attr)
for proxy in reversed(self.chain):
setattr(
proxy.tmaster.server.config,
"check_%s" % attr,
backup.pop()
)
assert not backup
delattr(self, "_ignore_%s_backup" % attr)
def _ignore_on(self):
super(TestUpstreamProxySSL, self)._ignore_on()
2014-10-18 16:29:35 +00:00
self._host_pattern_on("ignore")
def _ignore_off(self):
super(TestUpstreamProxySSL, self)._ignore_off()
2014-10-18 16:29:35 +00:00
self._host_pattern_off("ignore")
def _tcpproxy_on(self):
super(TestUpstreamProxySSL, self)._tcpproxy_on()
self._host_pattern_on("tcp")
def _tcpproxy_off(self):
super(TestUpstreamProxySSL, self)._tcpproxy_off()
self._host_pattern_off("tcp")
def test_simple(self):
p = self.pathoc()
req = p.request("get:'/p/418:b\"content\"'")
2016-06-22 19:04:38 +00:00
assert req.content == b"content"
assert req.status_code == 418
2015-05-30 00:03:28 +00:00
# CONNECT from pathoc to chain[0],
assert self.proxy.tmaster.state.flow_count() == 2
# request from pathoc to chain[0]
# CONNECT from proxy to chain[1],
assert self.chain[0].tmaster.state.flow_count() == 2
# request from proxy to chain[1]
# request from chain[0] (regular proxy doesn't store CONNECTs)
assert self.chain[1].tmaster.state.flow_count() == 1
2016-02-16 20:15:50 +00:00
class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest):
ssl = True
def test_reconnect(self):
"""
Tests proper functionality of ConnectionHandler.server_reconnect mock.
If we have a disconnect on a secure connection that's transparently proxified to
an upstream http proxy, we need to send the CONNECT request again.
"""
2015-07-03 00:47:12 +00:00
def kill_requests(master, attr, exclude):
k = [0] # variable scope workaround: put into array
_func = getattr(master, attr)
2015-05-30 00:03:28 +00:00
@controller.handler
def handler(*args):
f = args[-1]
k[0] += 1
if not (k[0] in exclude):
f.client_conn.finish()
f.error = Error("terminated")
2016-06-07 22:14:34 +00:00
f.reply.kill()
return _func(f)
2015-07-03 00:47:12 +00:00
2016-07-08 06:50:55 +00:00
setattr(master, attr, types.MethodType(handler, master))
kill_requests(
self.chain[1].tmaster,
"request",
exclude = [
# fail first request
2, # allow second request
]
)
kill_requests(self.chain[0].tmaster, "request",
exclude=[
1, # CONNECT
2015-07-03 00:47:12 +00:00
# fail first request
3, # reCONNECT
4, # request
2016-01-27 09:12:18 +00:00
])
p = self.pathoc()
req = p.request("get:'/p/418:b\"content\"'")
2016-06-22 19:04:38 +00:00
assert req.content == b"content"
2015-08-29 18:53:25 +00:00
assert req.status_code == 418
assert self.proxy.tmaster.state.flow_count() == 2 # CONNECT and request
2015-05-30 00:03:28 +00:00
# CONNECT, failing request,
assert self.chain[0].tmaster.state.flow_count() == 4
# reCONNECT, request
# failing request, request
assert self.chain[1].tmaster.state.flow_count() == 2
# (doesn't store (repeated) CONNECTs from chain[0]
# as it is a regular proxy)
2015-08-29 18:53:25 +00:00
assert not self.chain[1].tmaster.state.flows[0].response # killed
assert self.chain[1].tmaster.state.flows[1].response
2016-04-02 19:57:35 +00:00
assert self.proxy.tmaster.state.flows[0].request.first_line_format == "authority"
assert self.proxy.tmaster.state.flows[1].request.first_line_format == "relative"
2015-05-30 00:03:28 +00:00
assert self.chain[0].tmaster.state.flows[
2016-04-02 19:57:35 +00:00
0].request.first_line_format == "authority"
2015-05-30 00:03:28 +00:00
assert self.chain[0].tmaster.state.flows[
2016-04-02 19:57:35 +00:00
1].request.first_line_format == "relative"
2015-05-30 00:03:28 +00:00
assert self.chain[0].tmaster.state.flows[
2016-04-02 19:57:35 +00:00
2].request.first_line_format == "authority"
2015-05-30 00:03:28 +00:00
assert self.chain[0].tmaster.state.flows[
2016-04-02 19:57:35 +00:00
3].request.first_line_format == "relative"
2015-05-30 00:03:28 +00:00
assert self.chain[1].tmaster.state.flows[
2016-04-02 19:57:35 +00:00
0].request.first_line_format == "relative"
2015-05-30 00:03:28 +00:00
assert self.chain[1].tmaster.state.flows[
2016-04-02 19:57:35 +00:00
1].request.first_line_format == "relative"
req = p.request("get:'/p/418:b\"content2\"'")
assert req.status_code == 502
assert self.proxy.tmaster.state.flow_count() == 3 # + new request
2015-05-30 00:03:28 +00:00
# + new request, repeated CONNECT from chain[1]
assert self.chain[0].tmaster.state.flow_count() == 6
# (both terminated)
# nothing happened here
assert self.chain[1].tmaster.state.flow_count() == 2
class AddUpstreamCertsToClientChainMixin:
ssl = True
servercert = tutils.test_data.path("data/trusted-server.crt")
ssloptions = pathod.SSLOptions(
2016-06-22 19:04:38 +00:00
cn=b"trusted-cert",
certs=[
2016-06-22 19:04:38 +00:00
(b"trusted-cert", servercert)
]
)
def test_add_upstream_certs_to_client_chain(self):
with open(self.servercert, "rb") as f:
d = f.read()
upstreamCert = SSLCert.from_pem(d)
p = self.pathoc()
upstream_cert_found_in_client_chain = False
for receivedCert in p.server_certs:
if receivedCert.digest('sha256') == upstreamCert.digest('sha256'):
upstream_cert_found_in_client_chain = True
break
assert(upstream_cert_found_in_client_chain == self.master.options.add_upstream_certs_to_client_chain)
class TestHTTPSAddUpstreamCertsToClientChainTrue(
AddUpstreamCertsToClientChainMixin,
tservers.HTTPProxyTest
):
"""
If --add-server-certs-to-client-chain is True, then the client should
receive the upstream server's certificates
"""
@classmethod
def get_options(cls):
opts = super(tservers.HTTPProxyTest, cls).get_options()
opts.add_upstream_certs_to_client_chain = True
return opts
class TestHTTPSAddUpstreamCertsToClientChainFalse(
AddUpstreamCertsToClientChainMixin,
tservers.HTTPProxyTest
):
"""
If --add-server-certs-to-client-chain is False, then the client should not
receive the upstream server's certificates
"""
@classmethod
def get_options(cls):
opts = super(tservers.HTTPProxyTest, cls).get_options()
opts.add_upstream_certs_to_client_chain = False
return opts