diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/data/confdir/mitmproxy-ca-cert.cer | 17 | ||||
-rw-r--r-- | test/data/confdir/mitmproxy-ca-cert.p12 | bin | 0 -> 1689 bytes | |||
-rw-r--r-- | test/data/confdir/mitmproxy-ca-cert.pem | 17 | ||||
-rw-r--r-- | test/data/confdir/mitmproxy-ca.pem | 32 | ||||
-rw-r--r-- | test/data/serverkey.pem | 32 | ||||
-rw-r--r-- | test/mock_urwid.py | 8 | ||||
-rw-r--r-- | test/test_app.py | 19 | ||||
-rw-r--r-- | test/test_console.py | 24 | ||||
-rw-r--r-- | test/test_dump.py | 20 | ||||
-rw-r--r-- | test/test_filt.py | 44 | ||||
-rw-r--r-- | test/test_flow.py | 320 | ||||
-rw-r--r-- | test/test_fuzzing.py | 4 | ||||
-rw-r--r-- | test/test_protocol_http.py | 201 | ||||
-rw-r--r-- | test/test_proxy.py | 15 | ||||
-rw-r--r-- | test/test_script.py | 15 | ||||
-rw-r--r-- | test/test_server.py | 61 | ||||
-rw-r--r-- | test/tservers.py | 69 | ||||
-rw-r--r-- | test/tutils.py | 89 |
18 files changed, 634 insertions, 353 deletions
diff --git a/test/data/confdir/mitmproxy-ca-cert.cer b/test/data/confdir/mitmproxy-ca-cert.cer new file mode 100644 index 00000000..cc7f8f19 --- /dev/null +++ b/test/data/confdir/mitmproxy-ca-cert.cer @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICnzCCAgigAwIBAgIGDKiSwuJOMA0GCSqGSIb3DQEBBQUAMCgxEjAQBgNVBAMT +CW1pdG1wcm94eTESMBAGA1UEChMJbWl0bXByb3h5MB4XDTE0MDIwNzIzMjcwOFoX +DTE2MDEyODIzMjcwOFowKDESMBAGA1UEAxMJbWl0bXByb3h5MRIwEAYDVQQKEwlt +aXRtcHJveHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKsZ+XnBvjCjAJ00 +9M+v41AT91h7v7cF1UG0BpS3y4MOysN88btHM/IWRCllnmY+zx5LTMAEtbnqyOIk +nkgJ0sU3CFWHRIfwkinssEtMM2mOAFXm0wqffECxwe1p5z84M7nOolzuuw4FtkaK +G9/UqANdRVs6uOwz+CuyOSY7illTAgMBAAGjgdMwgdAwDwYDVR0TAQH/BAUwAwEB +/zAUBglghkgBhvhCAQEBAf8EBAMCAgQwewYDVR0lAQH/BHEwbwYIKwYBBQUHAwEG +CCsGAQUFBwMCBggrBgEFBQcDBAYIKwYBBQUHAwgGCisGAQQBgjcCARUGCisGAQQB +gjcCARYGCisGAQQBgjcKAwEGCisGAQQBgjcKAwMGCisGAQQBgjcKAwQGCWCGSAGG ++EIEATALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFFKVDIF+w2Ns4KsJx6tJZpILqWwG +MA0GCSqGSIb3DQEBBQUAA4GBABWYxoYFLgZh/ujz/0jrNsx0pvSNVTU1T669374z +PhO+ScvzuxVbgI2NQv86aqih35pzakK/DyKaTck85QduDiSiLNw2Yb5UfJvO4C0d +dPzQMIKNTInFFiLBjbvxx9cuDwAPyYOF247Xj9M6C2x6e/gq1L+GR75wT5288x9h +rFTJ +-----END CERTIFICATE----- diff --git a/test/data/confdir/mitmproxy-ca-cert.p12 b/test/data/confdir/mitmproxy-ca-cert.p12 Binary files differnew file mode 100644 index 00000000..d4cec0d4 --- /dev/null +++ b/test/data/confdir/mitmproxy-ca-cert.p12 diff --git a/test/data/confdir/mitmproxy-ca-cert.pem b/test/data/confdir/mitmproxy-ca-cert.pem new file mode 100644 index 00000000..cc7f8f19 --- /dev/null +++ b/test/data/confdir/mitmproxy-ca-cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICnzCCAgigAwIBAgIGDKiSwuJOMA0GCSqGSIb3DQEBBQUAMCgxEjAQBgNVBAMT +CW1pdG1wcm94eTESMBAGA1UEChMJbWl0bXByb3h5MB4XDTE0MDIwNzIzMjcwOFoX +DTE2MDEyODIzMjcwOFowKDESMBAGA1UEAxMJbWl0bXByb3h5MRIwEAYDVQQKEwlt +aXRtcHJveHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKsZ+XnBvjCjAJ00 +9M+v41AT91h7v7cF1UG0BpS3y4MOysN88btHM/IWRCllnmY+zx5LTMAEtbnqyOIk +nkgJ0sU3CFWHRIfwkinssEtMM2mOAFXm0wqffECxwe1p5z84M7nOolzuuw4FtkaK +G9/UqANdRVs6uOwz+CuyOSY7illTAgMBAAGjgdMwgdAwDwYDVR0TAQH/BAUwAwEB +/zAUBglghkgBhvhCAQEBAf8EBAMCAgQwewYDVR0lAQH/BHEwbwYIKwYBBQUHAwEG +CCsGAQUFBwMCBggrBgEFBQcDBAYIKwYBBQUHAwgGCisGAQQBgjcCARUGCisGAQQB +gjcCARYGCisGAQQBgjcKAwEGCisGAQQBgjcKAwMGCisGAQQBgjcKAwQGCWCGSAGG ++EIEATALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFFKVDIF+w2Ns4KsJx6tJZpILqWwG +MA0GCSqGSIb3DQEBBQUAA4GBABWYxoYFLgZh/ujz/0jrNsx0pvSNVTU1T669374z +PhO+ScvzuxVbgI2NQv86aqih35pzakK/DyKaTck85QduDiSiLNw2Yb5UfJvO4C0d +dPzQMIKNTInFFiLBjbvxx9cuDwAPyYOF247Xj9M6C2x6e/gq1L+GR75wT5288x9h +rFTJ +-----END CERTIFICATE----- diff --git a/test/data/confdir/mitmproxy-ca.pem b/test/data/confdir/mitmproxy-ca.pem new file mode 100644 index 00000000..2a2343a6 --- /dev/null +++ b/test/data/confdir/mitmproxy-ca.pem @@ -0,0 +1,32 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQCrGfl5wb4wowCdNPTPr+NQE/dYe7+3BdVBtAaUt8uDDsrDfPG7 +RzPyFkQpZZ5mPs8eS0zABLW56sjiJJ5ICdLFNwhVh0SH8JIp7LBLTDNpjgBV5tMK +n3xAscHtaec/ODO5zqJc7rsOBbZGihvf1KgDXUVbOrjsM/grsjkmO4pZUwIDAQAB +AoGAUtjn4Fm8cqZqpLRAmdOruFmCmbiJ0uAjK4Y07Yu1IgdmjJOSJMFMWLsJVBYd +RZrCBQQm7I8bQyN5E27xqSYAhKz7ymjgHGWlTXENtvfx/XlIIn9DYENKpN1N8Y/5 +BCt0O/F9h2/Z+zGNdV3R2tX3WuSjYlqzzD2RDBIDPe6Fr8kCQQDSLcyqGRXamt0X +MjPtltJHIjIXHp+++qQDT3n8eaP0maWtAm+75PzWGqOvfg4F2VoWMTGdDEbHbCmH +Qa6EW0B/AkEA0Gc90xLD+qLqVEbzdveca+yO1lAastqoYzRuM1StZ1Y4pW7F5D23 +MNhV0zV6z7ejZYnnsGvuQLTx51X8Ff59LQJAF1mxQECTNfs4jugr7rxv1ilNaVYk +p0IPULLWuZ8GARnE10jLAxP4pwzEnK2jfzDbmlWSzoDbqDIzFuzMJ7Y/nwJBAL+s +dNxRAhbfCA6DQyFEE4XfiG/sNOIS4ZR8gG6Njv7f+jGNdEy7xmUSU71yDoZFK+8T +qxhD7FlvEp3mI3hHG/ECQQC0x7z/lr5KRsFGqVZOErkc3nOZO+4rjApHSlbuhDLU +mnUwIi06KyjbN+0XL+6bJl+L5nfL3TIlnyHMJAta2uta +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICnzCCAgigAwIBAgIGDKiSwuJOMA0GCSqGSIb3DQEBBQUAMCgxEjAQBgNVBAMT +CW1pdG1wcm94eTESMBAGA1UEChMJbWl0bXByb3h5MB4XDTE0MDIwNzIzMjcwOFoX +DTE2MDEyODIzMjcwOFowKDESMBAGA1UEAxMJbWl0bXByb3h5MRIwEAYDVQQKEwlt +aXRtcHJveHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKsZ+XnBvjCjAJ00 +9M+v41AT91h7v7cF1UG0BpS3y4MOysN88btHM/IWRCllnmY+zx5LTMAEtbnqyOIk +nkgJ0sU3CFWHRIfwkinssEtMM2mOAFXm0wqffECxwe1p5z84M7nOolzuuw4FtkaK +G9/UqANdRVs6uOwz+CuyOSY7illTAgMBAAGjgdMwgdAwDwYDVR0TAQH/BAUwAwEB +/zAUBglghkgBhvhCAQEBAf8EBAMCAgQwewYDVR0lAQH/BHEwbwYIKwYBBQUHAwEG +CCsGAQUFBwMCBggrBgEFBQcDBAYIKwYBBQUHAwgGCisGAQQBgjcCARUGCisGAQQB +gjcCARYGCisGAQQBgjcKAwEGCisGAQQBgjcKAwMGCisGAQQBgjcKAwQGCWCGSAGG ++EIEATALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFFKVDIF+w2Ns4KsJx6tJZpILqWwG +MA0GCSqGSIb3DQEBBQUAA4GBABWYxoYFLgZh/ujz/0jrNsx0pvSNVTU1T669374z +PhO+ScvzuxVbgI2NQv86aqih35pzakK/DyKaTck85QduDiSiLNw2Yb5UfJvO4C0d +dPzQMIKNTInFFiLBjbvxx9cuDwAPyYOF247Xj9M6C2x6e/gq1L+GR75wT5288x9h +rFTJ +-----END CERTIFICATE----- diff --git a/test/data/serverkey.pem b/test/data/serverkey.pem deleted file mode 100644 index 289bfa71..00000000 --- a/test/data/serverkey.pem +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQC+N+9bv1YC0GKbGdv2wMuuWTGSNwE/Hq5IIxYN1eITsvbD1GgB -69x++XJd6KTIthnta0KCpCAtbaYbCkhUfxCVv2bP+iQt2AjwMOZlgRZ+RGJ25dBu -AjAxQmqDJcAdS6MoRHWziomnUNfNogVrfqjpvJor+1iRnrj2q00ab9WYCwIDAQAB -AoGBAIM7V9l2UcKzPbQ/zO+Z52urgXWcmTGQ2zBNdIOrEcQBbhmAyxi4PnEja3G6 -dSU77PtNSp+S19g/k5+IIoqY9zkGigdaPhRVRKJgBTAzFzMz+WHpQIffDojFKCnL -gyDnzMRJY8+cnsCqbHRY4hqFiCr8Rq9sCdlynAytdtrnxzqhAkEA9bha6MO+L0JA -6IEEbVY1vtaUO9Xg5DUDjRxQcfniSJACb/2IvF0tvxAnG7I/S8AavCXqtlDPtYkI -WOxY5Sd62QJBAMYtKUxGka4XxwCyBK8EUNaN8m9C++mpjoHD1kFri9B1bXm91nCO -iGWqtqdarwyEc/pAHw5UGzVyBXticPIcs4MCQQCcPvsHsZhYoq91aLyw7bXFQNsH -ZUvYsOEuNIfuwa+i5ne2UKhG5pU1PgcwNFrNRz140D98aMx7KcS2DqvEIyOZAkBF -6Yi4L+0Uza6WwDaGx679AfaU6byVIgv0G3JqgdZBJCwK1r3f12im9SKax5MZh2Ci -2Bwcoe83W5IzhPbzcsyhAkBo8O2U2vig5PQWQ0BUKJrCGHLq//D/ttdLVtmc6eWc -zqssCF3Unkk3bOq35swSKeAx8WotPPVsALWr87N2hCB+ ------END RSA PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIICsDCCAhmgAwIBAgIJANwogM9sqMHLMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQwHhcNMTAwMTMxMDEzOTEzWhcNMTEwMTMxMDEzOTEzWjBF -MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 -ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB -gQC+N+9bv1YC0GKbGdv2wMuuWTGSNwE/Hq5IIxYN1eITsvbD1GgB69x++XJd6KTI -thnta0KCpCAtbaYbCkhUfxCVv2bP+iQt2AjwMOZlgRZ+RGJ25dBuAjAxQmqDJcAd -S6MoRHWziomnUNfNogVrfqjpvJor+1iRnrj2q00ab9WYCwIDAQABo4GnMIGkMB0G -A1UdDgQWBBTTnBZyw7ZZsb8+/6gvZFIHhVgtDzB1BgNVHSMEbjBsgBTTnBZyw7ZZ -sb8+/6gvZFIHhVgtD6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt -U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJANwogM9s -qMHLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEApz428aOar0EBuAib -I+liefRlK4I3MQQxq3tOeB1dgAIo0ivKtdVJGi1kPg8EO0KMvFfn6IRtssUmFgCp -JBD+HoDzFxwI1bLMVni+g7OzaNSwL3nQ94lZUdpWMYDxqY4bLUv3goX1TlN9lmpG -8FiBLYUC0RNTCCRDFGfDr/wUT/M= ------END CERTIFICATE----- diff --git a/test/mock_urwid.py b/test/mock_urwid.py new file mode 100644 index 00000000..f132e0bd --- /dev/null +++ b/test/mock_urwid.py @@ -0,0 +1,8 @@ +import os, sys, mock +if os.name == "nt": + m = mock.Mock() + m.__version__ = "1.1.1" + m.Widget = mock.Mock + m.WidgetWrap = mock.Mock + sys.modules['urwid'] = m + sys.modules['urwid.util'] = mock.Mock()
\ No newline at end of file diff --git a/test/test_app.py b/test/test_app.py new file mode 100644 index 00000000..f0eab7cc --- /dev/null +++ b/test/test_app.py @@ -0,0 +1,19 @@ +import mock, socket, os, time +from libmproxy import dump +from netlib import certutils, tcp +from libpathod.pathoc import Pathoc +import tutils, tservers + +class TestApp(tservers.HTTPProxTest): + def test_basic(self): + assert self.app("/").status_code == 200 + + def test_cert(self): + path = tutils.test_data.path("data/confdir/") + "mitmproxy-ca-cert." + with tutils.tmpdir() as d: + for ext in ["pem", "p12"]: + resp = self.app("/cert/%s" % ext) + assert resp.status_code == 200 + with open(path + ext, "rb") as f: + assert resp.content == f.read() + diff --git a/test/test_console.py b/test/test_console.py index 4fd9bb9f..0c5b4591 100644 --- a/test/test_console.py +++ b/test/test_console.py @@ -1,10 +1,9 @@ -import os -from nose.plugins.skip import SkipTest -if os.name == "nt": - raise SkipTest("Skipped on Windows.") - +import os, sys, mock, gc +from os.path import normpath +import mock_urwid from libmproxy import console from libmproxy.console import common + import tutils class TestConsoleState: @@ -16,7 +15,7 @@ class TestConsoleState: """ c = console.ConsoleState() f = self._add_request(c) - assert f.request in c._flow_map + assert f in c._flow_list assert c.get_focus() == (f, 0) def test_focus(self): @@ -89,6 +88,7 @@ class TestConsoleState: assert len(c.flowsettings) == 1 c.delete_flow(f) del f + gc.collect() assert len(c.flowsettings) == 0 @@ -107,19 +107,17 @@ def test_format_keyvals(): class TestPathCompleter: def test_lookup_construction(self): c = console._PathCompleter() - assert c.complete("/tm") == "/tmp/" - c.reset() cd = tutils.test_data.path("completion") ca = os.path.join(cd, "a") - assert c.complete(ca).endswith("/completion/aaa") - assert c.complete(ca).endswith("/completion/aab") + assert c.complete(ca).endswith(normpath("/completion/aaa")) + assert c.complete(ca).endswith(normpath("/completion/aab")) c.reset() ca = os.path.join(cd, "aaa") - assert c.complete(ca).endswith("/completion/aaa") - assert c.complete(ca).endswith("/completion/aaa") + assert c.complete(ca).endswith(normpath("/completion/aaa")) + assert c.complete(ca).endswith(normpath("/completion/aaa")) c.reset() - assert c.complete(cd).endswith("/completion/aaa") + assert c.complete(cd).endswith(normpath("/completion/aaa")) def test_completion(self): c = console._PathCompleter(True) diff --git a/test/test_dump.py b/test/test_dump.py index a958a2ec..8b4b9aa5 100644 --- a/test/test_dump.py +++ b/test/test_dump.py @@ -6,11 +6,11 @@ import mock def test_strfuncs(): t = tutils.tresp() - t._set_replay() + t.is_replay = True dump.str_response(t) t = tutils.treq() - t.client_conn = None + t.flow.client_conn = None t.stickycookie = True assert "stickycookie" in dump.str_request(t, False) assert "stickycookie" in dump.str_request(t, True) @@ -20,24 +20,20 @@ def test_strfuncs(): class TestDumpMaster: def _cycle(self, m, content): - req = tutils.treq() - req.content = content + req = tutils.treq(content=content) l = proxy.Log("connect") l.reply = mock.MagicMock() m.handle_log(l) - cc = req.client_conn - cc.connection_error = "error" - resp = tutils.tresp(req) - resp.content = content + cc = req.flow.client_conn + cc.reply = mock.MagicMock() m.handle_clientconnect(cc) - sc = proxy.ServerConnection(m.o, req.scheme, req.host, req.port, None) + sc = proxy.ServerConnection((req.get_host(), req.get_port()), None) sc.reply = mock.MagicMock() m.handle_serverconnection(sc) m.handle_request(req) + resp = tutils.tresp(req, content=content) f = m.handle_response(resp) - cd = flow.ClientDisconnect(cc) - cd.reply = mock.MagicMock() - m.handle_clientdisconnect(cd) + m.handle_clientdisconnect(cc) return f def _dummy_cycle(self, n, filt, content, **options): diff --git a/test/test_filt.py b/test/test_filt.py index 4e059196..452a4505 100644 --- a/test/test_filt.py +++ b/test/test_filt.py @@ -1,6 +1,8 @@ import cStringIO from libmproxy import filt, flow - +from libmproxy.protocol import http +from libmproxy.protocol.primitives import Error +import tutils class TestParsing: def _dump(self, x): @@ -72,41 +74,37 @@ class TestParsing: class TestMatching: def req(self): - conn = flow.ClientConnect(("one", 2222)) headers = flow.ODictCaseless() headers["header"] = ["qvalue"] - req = flow.Request( - conn, - (1, 1), - "host", - 80, - "http", - "GET", - "/path", - headers, - "content_request" + req = http.HTTPRequest( + "absolute", + "GET", + "http", + "host", + 80, + "/path", + (1, 1), + headers, + "content_request", + None, + None ) - return flow.Flow(req) + f = http.HTTPFlow(tutils.tclient_conn(), None) + f.request = req + return f def resp(self): f = self.req() headers = flow.ODictCaseless() headers["header_response"] = ["svalue"] - f.response = flow.Response( - f.request, - (1, 1), - 200, - "message", - headers, - "content_response", - None - ) + f.response = http.HTTPResponse((1, 1), 200, "OK", headers, "content_response", None, None) + return f def err(self): f = self.req() - f.error = flow.Error(f.request, "msg") + f.error = Error("msg") return f def q(self, q, o): diff --git a/test/test_flow.py b/test/test_flow.py index f9198f0c..fbead1ca 100644 --- a/test/test_flow.py +++ b/test/test_flow.py @@ -1,7 +1,10 @@ import Queue, time, os.path from cStringIO import StringIO import email.utils -from libmproxy import filt, flow, controller, utils, tnetstring, proxy +from libmproxy import filt, protocol, controller, utils, tnetstring, proxy, flow +from libmproxy.protocol.primitives import Error, Flow +from libmproxy.protocol.http import decoded +from netlib import tcp import tutils @@ -10,8 +13,7 @@ def test_app_registry(): ar.add("foo", "domain", 80) r = tutils.treq() - r.host = "domain" - r.port = 80 + r.set_url("http://domain:80/") assert ar.get(r) r.port = 81 @@ -30,7 +32,7 @@ class TestStickyCookieState: def _response(self, cookie, host): s = flow.StickyCookieState(filt.parse(".*")) f = tutils.tflow_full() - f.request.host = host + f.server_conn.address = tcp.Address((host, 80)) f.response.headers["Set-Cookie"] = [cookie] s.handle_response(f) return s, f @@ -66,7 +68,7 @@ class TestStickyAuthState: f = tutils.tflow_full() f.request.headers["authorization"] = ["foo"] s.handle_request(f) - assert "host" in s.hosts + assert "address" in s.hosts f = tutils.tflow_full() s.handle_request(f) @@ -171,8 +173,14 @@ class TestServerPlaybackState: class TestFlow: def test_copy(self): f = tutils.tflow_full() + a0 = f._get_state() f2 = f.copy() + a = f._get_state() + b = f2._get_state() + assert f._get_state() == f2._get_state() + assert not f == f2 assert not f is f2 + assert f.request == f2.request assert not f.request is f2.request assert f.request.headers == f2.request.headers assert not f.request.headers is f2.request.headers @@ -189,9 +197,7 @@ class TestFlow: assert not f.error is f2.error def test_match(self): - f = tutils.tflow() - f.response = tutils.tresp() - f.request = f.response.request + f = tutils.tflow_full() assert not f.match("~b test") assert f.match(None) assert not f.match("~b test") @@ -201,11 +207,9 @@ class TestFlow: tutils.raises(ValueError, f.match, "~") - def test_backup(self): f = tutils.tflow() f.response = tutils.tresp() - f.request = f.response.request f.request.content = "foo" assert not f.modified() f.backup() @@ -222,18 +226,19 @@ class TestFlow: f.revert() def test_getset_state(self): - f = tutils.tflow() - f.response = tutils.tresp(f.request) + f = tutils.tflow_full() state = f._get_state() - assert f._get_state() == flow.Flow._from_state(state)._get_state() + assert f._get_state() == protocol.http.HTTPFlow._from_state(state)._get_state() f.response = None - f.error = flow.Error(f.request, "error") + f.error = Error("error") state = f._get_state() - assert f._get_state() == flow.Flow._from_state(state)._get_state() + assert f._get_state() == protocol.http.HTTPFlow._from_state(state)._get_state() - f2 = tutils.tflow() - f2.error = flow.Error(f.request, "e2") + f2 = f.copy() + assert f._get_state() == f2._get_state() + assert not f == f2 + f2.error = Error("e2") assert not f == f2 f._load_state(f2._get_state()) assert f._get_state() == f2._get_state() @@ -249,7 +254,6 @@ class TestFlow: assert f.request.reply.acked f.intercept() f.response = tutils.tresp() - f.request = f.response.request f.request.reply() assert not f.response.reply.acked f.kill(fm) @@ -279,17 +283,12 @@ class TestFlow: f.accept_intercept() assert f.request.reply.acked f.response = tutils.tresp() - f.request = f.response.request f.intercept() f.request.reply() assert not f.response.reply.acked f.accept_intercept() assert f.response.reply.acked - def test_serialization(self): - f = flow.Flow(None) - f.request = tutils.treq() - def test_replace_unicode(self): f = tutils.tflow_full() f.response.content = "\xc2foo" @@ -310,10 +309,6 @@ class TestFlow: assert f.response.headers["bar"] == ["bar"] assert f.response.content == "abarb" - f = tutils.tflow_err() - f.replace("error", "bar") - assert f.error.msg == "bar" - def test_replace_encoded(self): f = tutils.tflow_full() f.request.content = "afoob" @@ -348,30 +343,27 @@ class TestState: connect -> request -> response """ - bc = flow.ClientConnect(("address", 22)) + bc = tutils.tclient_conn() c = flow.State() req = tutils.treq(bc) f = c.add_request(req) assert f assert c.flow_count() == 1 - assert c._flow_map.get(req) assert c.active_flow_count() == 1 newreq = tutils.treq() assert c.add_request(newreq) - assert c._flow_map.get(newreq) assert c.active_flow_count() == 2 resp = tutils.tresp(req) assert c.add_response(resp) assert c.flow_count() == 2 - assert c._flow_map.get(resp.request) assert c.active_flow_count() == 1 unseen_resp = tutils.tresp() + unseen_resp.flow = None assert not c.add_response(unseen_resp) - assert not c._flow_map.get(unseen_resp.request) assert c.active_flow_count() == 1 resp = tutils.tresp(newreq) @@ -382,19 +374,18 @@ class TestState: c = flow.State() req = tutils.treq() f = c.add_request(req) - e = flow.Error(f.request, "message") - assert c.add_error(e) + f.error = Error("message") + assert c.add_error(f.error) - e = flow.Error(tutils.tflow().request, "message") + e = Error("message") assert not c.add_error(e) c = flow.State() req = tutils.treq() f = c.add_request(req) - e = flow.Error(f.request, "message") + e = tutils.terr() c.set_limit("~e") assert not c.view - assert not c.view assert c.add_error(e) assert c.view @@ -448,7 +439,7 @@ class TestState: def _add_error(self, state): req = tutils.treq() f = state.add_request(req) - f.error = flow.Error(f.request, "msg") + f.error = Error("msg") def test_clear(self): c = flow.State() @@ -472,7 +463,7 @@ class TestState: c.clear() c.load_flows(flows) - assert isinstance(c._flow_list[0], flow.Flow) + assert isinstance(c._flow_list[0], Flow) def test_accept_all(self): c = flow.State() @@ -585,7 +576,7 @@ class TestFlowMaster: fm = flow.FlowMaster(None, s) assert not fm.load_script(tutils.test_data.path("scripts/reqerr.py")) req = tutils.treq() - fm.handle_clientconnect(req.client_conn) + fm.handle_clientconnect(req.flow.client_conn) assert fm.handle_request(req) def test_script(self): @@ -593,9 +584,9 @@ class TestFlowMaster: fm = flow.FlowMaster(None, s) assert not fm.load_script(tutils.test_data.path("scripts/all.py")) req = tutils.treq() - fm.handle_clientconnect(req.client_conn) + fm.handle_clientconnect(req.flow.client_conn) assert fm.scripts[0].ns["log"][-1] == "clientconnect" - sc = proxy.ServerConnection(None, req.scheme, req.host, req.port, None) + sc = proxy.ServerConnection((req.get_host(), req.get_port()), None) sc.reply = controller.DummyReply() fm.handle_serverconnection(sc) assert fm.scripts[0].ns["log"][-1] == "serverconnect" @@ -607,9 +598,7 @@ class TestFlowMaster: #load second script assert not fm.load_script(tutils.test_data.path("scripts/all.py")) assert len(fm.scripts) == 2 - dc = flow.ClientDisconnect(req.client_conn) - dc.reply = controller.DummyReply() - fm.handle_clientdisconnect(dc) + fm.handle_clientdisconnect(sc) assert fm.scripts[0].ns["log"][-1] == "clientdisconnect" assert fm.scripts[1].ns["log"][-1] == "clientdisconnect" @@ -619,7 +608,7 @@ class TestFlowMaster: assert len(fm.scripts) == 0 assert not fm.load_script(tutils.test_data.path("scripts/all.py")) - err = flow.Error(f.request, "msg") + err = tutils.terr() err.reply = controller.DummyReply() fm.handle_error(err) assert fm.scripts[0].ns["log"][-1] == "error" @@ -633,7 +622,7 @@ class TestFlowMaster: f2 = fm.duplicate_flow(f) assert f2.response assert s.flow_count() == 2 - assert s.index(f2) + assert s.index(f2) == 1 def test_all(self): s = flow.State() @@ -641,7 +630,7 @@ class TestFlowMaster: fm.anticache = True fm.anticomp = True req = tutils.treq() - fm.handle_clientconnect(req.client_conn) + fm.handle_clientconnect(req.flow.client_conn) f = fm.handle_request(req) assert s.flow_count() == 1 @@ -651,16 +640,14 @@ class TestFlowMaster: assert s.flow_count() == 1 rx = tutils.tresp() + rx.flow = None assert not fm.handle_response(rx) - dc = flow.ClientDisconnect(req.client_conn) - dc.reply = controller.DummyReply() - req.client_conn.requestcount = 1 - fm.handle_clientdisconnect(dc) + fm.handle_clientdisconnect(req.flow.client_conn) - err = flow.Error(f.request, "msg") - err.reply = controller.DummyReply() - fm.handle_error(err) + f.error = Error("msg") + f.error.reply = controller.DummyReply() + fm.handle_error(f.error) fm.load_script(tutils.test_data.path("scripts/a.py")) fm.shutdown() @@ -679,9 +666,9 @@ class TestFlowMaster: fm.tick(q) assert fm.state.flow_count() - err = flow.Error(f.request, "error") - err.reply = controller.DummyReply() - fm.handle_error(err) + f.error = Error("error") + f.error.reply = controller.DummyReply() + fm.handle_error(f.error) def test_server_playback(self): controller.should_exit = False @@ -784,20 +771,16 @@ class TestFlowMaster: assert r()[0].response - tf = tutils.tflow_full() + tf = tutils.tflow() fm.start_stream(file(p, "ab"), None) fm.handle_request(tf.request) fm.shutdown() assert not r()[1].response - class TestRequest: def test_simple(self): - h = flow.ODictCaseless() - h["test"] = ["test"] - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") + r = tutils.treq() u = r.get_url() assert r.set_url(u) assert not r.set_url("") @@ -812,31 +795,34 @@ class TestRequest: assert r._assemble() assert r.size() == len(r._assemble()) - r.close = True - assert "connection: close" in r._assemble() - - assert r._assemble(True) - r.content = flow.CONTENT_MISSING - assert not r._assemble() + tutils.raises("Cannot assemble flow with CONTENT_MISSING", r._assemble) def test_get_url(self): - h = flow.ODictCaseless() - h["test"] = ["test"] - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") - assert r.get_url() == "https://host:22/" - assert r.get_url(hostheader=True) == "https://host:22/" + r = tutils.tflow().request + + assert r.get_url() == "http://address:22/path" + + r.flow.server_conn.ssl_established = True + assert r.get_url() == "https://address:22/path" + + r.flow.server_conn.address = tcp.Address(("host", 42)) + assert r.get_url() == "https://host:42/path" + + r.host = "address" + r.port = 22 + assert r.get_url() == "https://address:22/path" + + assert r.get_url(hostheader=True) == "https://address:22/path" r.headers["Host"] = ["foo.com"] - assert r.get_url() == "https://host:22/" - assert r.get_url(hostheader=True) == "https://foo.com:22/" + assert r.get_url() == "https://address:22/path" + assert r.get_url(hostheader=True) == "https://foo.com:22/path" def test_path_components(self): - h = flow.ODictCaseless() - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") + r = tutils.treq() + r.path = "/" assert r.get_path_components() == [] - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/foo/bar", h, "content") + r.path = "/foo/bar" assert r.get_path_components() == ["foo", "bar"] q = flow.ODict() q["test"] = ["123"] @@ -852,10 +838,9 @@ class TestRequest: assert "%2F" in r.path def test_getset_form_urlencoded(self): - h = flow.ODictCaseless() - h["content-type"] = [flow.HDR_FORM_URLENCODED] d = flow.ODict([("one", "two"), ("three", "four")]) - r = flow.Request(None, (1, 1), "host", 22, "https", "GET", "/", h, utils.urlencode(d.lst)) + r = tutils.treq(content=utils.urlencode(d.lst)) + r.headers["content-type"] = [protocol.http.HDR_FORM_URLENCODED] assert r.get_form_urlencoded() == d d = flow.ODict([("x", "y")]) @@ -868,19 +853,20 @@ class TestRequest: def test_getset_query(self): h = flow.ODictCaseless() - r = flow.Request(None, (1, 1), "host", 22, "https", "GET", "/foo?x=y&a=b", h, "content") + r = tutils.treq() + r.path = "/foo?x=y&a=b" q = r.get_query() assert q.lst == [("x", "y"), ("a", "b")] - r = flow.Request(None, (1, 1), "host", 22, "https", "GET", "/", h, "content") + r.path = "/" q = r.get_query() assert not q - r = flow.Request(None, (1, 1), "host", 22, "https", "GET", "/?adsfa", h, "content") + r.path = "/?adsfa" q = r.get_query() assert q.lst == [("adsfa", "")] - r = flow.Request(None, (1, 1), "host", 22, "https", "GET", "/foo?x=y&a=b", h, "content") + r.path = "/foo?x=y&a=b" assert r.get_query() r.set_query(flow.ODict([])) assert not r.get_query() @@ -890,34 +876,14 @@ class TestRequest: def test_anticache(self): h = flow.ODictCaseless() - r = flow.Request(None, (1, 1), "host", 22, "https", "GET", "/", h, "content") + r = tutils.treq() + r.headers = h h["if-modified-since"] = ["test"] h["if-none-match"] = ["test"] r.anticache() assert not "if-modified-since" in r.headers assert not "if-none-match" in r.headers - def test_getset_state(self): - h = flow.ODictCaseless() - h["test"] = ["test"] - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") - state = r._get_state() - assert flow.Request._from_state(state) == r - - r.client_conn = None - state = r._get_state() - assert flow.Request._from_state(state) == r - - r2 = flow.Request(c, (1, 1), "testing", 20, "http", "PUT", "/foo", h, "test") - assert not r == r2 - r._load_state(r2._get_state()) - assert r == r2 - - r2.client_conn = None - r._load_state(r2._get_state()) - assert not r.client_conn - def test_replace(self): r = tutils.treq() r.path = "path/foo" @@ -975,15 +941,15 @@ class TestRequest: def test_get_cookies_none(self): h = flow.ODictCaseless() - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") - assert r.get_cookies() == None + r = tutils.treq() + r.headers = h + assert r.get_cookies() is None def test_get_cookies_single(self): h = flow.ODictCaseless() h["Cookie"] = ["cookiename=cookievalue"] - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") + r = tutils.treq() + r.headers = h result = r.get_cookies() assert len(result)==1 assert result['cookiename']==('cookievalue',{}) @@ -991,8 +957,8 @@ class TestRequest: def test_get_cookies_double(self): h = flow.ODictCaseless() h["Cookie"] = ["cookiename=cookievalue;othercookiename=othercookievalue"] - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") + r = tutils.treq() + r.headers = h result = r.get_cookies() assert len(result)==2 assert result['cookiename']==('cookievalue',{}) @@ -1001,49 +967,35 @@ class TestRequest: def test_get_cookies_withequalsign(self): h = flow.ODictCaseless() h["Cookie"] = ["cookiename=coo=kievalue;othercookiename=othercookievalue"] - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") + r = tutils.treq() + r.headers = h result = r.get_cookies() assert len(result)==2 assert result['cookiename']==('coo=kievalue',{}) assert result['othercookiename']==('othercookievalue',{}) - def test_get_header_size(self): + def test_header_size(self): h = flow.ODictCaseless() h["headername"] = ["headervalue"] - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") - result = r.get_header_size() - assert result==43 - - def test_get_transmitted_size(self): - h = flow.ODictCaseless() - h["headername"] = ["headervalue"] - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") - result = r.get_transmitted_size() - assert result==len("content") - r.content = None - assert r.get_transmitted_size() == 0 + r = tutils.treq() + r.headers = h + result = len(r._assemble_headers()) + assert result == 62 def test_get_content_type(self): h = flow.ODictCaseless() h["Content-Type"] = ["text/plain"] - c = flow.ClientConnect(("addr", 2222)) - r = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") - assert r.get_content_type()=="text/plain" + resp = tutils.tresp() + resp.headers = h + assert resp.headers.get_first("content-type") == "text/plain" class TestResponse: def test_simple(self): - h = flow.ODictCaseless() - h["test"] = ["test"] - c = flow.ClientConnect(("addr", 2222)) - req = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") - resp = flow.Response(req, (1, 1), 200, "msg", h.copy(), "content", None) + f = tutils.tflow_full() + resp = f.response assert resp._assemble() assert resp.size() == len(resp._assemble()) - resp2 = resp.copy() assert resp2 == resp @@ -1052,7 +1004,7 @@ class TestResponse: assert resp.size() == len(resp._assemble()) resp.content = flow.CONTENT_MISSING - assert not resp._assemble() + tutils.raises("Cannot assemble flow with CONTENT_MISSING", resp._assemble) def test_refresh(self): r = tutils.tresp() @@ -1081,21 +1033,6 @@ class TestResponse: c = "MOO=BAR; Expires=Tue, 08-Mar-2011 00:20:38 GMT; Path=foo.com; Secure" assert "00:21:38" in r._refresh_cookie(c, 60) - def test_getset_state(self): - h = flow.ODictCaseless() - h["test"] = ["test"] - c = flow.ClientConnect(("addr", 2222)) - req = flow.Request(c, (1, 1), "host", 22, "https", "GET", "/", h, "content") - resp = flow.Response(req, (1, 1), 200, "msg", h.copy(), "content", None) - - state = resp._get_state() - assert flow.Response._from_state(req, state) == resp - - resp2 = flow.Response(req, (1, 1), 220, "foo", h.copy(), "test", None) - assert not resp == resp2 - resp._load_state(resp2._get_state()) - assert resp == resp2 - def test_replace(self): r = tutils.tresp() r.headers["Foo"] = ["fOo"] @@ -1108,7 +1045,7 @@ class TestResponse: r = tutils.tresp() r.headers["content-encoding"] = ["identity"] r.content = "falafel" - r.decode() + assert r.decode() assert not r.headers["content-encoding"] assert r.content == "falafel" @@ -1125,24 +1062,30 @@ class TestResponse: r.encode("gzip") assert r.headers["content-encoding"] == ["gzip"] assert r.content != "falafel" - r.decode() + assert r.decode() assert not r.headers["content-encoding"] assert r.content == "falafel" - def test_get_header_size(self): + r.headers["content-encoding"] = ["gzip"] + assert not r.decode() + assert r.content == "falafel" + + def test_header_size(self): r = tutils.tresp() - result = r.get_header_size() - assert result==49 + result = len(r._assemble_headers()) + assert result==44 def test_get_cookies_none(self): h = flow.ODictCaseless() - resp = flow.Response(None, (1, 1), 200, "OK", h, "content", None) + resp = tutils.tresp() + resp.headers = h assert not resp.get_cookies() def test_get_cookies_simple(self): h = flow.ODictCaseless() h["Set-Cookie"] = ["cookiename=cookievalue"] - resp = flow.Response(None, (1, 1), 200, "OK", h, "content", None) + resp = tutils.tresp() + resp.headers = h result = resp.get_cookies() assert len(result)==1 assert "cookiename" in result @@ -1151,7 +1094,8 @@ class TestResponse: def test_get_cookies_with_parameters(self): h = flow.ODictCaseless() h["Set-Cookie"] = ["cookiename=cookievalue;domain=example.com;expires=Wed Oct 21 16:29:41 2015;path=/; HttpOnly"] - resp = flow.Response(None, (1, 1), 200, "OK", h, "content", None) + resp = tutils.tresp() + resp.headers = h result = resp.get_cookies() assert len(result)==1 assert "cookiename" in result @@ -1165,7 +1109,8 @@ class TestResponse: def test_get_cookies_no_value(self): h = flow.ODictCaseless() h["Set-Cookie"] = ["cookiename=; Expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/"] - resp = flow.Response(None, (1, 1), 200, "OK", h, "content", None) + resp = tutils.tresp() + resp.headers = h result = resp.get_cookies() assert len(result)==1 assert "cookiename" in result @@ -1175,7 +1120,8 @@ class TestResponse: def test_get_cookies_twocookies(self): h = flow.ODictCaseless() h["Set-Cookie"] = ["cookiename=cookievalue","othercookie=othervalue"] - resp = flow.Response(None, (1, 1), 200, "OK", h, "content", None) + resp = tutils.tresp() + resp.headers = h result = resp.get_cookies() assert len(result)==2 assert "cookiename" in result @@ -1186,19 +1132,20 @@ class TestResponse: def test_get_content_type(self): h = flow.ODictCaseless() h["Content-Type"] = ["text/plain"] - resp = flow.Response(None, (1, 1), 200, "OK", h, "content", None) - assert resp.get_content_type()=="text/plain" + resp = tutils.tresp() + resp.headers = h + assert resp.headers.get_first("content-type") == "text/plain" class TestError: def test_getset_state(self): - e = flow.Error(None, "Error") + e = Error("Error") state = e._get_state() - assert flow.Error._from_state(None, state) == e + assert Error._from_state(state) == e assert e.copy() - e2 = flow.Error(None, "bar") + e2 = Error("bar") assert not e == e2 e._load_state(e2._get_state()) assert e == e2 @@ -1207,23 +1154,20 @@ class TestError: e3 = e.copy() assert e3 == e - def test_replace(self): - e = flow.Error(None, "amoop") - e.replace("moo", "bar") - assert e.msg == "abarp" - -class TestClientConnect: +class TestClientConnection: def test_state(self): - c = flow.ClientConnect(("a", 22)) - assert flow.ClientConnect._from_state(c._get_state()) == c - c2 = flow.ClientConnect(("a", 25)) + c = tutils.tclient_conn() + assert proxy.ClientConnection._from_state(c._get_state()) == c + + c2 = tutils.tclient_conn() + c2.address.address = (c2.address.host, 4242) assert not c == c2 - c2.requestcount = 99 + c2.timestamp_start = 42 c._load_state(c2._get_state()) - assert c.requestcount == 99 + assert c.timestamp_start == 42 c3 = c.copy() assert c3 == c @@ -1238,13 +1182,13 @@ def test_decoded(): r.encode("gzip") assert r.headers["content-encoding"] assert r.content != "content" - with flow.decoded(r): + with decoded(r): assert not r.headers["content-encoding"] assert r.content == "content" assert r.headers["content-encoding"] assert r.content != "content" - with flow.decoded(r): + with decoded(r): r.content = "foo" assert r.content != "foo" diff --git a/test/test_fuzzing.py b/test/test_fuzzing.py index ba7b751c..646ce5c1 100644 --- a/test/test_fuzzing.py +++ b/test/test_fuzzing.py @@ -32,8 +32,8 @@ class TestFuzzy(tservers.HTTPProxTest): assert p.request(req%self.server.port).status_code == 502 def test_upstream_disconnect(self): - req = r'200:d0:h"Date"="Sun, 03 Mar 2013 04:00:00 GMT"' + req = r'200:d0' p = self.pathod(req) - assert p.status_code == 400 + assert p.status_code == 502 diff --git a/test/test_protocol_http.py b/test/test_protocol_http.py new file mode 100644 index 00000000..3bf5af22 --- /dev/null +++ b/test/test_protocol_http.py @@ -0,0 +1,201 @@ +from libmproxy import proxy # FIXME: Remove +from libmproxy.protocol.http import * +from libmproxy.protocol import KILL +from cStringIO import StringIO +import tutils, tservers + + +def test_HttpAuthenticationError(): + x = HttpAuthenticationError({"foo": "bar"}) + assert str(x) + assert "foo" in x.auth_headers + + +def test_stripped_chunked_encoding_no_content(): + """ + https://github.com/mitmproxy/mitmproxy/issues/186 + """ + r = tutils.tresp(content="") + r.headers["Transfer-Encoding"] = ["chunked"] + assert "Content-Length" in r._assemble_headers() + + r = tutils.treq(content="") + r.headers["Transfer-Encoding"] = ["chunked"] + assert "Content-Length" in r._assemble_headers() + + +class TestHTTPRequest: + def test_asterisk_form(self): + s = StringIO("OPTIONS * HTTP/1.1") + f = tutils.tflow_noreq() + f.request = HTTPRequest.from_stream(s) + assert f.request.form_in == "asterisk" + x = f.request._assemble() + assert f.request._assemble() == "OPTIONS * HTTP/1.1\r\nHost: address:22\r\n\r\n" + + def test_origin_form(self): + s = StringIO("GET /foo\xff HTTP/1.1") + tutils.raises("Bad HTTP request line", HTTPRequest.from_stream, s) + + def test_authority_form(self): + s = StringIO("CONNECT oops-no-port.com HTTP/1.1") + tutils.raises("Bad HTTP request line", HTTPRequest.from_stream, s) + s = StringIO("CONNECT address:22 HTTP/1.1") + r = HTTPRequest.from_stream(s) + assert r._assemble() == "CONNECT address:22 HTTP/1.1\r\nHost: address:22\r\n\r\n" + + + def test_absolute_form(self): + s = StringIO("GET oops-no-protocol.com HTTP/1.1") + tutils.raises("Bad HTTP request line", HTTPRequest.from_stream, s) + s = StringIO("GET http://address:22/ HTTP/1.1") + r = HTTPRequest.from_stream(s) + assert r._assemble() == "GET http://address:22/ HTTP/1.1\r\nHost: address:22\r\n\r\n" + + def test_assemble_unknown_form(self): + r = tutils.treq() + tutils.raises("Invalid request form", r._assemble, "antiauthority") + + + def test_set_url(self): + r = tutils.treq_absolute() + r.set_url("https://otheraddress:42/ORLY") + assert r.scheme == "https" + assert r.host == "otheraddress" + assert r.port == 42 + assert r.path == "/ORLY" + + +class TestHTTPResponse: + def test_read_from_stringio(self): + _s = "HTTP/1.1 200 OK\r\n" \ + "Content-Length: 7\r\n" \ + "\r\n"\ + "content\r\n" \ + "HTTP/1.1 204 OK\r\n" \ + "\r\n" + s = StringIO(_s) + r = HTTPResponse.from_stream(s, "GET") + assert r.code == 200 + assert r.content == "content" + assert HTTPResponse.from_stream(s, "GET").code == 204 + + s = StringIO(_s) + r = HTTPResponse.from_stream(s, "HEAD") # HEAD must not have content by spec. We should leave it on the pipe. + assert r.code == 200 + assert r.content == "" + tutils.raises("Invalid server response: 'content", HTTPResponse.from_stream, s, "GET") + + +class TestInvalidRequests(tservers.HTTPProxTest): + ssl = True + + def test_double_connect(self): + p = self.pathoc() + r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port)) + assert r.status_code == 502 + assert "Must not CONNECT on already encrypted connection" in r.content + + def test_origin_request(self): + p = self.pathoc_raw() + p.connect() + r = p.request("get:/p/200") + assert r.status_code == 400 + assert "Invalid request form" in r.content + + +class TestProxyChaining(tservers.HTTPChainProxyTest): + def test_all(self): + self.chain[1].tmaster.replacehooks.add("~q", "foo", "bar") # replace in request + self.chain[0].tmaster.replacehooks.add("~q", "foo", "oh noes!") + self.proxy.tmaster.replacehooks.add("~q", "bar", "baz") + self.chain[0].tmaster.replacehooks.add("~s", "baz", "ORLY") # replace in response + + p = self.pathoc() + req = p.request("get:'%s/p/418:b\"foo\"'" % self.server.urlbase) + assert req.content == "ORLY" + assert req.status_code == 418 + +class TestProxyChainingSSL(tservers.HTTPChainProxyTest): + ssl = True + + def test_simple(self): + + p = self.pathoc() + req = p.request("get:'/p/418:b\"content\"'") + assert req.content == "content" + assert req.status_code == 418 + + assert self.chain[1].tmaster.state.flow_count() == 2 # CONNECT from pathoc to chain[0], + # request from pathoc to chain[0] + assert self.chain[0].tmaster.state.flow_count() == 2 # CONNECT from chain[1] to proxy, + # request from chain[1] to proxy + assert self.proxy.tmaster.state.flow_count() == 1 # request from chain[0] (regular proxy doesn't store CONNECTs) + +class TestProxyChainingSSLReconnect(tservers.HTTPChainProxyTest): + 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. + """ + def kill_requests(master, attr, exclude): + k = [0] # variable scope workaround: put into array + _func = getattr(master, attr) + def handler(r): + k[0] += 1 + if not (k[0] in exclude): + r.flow.client_conn.finish() + r.flow.error = Error("terminated") + r.reply(KILL) + return _func(r) + setattr(master, attr, handler) + + kill_requests(self.proxy.tmaster, "handle_request", + exclude=[ + # fail first request + 2, # allow second request + ]) + + kill_requests(self.chain[0].tmaster, "handle_request", + exclude=[ + 1, # CONNECT + # fail first request + 3, # reCONNECT + 4, # request + ]) + + p = self.pathoc() + req = p.request("get:'/p/418:b\"content\"'") + assert self.chain[1].tmaster.state.flow_count() == 2 # CONNECT and request + assert self.chain[0].tmaster.state.flow_count() == 4 # CONNECT, failing request, + # reCONNECT, request + assert self.proxy.tmaster.state.flow_count() == 2 # failing request, request + # (doesn't store (repeated) CONNECTs from chain[0] + # as it is a regular proxy) + assert req.content == "content" + assert req.status_code == 418 + + assert not self.proxy.tmaster.state._flow_list[0].response # killed + assert self.proxy.tmaster.state._flow_list[1].response + + assert self.chain[1].tmaster.state._flow_list[0].request.form_in == "authority" + assert self.chain[1].tmaster.state._flow_list[1].request.form_in == "origin" + + assert self.chain[0].tmaster.state._flow_list[0].request.form_in == "authority" + assert self.chain[0].tmaster.state._flow_list[1].request.form_in == "origin" + assert self.chain[0].tmaster.state._flow_list[2].request.form_in == "authority" + assert self.chain[0].tmaster.state._flow_list[3].request.form_in == "origin" + + assert self.proxy.tmaster.state._flow_list[0].request.form_in == "origin" + assert self.proxy.tmaster.state._flow_list[1].request.form_in == "origin" + + req = p.request("get:'/p/418:b\"content2\"'") + + assert req.status_code == 502 + assert self.chain[1].tmaster.state.flow_count() == 3 # + new request + assert self.chain[0].tmaster.state.flow_count() == 6 # + new request, repeated CONNECT from chain[1] + # (both terminated) + assert self.proxy.tmaster.state.flow_count() == 2 # nothing happened here diff --git a/test/test_proxy.py b/test/test_proxy.py index 371e5ef7..c42d66e7 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -19,25 +19,24 @@ class TestServerConnection: self.d.shutdown() def test_simple(self): - sc = proxy.ServerConnection(proxy.ProxyConfig(), "http", self.d.IFACE, self.d.port, "host.com") + sc = proxy.ServerConnection((self.d.IFACE, self.d.port), None) sc.connect() r = tutils.treq() + r.flow.server_conn = sc r.path = "/p/200:da" - sc.send(r) + sc.send(r._assemble()) assert http.read_response(sc.rfile, r.method, 1000) assert self.d.last_log() - r.content = flow.CONTENT_MISSING - tutils.raises("incomplete request", sc.send, r) - - sc.terminate() + sc.finish() def test_terminate_error(self): - sc = proxy.ServerConnection(proxy.ProxyConfig(), "http", self.d.IFACE, self.d.port, "host.com") + sc = proxy.ServerConnection((self.d.IFACE, self.d.port), None) sc.connect() sc.connection = mock.Mock() + sc.connection.recv = mock.Mock(return_value=False) sc.connection.flush = mock.Mock(side_effect=tcp.NetLibDisconnect) - sc.terminate() + sc.finish() class MockParser: diff --git a/test/test_script.py b/test/test_script.py index 025e9f37..13903066 100644 --- a/test/test_script.py +++ b/test/test_script.py @@ -32,8 +32,8 @@ class TestScript: r = tutils.treq() fm.handle_request(r) assert fm.state.flow_count() == 2 - assert not fm.state.view[0].request.is_replay() - assert fm.state.view[1].request.is_replay() + assert not fm.state.view[0].request.is_replay + assert fm.state.view[1].request.is_replay def test_err(self): s = flow.State() @@ -75,9 +75,6 @@ class TestScript: # Two instantiations assert m.call_count == 2 assert (time.time() - t_start) < 0.09 - time.sleep(0.2) - # Plus two invocations - assert m.call_count == 4 def test_concurrent2(self): s = flow.State() @@ -89,13 +86,17 @@ class TestScript: f.reply = f.request.reply with mock.patch("libmproxy.controller.DummyReply.__call__") as m: + t_start = time.time() s.run("clientconnect", f) s.run("serverconnect", f) s.run("response", f) s.run("error", f) s.run("clientdisconnect", f) - time.sleep(0.1) - assert m.call_count == 5 + while (time.time() - t_start) < 1 and m.call_count <= 5: + if m.call_count == 5: + return + time.sleep(0.001) + assert False def test_concurrent_err(self): s = flow.State() diff --git a/test/test_server.py b/test/test_server.py index 646460ab..2f9e6728 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -4,6 +4,7 @@ from netlib import tcp, http_auth, http from libpathod import pathoc, pathod import tutils, tservers from libmproxy import flow, proxy +from libmproxy.protocol import KILL """ Note that the choice of response code in these tests matters more than you @@ -41,16 +42,17 @@ class CommonMixin: assert f.status_code == 304 l = self.master.state.view[0] - assert l.request.client_conn.address + assert l.client_conn.address assert "host" in l.request.headers assert l.response.code == 304 def test_invalid_http(self): - t = tcp.TCPClient("127.0.0.1", self.proxy.port) + t = tcp.TCPClient(("127.0.0.1", self.proxy.port)) t.connect() t.wfile.write("invalid\r\n\r\n") t.wfile.flush() - assert "Bad Request" in t.rfile.readline() + line = t.rfile.readline() + assert ("Bad Request" in line) or ("Bad Gateway" in line) @@ -70,7 +72,7 @@ class TestHTTP(tservers.HTTPProxTest, CommonMixin, AppMixin): assert "ValueError" in ret.content def test_invalid_connect(self): - t = tcp.TCPClient("127.0.0.1", self.proxy.port) + t = tcp.TCPClient(("127.0.0.1", self.proxy.port)) t.connect() t.wfile.write("CONNECT invalid\n\n") t.wfile.flush() @@ -105,22 +107,17 @@ class TestHTTP(tservers.HTTPProxTest, CommonMixin, AppMixin): assert p.request(req) assert p.request(req) - # However, if the server disconnects on our first try, it's an error. - req = "get:'%s/p/200:b@1:d0'"%self.server.urlbase - p = self.pathoc() - tutils.raises("server disconnect", p.request, req) - def test_proxy_ioerror(self): # Tests a difficult-to-trigger condition, where an IOError is raised # within our read loop. - with mock.patch("libmproxy.proxy.ProxyHandler.read_request") as m: + with mock.patch("libmproxy.protocol.http.HTTPRequest.from_stream") as m: m.side_effect = IOError("error!") tutils.raises("server disconnect", self.pathod, "304") def test_get_connection_switching(self): def switched(l): for i in l: - if "switching" in i: + if "serverdisconnect" in i: return True req = "get:'%s/p/200:b@1'" p = self.pathoc() @@ -156,6 +153,7 @@ class TestHTTP(tservers.HTTPProxTest, CommonMixin, AppMixin): connection.close() assert "content-length" in resp.lower() + class TestHTTPAuth(tservers.HTTPProxTest): authenticator = http_auth.BasicProxyAuth(http_auth.PassManSingleUser("test", "test"), "realm") def test_auth(self): @@ -230,12 +228,13 @@ class TestTransparentSSL(tservers.TransparentProxTest, CommonMixin): f = self.pathod("304", sni="testserver.com") assert f.status_code == 304 l = self.server.last_log() - assert self.server.last_log()["request"]["sni"] == "testserver.com" + assert l["request"]["sni"] == "testserver.com" def test_sslerr(self): - p = pathoc.Pathoc("localhost", self.proxy.port) + p = pathoc.Pathoc(("localhost", self.proxy.port)) p.connect() - assert p.request("get:/").status_code == 400 + r = p.request("get:/") + assert r.status_code == 502 class TestProxy(tservers.HTTPProxTest): @@ -243,10 +242,10 @@ class TestProxy(tservers.HTTPProxTest): f = self.pathod("304") assert f.status_code == 304 - l = self.master.state.view[0] - assert l.request.client_conn.address - assert "host" in l.request.headers - assert l.response.code == 304 + f = self.master.state.view[0] + assert f.client_conn.address + assert "host" in f.request.headers + assert f.response.code == 304 def test_response_timestamps(self): # test that we notice at least 2 sec delay between timestamps @@ -288,8 +287,7 @@ class TestProxy(tservers.HTTPProxTest): assert request.timestamp_end - request.timestamp_start <= 0.1 def test_request_tcp_setup_timestamp_presence(self): - # tests that the first request in a tcp connection has a tcp_setup_timestamp - # while others do not + # 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)) connection.send("GET http://localhost:%d/p/304:b@1k HTTP/1.1\r\n"%self.server.port) @@ -300,18 +298,18 @@ class TestProxy(tservers.HTTPProxTest): connection.recv(5000) connection.close() - first_request = self.master.state.view[0].request - second_request = self.master.state.view[1].request - assert first_request.tcp_setup_timestamp - assert first_request.ssl_setup_timestamp == None - assert second_request.tcp_setup_timestamp == None - assert second_request.ssl_setup_timestamp == None + 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 - request = self.master.state.view[0].request - assert request.ip == "127.0.0.1" + f = self.master.state.view[0] + assert f.server_conn.peername == ("127.0.0.1", self.server.port) class TestProxySSL(tservers.HTTPProxTest): ssl=True @@ -320,7 +318,7 @@ class TestProxySSL(tservers.HTTPProxTest): f = self.pathod("304:b@10k") assert f.status_code == 304 first_request = self.master.state.view[0].request - assert first_request.ssl_setup_timestamp + assert first_request.flow.server_conn.timestamp_ssl_setup class MasterFakeResponse(tservers.TestMaster): def handle_request(self, m): @@ -335,10 +333,9 @@ class TestFakeResponse(tservers.HTTPProxTest): assert "header_response" in f.headers.keys() - class MasterKillRequest(tservers.TestMaster): def handle_request(self, m): - m.reply(proxy.KILL) + m.reply(KILL) class TestKillRequest(tservers.HTTPProxTest): @@ -351,7 +348,7 @@ class TestKillRequest(tservers.HTTPProxTest): class MasterKillResponse(tservers.TestMaster): def handle_response(self, m): - m.reply(proxy.KILL) + m.reply(KILL) class TestKillResponse(tservers.HTTPProxTest): diff --git a/test/tservers.py b/test/tservers.py index ac95b168..812e8921 100644 --- a/test/tservers.py +++ b/test/tservers.py @@ -21,13 +21,12 @@ def errapp(environ, start_response): class TestMaster(flow.FlowMaster): - def __init__(self, testq, config): + def __init__(self, config): s = proxy.ProxyServer(config, 0) state = flow.State() flow.FlowMaster.__init__(self, s, state) self.apps.add(testapp, "testapp", 80) self.apps.add(errapp, "errapp", 80) - self.testq = testq self.clear_log() self.start_app(APP_HOST, APP_PORT, False) @@ -51,11 +50,12 @@ class ProxyThread(threading.Thread): def __init__(self, tmaster): threading.Thread.__init__(self) self.tmaster = tmaster + self.name = "ProxyThread (%s:%s)" % (tmaster.server.address.host, tmaster.server.address.port) controller.should_exit = False @property def port(self): - return self.tmaster.server.port + return self.tmaster.server.address.port @property def log(self): @@ -68,7 +68,7 @@ class ProxyThread(threading.Thread): self.tmaster.shutdown() -class ProxTestBase: +class ProxTestBase(object): # Test Configuration ssl = None ssloptions = False @@ -79,17 +79,16 @@ class ProxTestBase: masterclass = TestMaster @classmethod def setupAll(cls): - cls.tqueue = Queue.Queue() cls.server = libpathod.test.Daemon(ssl=cls.ssl, ssloptions=cls.ssloptions) cls.server2 = libpathod.test.Daemon(ssl=cls.ssl, ssloptions=cls.ssloptions) pconf = cls.get_proxy_config() config = proxy.ProxyConfig( no_upstream_cert = cls.no_upstream_cert, - cacert = tutils.test_data.path("data/serverkey.pem"), + cacert = tutils.test_data.path("data/confdir/mitmproxy-ca.pem"), authenticator = cls.authenticator, **pconf ) - tmaster = cls.masterclass(cls.tqueue, config) + tmaster = cls.masterclass(config) cls.proxy = ProxyThread(tmaster) cls.proxy.start() @@ -134,13 +133,13 @@ class ProxTestBase: class HTTPProxTest(ProxTestBase): def pathoc_raw(self): - return libpathod.pathoc.Pathoc("127.0.0.1", self.proxy.port) + return libpathod.pathoc.Pathoc(("127.0.0.1", self.proxy.port)) def pathoc(self, sni=None): """ Returns a connected Pathoc instance. """ - p = libpathod.pathoc.Pathoc("localhost", self.proxy.port, ssl=self.ssl, sni=sni) + p = libpathod.pathoc.Pathoc(("localhost", self.proxy.port), ssl=self.ssl, sni=sni) if self.ssl: p.connect(("127.0.0.1", self.server.port)) else: @@ -161,10 +160,8 @@ class HTTPProxTest(ProxTestBase): def app(self, page): if self.ssl: - p = libpathod.pathoc.Pathoc("127.0.0.1", self.proxy.port, True) - print "PRE" + p = libpathod.pathoc.Pathoc(("127.0.0.1", self.proxy.port), True) p.connect((APP_HOST, APP_PORT)) - print "POST" return p.request("get:'/%s'"%page) else: p = self.pathoc() @@ -211,7 +208,7 @@ class TransparentProxTest(ProxTestBase): """ Returns a connected Pathoc instance. """ - p = libpathod.pathoc.Pathoc("localhost", self.proxy.port, ssl=self.ssl, sni=sni) + p = libpathod.pathoc.Pathoc(("localhost", self.proxy.port), ssl=self.ssl, sni=sni) p.connect() return p @@ -232,7 +229,7 @@ class ReverseProxTest(ProxTestBase): """ Returns a connected Pathoc instance. """ - p = libpathod.pathoc.Pathoc("localhost", self.proxy.port, ssl=self.ssl, sni=sni) + p = libpathod.pathoc.Pathoc(("localhost", self.proxy.port), ssl=self.ssl, sni=sni) p.connect() return p @@ -249,5 +246,49 @@ class ReverseProxTest(ProxTestBase): return p.request(q) +class ChainProxTest(ProxTestBase): + """ + Chain n instances of mitmproxy in a row - because we can. + """ + n = 2 + chain_config = [lambda: proxy.ProxyConfig( + cacert = tutils.test_data.path("data/confdir/mitmproxy-ca.pem"), + )] * n + @classmethod + def setupAll(cls): + super(ChainProxTest, cls).setupAll() + cls.chain = [] + for i in range(cls.n): + config = cls.chain_config[i]() + config.forward_proxy = ("http", "127.0.0.1", + cls.proxy.port if i == 0 else + cls.chain[-1].port + ) + tmaster = cls.masterclass(config) + cls.chain.append(ProxyThread(tmaster)) + cls.chain[-1].start() + @classmethod + def teardownAll(cls): + super(ChainProxTest, cls).teardownAll() + for p in cls.chain: + p.tmaster.server.shutdown() + def setUp(self): + super(ChainProxTest, self).setUp() + for p in self.chain: + p.tmaster.clear_log() + p.tmaster.state.clear() + + +class HTTPChainProxyTest(ChainProxTest): + def pathoc(self, sni=None): + """ + Returns a connected Pathoc instance. + """ + p = libpathod.pathoc.Pathoc(("localhost", self.chain[-1].port), ssl=self.ssl, sni=sni) + if self.ssl: + p.connect(("127.0.0.1", self.server.port)) + else: + p.connect() + return p diff --git a/test/tutils.py b/test/tutils.py index fb41d77a..75fb7c0b 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -1,12 +1,15 @@ import os, shutil, tempfile from contextlib import contextmanager -from libmproxy import flow, utils, controller -if os.name != "nt": - from libmproxy.console.flowview import FlowView - from libmproxy.console import ConsoleState +from libmproxy import flow, utils, controller, proxy +from libmproxy.protocol import http +import mock_urwid +from libmproxy.console.flowview import FlowView +from libmproxy.console import ConsoleState +from libmproxy.protocol.primitives import Error from netlib import certutils from nose.plugins.skip import SkipTest from mock import Mock +from time import time def _SkipWindows(): raise SkipTest("Skipped on Windows.") @@ -16,40 +19,82 @@ def SkipWindows(fn): else: return fn + +def tclient_conn(): + c = proxy.ClientConnection._from_state(dict( + address=dict(address=("address", 22), use_ipv6=True), + clientcert=None + )) + c.reply = controller.DummyReply() + return c + + +def tserver_conn(): + c = proxy.ServerConnection._from_state(dict( + address=dict(address=("address", 22), use_ipv6=True), + source_address=dict(address=("address", 22), use_ipv6=True), + cert=None + )) + c.reply = controller.DummyReply() + return c + + +def treq_absolute(conn=None, content="content"): + r = treq(conn, content) + r.form_in = r.form_out = "absolute" + r.host = "address" + r.port = 22 + r.scheme = "http" + return r + def treq(conn=None, content="content"): if not conn: - conn = flow.ClientConnect(("address", 22)) - conn.reply = controller.DummyReply() + conn = tclient_conn() + server_conn = tserver_conn() headers = flow.ODictCaseless() headers["header"] = ["qvalue"] - r = flow.Request(conn, (1, 1), "host", 80, "http", "GET", "/path", headers, - content) - r.reply = controller.DummyReply() - return r + f = http.HTTPFlow(conn, server_conn) + f.request = http.HTTPRequest("origin", "GET", None, None, None, "/path", (1, 1), headers, content, + None, None, None) + f.request.reply = controller.DummyReply() + return f.request -def tresp(req=None): + +def tresp(req=None, content="message"): if not req: req = treq() + f = req.flow + headers = flow.ODictCaseless() headers["header_response"] = ["svalue"] - cert = certutils.SSLCert.from_der(file(test_data.path("data/dercert"),"rb").read()) - resp = flow.Response(req, (1, 1), 200, "message", headers, "content_response", cert) - resp.reply = controller.DummyReply() - return resp + cert = certutils.SSLCert.from_der(file(test_data.path("data/dercert"), "rb").read()) + f.server_conn = proxy.ServerConnection._from_state(dict( + address=dict(address=("address", 22), use_ipv6=True), + source_address=None, + cert=cert.to_pem())) + f.response = http.HTTPResponse((1, 1), 200, "OK", headers, content, time(), time()) + f.response.reply = controller.DummyReply() + return f.response + def terr(req=None): if not req: req = treq() - err = flow.Error(req, "error") - err.reply = controller.DummyReply() - return err + f = req.flow + f.error = Error("error") + f.error.reply = controller.DummyReply() + return f.error +def tflow_noreq(): + f = tflow() + f.request = None + return f -def tflow(r=None): - if r == None: - r = treq() - return flow.Flow(r) +def tflow(req=None): + if not req: + req = treq() + return req.flow def tflow_full(): |