def setUp(self):
     """Initialize this testcase."""
     yield super(TunnelIntegrationTestCase, self).setUp()
     self.ws = MockWebServer()
     self.addCleanup(self.ws.stop)
     self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
     self.cookie = FAKE_COOKIE
     self.tunnel_server = tunnel_server.TunnelServer(self.cookie)
     self.addCleanup(self.tunnel_server.shutdown)
    def setUp(self):
        """Initialize this testcase."""
        yield super(RemoteSocketTestCase, self).setUp()
        self.ws = MockWebServer()
        self.addCleanup(self.ws.stop)
        self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE

        self.addCleanup(tunnel_server.QNetworkProxy.setApplicationProxy,
                        tunnel_server.QNetworkProxy.applicationProxy())
        settings = {"http": self.get_proxy_settings()}
        proxy = tunnel_server.build_proxy(settings)
        tunnel_server.QNetworkProxy.setApplicationProxy(proxy)
class TunnelClientTestCase(SquidTestCase):
    """Test the client for the tunnel."""

    timeout = 3

    @defer.inlineCallbacks
    def setUp(self):
        """Initialize this testcase."""
        yield super(TunnelClientTestCase, self).setUp()
        self.ws = MockWebServer()
        self.addCleanup(self.ws.stop)
        self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
        self.dest_ssl_url = (self.ws.get_ssl_iri().encode("utf-8") +
                             SIMPLERESOURCE)
        self.cookie = FAKE_COOKIE
        self.tunnel_server = TunnelServer(self.cookie)
        self.addCleanup(self.tunnel_server.shutdown)

    @defer.inlineCallbacks
    def test_connects_right(self):
        """Uses the CONNECT method on the tunnel."""
        tunnel_client = TunnelClient("0.0.0.0", self.tunnel_server.port,
                                     self.cookie)
        factory = client.HTTPClientFactory(self.dest_url)
        scheme, host, port, path = client._parse(self.dest_url)
        tunnel_client.connectTCP(host, port, factory)
        result = yield factory.deferred
        self.assertEqual(result, SAMPLE_CONTENT)

    @defer.inlineCallbacks
    def test_starts_tls_connection(self):
        """TLS is started after connecting; control passed to the client."""
        tunnel_client = TunnelClient("0.0.0.0", self.tunnel_server.port,
                                     self.cookie)
        factory = client.HTTPClientFactory(self.dest_ssl_url)
        scheme, host, port, path = client._parse(self.dest_ssl_url)
        context_factory = ssl.ClientContextFactory()
        tunnel_client.connectSSL(host, port, factory, context_factory)
        result = yield factory.deferred
        self.assertEqual(result, SAMPLE_CONTENT)
 def setUp(self):
     """Initialize this test instance."""
     yield super(ServerTunnelProtocolTestCase, self).setUp()
     self.ws = MockWebServer()
     self.addCleanup(self.ws.stop)
     self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
     self.transport = FakeTransport()
     self.transport.cookie = FAKE_COOKIE
     self.fake_client = FakeClient()
     self.proto = tunnel_server.ServerTunnelProtocol(
                                             lambda _: self.fake_client)
     self.fake_client.protocol = self.proto
     self.proto.transport = self.transport
     self.cookie_line = "%s: %s" % (tunnel_server.TUNNEL_COOKIE_HEADER,
                                    FAKE_COOKIE)
class TunnelIntegrationTestCase(SquidTestCase):
    """Basic tunnel integration tests."""

    timeout = 3

    @defer.inlineCallbacks
    def setUp(self):
        """Initialize this testcase."""
        yield super(TunnelIntegrationTestCase, self).setUp()
        self.ws = MockWebServer()
        self.addCleanup(self.ws.stop)
        self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
        self.cookie = FAKE_COOKIE
        self.tunnel_server = tunnel_server.TunnelServer(self.cookie)
        self.addCleanup(self.tunnel_server.shutdown)

    def test_init(self):
        """The tunnel is started."""
        self.assertNotEqual(self.tunnel_server.port, 0)

    @defer.inlineCallbacks
    def test_accepts_connections(self):
        """The tunnel accepts incoming connections."""
        ncf = DisconnectingClientFactory()
        reactor.connectTCP("0.0.0.0", self.tunnel_server.port, ncf)
        yield ncf.connected

    @defer.inlineCallbacks
    def test_complete_connection(self):
        """Test from the tunnel server down."""
        url = urlparse(self.dest_url)
        fake_session = FAKE_SESSION_TEMPLATE % (
                                    url.netloc, self.cookie, url.path)
        client = FakeClientFactory(fake_session)
        reactor.connectTCP("0.0.0.0", self.tunnel_server.port, client)
        response = yield client.response
        self.assertIn(SAMPLE_CONTENT, response)
class RemoteSocketTestCase(SquidTestCase):
    """Tests for the client that connects to the other side."""

    timeout = 3
    get_proxy_settings = lambda _: {}

    @defer.inlineCallbacks
    def setUp(self):
        """Initialize this testcase."""
        yield super(RemoteSocketTestCase, self).setUp()
        self.ws = MockWebServer()
        self.addCleanup(self.ws.stop)
        self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE

        self.addCleanup(tunnel_server.QNetworkProxy.setApplicationProxy,
                        tunnel_server.QNetworkProxy.applicationProxy())
        settings = {"http": self.get_proxy_settings()}
        proxy = tunnel_server.build_proxy(settings)
        tunnel_server.QNetworkProxy.setApplicationProxy(proxy)

    def test_invalid_port(self):
        """A request with an invalid port fails with a 400."""
        protocol = tunnel_server.ServerTunnelProtocol(
                                                    tunnel_server.RemoteSocket)
        protocol.transport = FakeTransport()
        protocol.dataReceived("CONNECT 127.0.0.1:wrong_port HTTP/1.0" +
                              CRLF * 2)

        status_line = protocol.transport.getvalue()
        self.assertTrue(status_line.startswith("HTTP/1.0 400 "),
                        "The port must be an integer.")

    @defer.inlineCallbacks
    def test_connection_is_finished_when_stopping(self):
        """The client disconnects when requested."""
        fake_protocol = FakeServerTunnelProtocol()
        client = tunnel_server.RemoteSocket(fake_protocol)
        url = urlparse(self.dest_url)
        yield client.connect(url.netloc)
        yield client.stop()

    @defer.inlineCallbacks
    def test_stop_but_never_connected(self):
        """Stop but it was never connected."""
        fake_protocol = FakeServerTunnelProtocol()
        client = tunnel_server.RemoteSocket(fake_protocol)
        yield client.stop()

    @defer.inlineCallbacks
    def test_client_write(self):
        """Data written to the client is sent to the other side."""
        fake_protocol = FakeServerTunnelProtocol()
        client = tunnel_server.RemoteSocket(fake_protocol)
        self.addCleanup(client.stop)
        url = urlparse(self.dest_url)
        yield client.connect(url.netloc)
        client.write("GET /simpleresource HTTP/1.0" + CRLF * 2)
        yield self.ws.simple_resource.rendered

    @defer.inlineCallbacks
    def test_client_read(self):
        """Data received by the client is written into the transport."""
        fake_protocol = FakeServerTunnelProtocol()
        client = tunnel_server.RemoteSocket(fake_protocol)
        self.addCleanup(client.stop)
        url = urlparse(self.dest_url)
        yield client.connect(url.netloc)
        client.write("GET /simpleresource HTTP/1.0" + CRLF * 2)
        yield self.ws.simple_resource.rendered
        data = yield fake_protocol.response_received
        _headers, content = str(data).split(CRLF * 2, 1)
        self.assertEqual(content, SAMPLE_CONTENT)
class ServerTunnelProtocolTestCase(SquidTestCase):
    """Tests for the ServerTunnelProtocol."""

    @defer.inlineCallbacks
    def setUp(self):
        """Initialize this test instance."""
        yield super(ServerTunnelProtocolTestCase, self).setUp()
        self.ws = MockWebServer()
        self.addCleanup(self.ws.stop)
        self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
        self.transport = FakeTransport()
        self.transport.cookie = FAKE_COOKIE
        self.fake_client = FakeClient()
        self.proto = tunnel_server.ServerTunnelProtocol(
                                                lambda _: self.fake_client)
        self.fake_client.protocol = self.proto
        self.proto.transport = self.transport
        self.cookie_line = "%s: %s" % (tunnel_server.TUNNEL_COOKIE_HEADER,
                                       FAKE_COOKIE)

    def test_broken_request(self):
        """Broken request."""
        self.proto.dataReceived("Broken request." + CRLF)
        self.assertTrue(self.transport.getvalue().startswith("HTTP/1.0 400 "),
                        "A broken request must fail.")

    def test_wrong_method(self):
        """Wrong method."""
        self.proto.dataReceived("GET http://slashdot.org HTTP/1.0" + CRLF)
        self.assertTrue(self.transport.getvalue().startswith("HTTP/1.0 405 "),
                        "Using a wrong method fails.")

    def test_invalid_http_version(self):
        """Invalid HTTP version."""
        self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.1" + CRLF)
        self.assertTrue(self.transport.getvalue().startswith("HTTP/1.0 505 "),
                        "Invalid http version is not allowed.")

    def test_connection_is_established(self):
        """The response code is sent."""
        expected = "HTTP/1.0 200 Proxy connection established" + CRLF
        self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF +
                                self.cookie_line + CRLF * 2)
        self.assertTrue(self.transport.getvalue().startswith(expected),
                        "First line must be the response status")

    def test_connection_fails(self):
        """The connection to the other end fails, and it's handled."""
        error = tunnel_server.ConnectionError()
        self.patch(self.fake_client, "connection_result", defer.fail(error))
        expected = "HTTP/1.0 500 Connection error" + CRLF
        self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF +
                                self.cookie_line + CRLF * 2)
        self.assertTrue(self.transport.getvalue().startswith(expected),
                        "The connection should fail at this point.")

    def test_headers_stored(self):
        """The request headers are stored."""
        expected = [
            ("Header1", "value1"),
            ("Header2", "value2"),
        ]
        self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF +
                                "Header1: value1" + CRLF +
                                "Header2: value2" + CRLF + CRLF)
        self.assertEqual(self.proto.received_headers, expected)

    def test_cookie_header_present(self):
        """The cookie header must be present."""
        self.proto.received_headers = [
            (tunnel_server.TUNNEL_COOKIE_HEADER, FAKE_COOKIE),
        ]
        self.proto.verify_cookie()

    def test_cookie_header_absent(self):
        """The tunnel should refuse connections without the cookie."""
        self.proto.received_headers = []
        exception = self.assertRaises(tunnel_server.ConnectionError,
                                      self.proto.verify_cookie)
        self.assertEqual(exception.code, 418)

    def test_successful_connect(self):
        """A successful connect thru the tunnel."""
        url = urlparse(self.dest_url)
        data = FAKE_SESSION_TEMPLATE % (url.netloc, self.transport.cookie,
                                        url.path)
        self.proto.dataReceived(data)
        lines = self.transport.getvalue().split(CRLF)
        self.assertEqual(lines[-1], SAMPLE_CONTENT)

    def test_header_split(self):
        """Test a header with many colons."""
        self.proto.header_line("key: host:port")
        self.assertIn("key", dict(self.proto.received_headers))

    @defer.inlineCallbacks
    def test_keyring_credentials_are_retried(self):
        """Wrong credentials are retried with values from keyring."""
        self.fake_client.check_credentials = True
        self.patch(self.proto, "verify_cookie", lambda: None)
        self.patch(self.proto, "error_response",
                   lambda code, desc: self.fail(desc))
        self.proto.proxy_domain = "xxx"
        self.patch(tunnel_server.Keyring, "get_credentials",
                   lambda _, domain: defer.succeed(FAKE_CREDS))
        yield self.proto.headers_done()

    def test_creds_are_not_logged(self):
        """The proxy credentials are not logged."""
        log = []
        self.patch(tunnel_server.logger, "info",
                   lambda text, *args: log.append(text % args))
        proxy = tunnel_server.build_proxy(FAKE_AUTH_SETTINGS)
        authenticator = QAuthenticator()
        username = FAKE_AUTH_SETTINGS["http"]["username"]
        password = FAKE_AUTH_SETTINGS["http"]["password"]
        self.proto.proxy_credentials = {
            "username": username,
            "password": password,
        }
        self.proto.proxy_domain = proxy.hostName()

        self.proto.proxy_auth_required(proxy, authenticator)

        for line in log:
            self.assertNotIn(username, line)
            self.assertNotIn(password, line)