import unittest
import io

class TestThreadedTaskDispatcher(unittest.TestCase):

    def _makeOne(self):
        from waitress.task import ThreadedTaskDispatcher
        return ThreadedTaskDispatcher()

    def test_handler_thread_task_raises(self):
        inst = self._makeOne()
        inst.threads.add(0)
        inst.logger = DummyLogger()
        class BadDummyTask(DummyTask):
            def service(self):
                super(BadDummyTask, self).service()
                inst.stop_count += 1
                raise Exception
        task = BadDummyTask()
        inst.logger = DummyLogger()
        inst.queue.append(task)
        inst.active_count += 1
        inst.handler_thread(0)
        self.assertEqual(inst.stop_count, 0)
        self.assertEqual(inst.active_count, 0)
        self.assertEqual(inst.threads, set())
        self.assertEqual(len(inst.logger.logged), 1)

    def test_set_thread_count_increase(self):
        inst = self._makeOne()
        L = []
        inst.start_new_thread = lambda *x: L.append(x)
        inst.set_thread_count(1)
        self.assertEqual(L, [(inst.handler_thread, (0,))])

    def test_set_thread_count_increase_with_existing(self):
        inst = self._makeOne()
        L = []
        inst.threads = {0}
        inst.start_new_thread = lambda *x: L.append(x)
        inst.set_thread_count(2)
        self.assertEqual(L, [(inst.handler_thread, (1,))])

    def test_set_thread_count_decrease(self):
        inst = self._makeOne()
        inst.threads = {0, 1}
        inst.set_thread_count(1)
        self.assertEqual(inst.stop_count, 1)

    def test_set_thread_count_same(self):
        inst = self._makeOne()
        L = []
        inst.start_new_thread = lambda *x: L.append(x)
        inst.threads = {0}
        inst.set_thread_count(1)
        self.assertEqual(L, [])

    def test_add_task_with_idle_threads(self):
        task = DummyTask()
        inst = self._makeOne()
        inst.threads.add(0)
        inst.queue_logger = DummyLogger()
        inst.add_task(task)
        self.assertEqual(len(inst.queue), 1)
        self.assertEqual(len(inst.queue_logger.logged), 0)

    def test_add_task_with_all_busy_threads(self):
        task = DummyTask()
        inst = self._makeOne()
        inst.queue_logger = DummyLogger()
        inst.add_task(task)
        self.assertEqual(len(inst.queue_logger.logged), 1)
        inst.add_task(task)
        self.assertEqual(len(inst.queue_logger.logged), 2)

    def test_shutdown_one_thread(self):
        inst = self._makeOne()
        inst.threads.add(0)
        inst.logger = DummyLogger()
        task = DummyTask()
        inst.queue.append(task)
        self.assertEqual(inst.shutdown(timeout=.01), True)
        self.assertEqual(inst.logger.logged, [
            '1 thread(s) still running',
            'Canceling 1 pending task(s)',
        ])
        self.assertEqual(task.cancelled, True)

    def test_shutdown_no_threads(self):
        inst = self._makeOne()
        self.assertEqual(inst.shutdown(timeout=.01), True)

    def test_shutdown_no_cancel_pending(self):
        inst = self._makeOne()
        self.assertEqual(inst.shutdown(cancel_pending=False, timeout=.01),
                         False)

class TestTask(unittest.TestCase):

    def _makeOne(self, channel=None, request=None):
        if channel is None:
            channel = DummyChannel()
        if request is None:
            request = DummyParser()
        from waitress.task import Task
        return Task(channel, request)

    def test_ctor_version_not_in_known(self):
        request = DummyParser()
        request.version = '8.4'
        inst = self._makeOne(request=request)
        self.assertEqual(inst.version, '1.0')

    def test_build_response_header_bad_http_version(self):
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.version = '8.4'
        self.assertRaises(AssertionError, inst.build_response_header)

    def test_build_response_header_v10_keepalive_no_content_length(self):
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.request.headers['CONNECTION'] = 'keep-alive'
        inst.version = '1.0'
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 4)
        self.assertEqual(lines[0], b'HTTP/1.0 200 OK')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertTrue(lines[2].startswith(b'Date:'))
        self.assertEqual(lines[3], b'Server: waitress')
        self.assertEqual(inst.close_on_finish, True)
        self.assertTrue(('Connection', 'close') in inst.response_headers)

    def test_build_response_header_v10_keepalive_with_content_length(self):
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.request.headers['CONNECTION'] = 'keep-alive'
        inst.response_headers = [('Content-Length', '10')]
        inst.version = '1.0'
        inst.content_length = 0
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 5)
        self.assertEqual(lines[0], b'HTTP/1.0 200 OK')
        self.assertEqual(lines[1], b'Connection: Keep-Alive')
        self.assertEqual(lines[2], b'Content-Length: 10')
        self.assertTrue(lines[3].startswith(b'Date:'))
        self.assertEqual(lines[4], b'Server: waitress')
        self.assertEqual(inst.close_on_finish, False)

    def test_build_response_header_v11_connection_closed_by_client(self):
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.version = '1.1'
        inst.request.headers['CONNECTION'] = 'close'
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 5)
        self.assertEqual(lines[0], b'HTTP/1.1 200 OK')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertTrue(lines[2].startswith(b'Date:'))
        self.assertEqual(lines[3], b'Server: waitress')
        self.assertEqual(lines[4], b'Transfer-Encoding: chunked')
        self.assertTrue(('Connection', 'close') in inst.response_headers)
        self.assertEqual(inst.close_on_finish, True)

    def test_build_response_header_v11_connection_keepalive_by_client(self):
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.request.headers['CONNECTION'] = 'keep-alive'
        inst.version = '1.1'
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 5)
        self.assertEqual(lines[0], b'HTTP/1.1 200 OK')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertTrue(lines[2].startswith(b'Date:'))
        self.assertEqual(lines[3], b'Server: waitress')
        self.assertEqual(lines[4], b'Transfer-Encoding: chunked')
        self.assertTrue(('Connection', 'close') in inst.response_headers)
        self.assertEqual(inst.close_on_finish, True)

    def test_build_response_header_v11_200_no_content_length(self):
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.version = '1.1'
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 5)
        self.assertEqual(lines[0], b'HTTP/1.1 200 OK')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertTrue(lines[2].startswith(b'Date:'))
        self.assertEqual(lines[3], b'Server: waitress')
        self.assertEqual(lines[4], b'Transfer-Encoding: chunked')
        self.assertEqual(inst.close_on_finish, True)
        self.assertTrue(('Connection', 'close') in inst.response_headers)

    def test_build_response_header_v11_204_no_content_length_or_transfer_encoding(self):
        # RFC 7230: MUST NOT send Transfer-Encoding or Content-Length
        # for any response with a status code of 1xx or 204.
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.version = '1.1'
        inst.status = '204 No Content'
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 4)
        self.assertEqual(lines[0], b'HTTP/1.1 204 No Content')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertTrue(lines[2].startswith(b'Date:'))
        self.assertEqual(lines[3], b'Server: waitress')
        self.assertEqual(inst.close_on_finish, True)
        self.assertTrue(('Connection', 'close') in inst.response_headers)

    def test_build_response_header_v11_1xx_no_content_length_or_transfer_encoding(self):
        # RFC 7230: MUST NOT send Transfer-Encoding or Content-Length
        # for any response with a status code of 1xx or 204.
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.version = '1.1'
        inst.status = '100 Continue'
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 4)
        self.assertEqual(lines[0], b'HTTP/1.1 100 Continue')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertTrue(lines[2].startswith(b'Date:'))
        self.assertEqual(lines[3], b'Server: waitress')
        self.assertEqual(inst.close_on_finish, True)
        self.assertTrue(('Connection', 'close') in inst.response_headers)

    def test_build_response_header_v11_304_no_content_length_or_transfer_encoding(self):
        # RFC 7230: MUST NOT send Transfer-Encoding or Content-Length
        # for any response with a status code of 1xx, 204 or 304.
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.version = '1.1'
        inst.status = '304 Not Modified'
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 4)
        self.assertEqual(lines[0], b'HTTP/1.1 304 Not Modified')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertTrue(lines[2].startswith(b'Date:'))
        self.assertEqual(lines[3], b'Server: waitress')
        self.assertEqual(inst.close_on_finish, True)
        self.assertTrue(('Connection', 'close') in inst.response_headers)

    def test_build_response_header_via_added(self):
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.version = '1.0'
        inst.response_headers = [('Server', 'abc')]
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 5)
        self.assertEqual(lines[0], b'HTTP/1.0 200 OK')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertTrue(lines[2].startswith(b'Date:'))
        self.assertEqual(lines[3], b'Server: abc')
        self.assertEqual(lines[4], b'Via: waitress')

    def test_build_response_header_date_exists(self):
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.version = '1.0'
        inst.response_headers = [('Date', 'date')]
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 4)
        self.assertEqual(lines[0], b'HTTP/1.0 200 OK')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertTrue(lines[2].startswith(b'Date:'))
        self.assertEqual(lines[3], b'Server: waitress')

    def test_build_response_header_preexisting_content_length(self):
        inst = self._makeOne()
        inst.request = DummyParser()
        inst.version = '1.1'
        inst.content_length = 100
        result = inst.build_response_header()
        lines = filter_lines(result)
        self.assertEqual(len(lines), 4)
        self.assertEqual(lines[0], b'HTTP/1.1 200 OK')
        self.assertEqual(lines[1], b'Content-Length: 100')
        self.assertTrue(lines[2].startswith(b'Date:'))
        self.assertEqual(lines[3], b'Server: waitress')

    def test_remove_content_length_header(self):
        inst = self._makeOne()
        inst.response_headers = [('Content-Length', '70')]
        inst.remove_content_length_header()
        self.assertEqual(inst.response_headers, [])

    def test_remove_content_length_header_with_other(self):
        inst = self._makeOne()
        inst.response_headers = [('Content-Length', '70'), ('Content-Type', 'text/html')]
        inst.remove_content_length_header()
        self.assertEqual(inst.response_headers, [('Content-Type', 'text/html')])

    def test_start(self):
        inst = self._makeOne()
        inst.start()
        self.assertTrue(inst.start_time)

    def test_finish_didnt_write_header(self):
        inst = self._makeOne()
        inst.wrote_header = False
        inst.complete = True
        inst.finish()
        self.assertTrue(inst.channel.written)

    def test_finish_wrote_header(self):
        inst = self._makeOne()
        inst.wrote_header = True
        inst.finish()
        self.assertFalse(inst.channel.written)

    def test_finish_chunked_response(self):
        inst = self._makeOne()
        inst.wrote_header = True
        inst.chunked_response = True
        inst.finish()
        self.assertEqual(inst.channel.written, b'0\r\n\r\n')

    def test_write_wrote_header(self):
        inst = self._makeOne()
        inst.wrote_header = True
        inst.complete = True
        inst.content_length = 3
        inst.write(b'abc')
        self.assertEqual(inst.channel.written, b'abc')

    def test_write_header_not_written(self):
        inst = self._makeOne()
        inst.wrote_header = False
        inst.complete = True
        inst.write(b'abc')
        self.assertTrue(inst.channel.written)
        self.assertEqual(inst.wrote_header, True)

    def test_write_start_response_uncalled(self):
        inst = self._makeOne()
        self.assertRaises(RuntimeError, inst.write, b'')

    def test_write_chunked_response(self):
        inst = self._makeOne()
        inst.wrote_header = True
        inst.chunked_response = True
        inst.complete = True
        inst.write(b'abc')
        self.assertEqual(inst.channel.written, b'3\r\nabc\r\n')

    def test_write_preexisting_content_length(self):
        inst = self._makeOne()
        inst.wrote_header = True
        inst.complete = True
        inst.content_length = 1
        inst.logger = DummyLogger()
        inst.write(b'abc')
        self.assertTrue(inst.channel.written)
        self.assertEqual(inst.logged_write_excess, True)
        self.assertEqual(len(inst.logger.logged), 1)

class TestWSGITask(unittest.TestCase):

    def _makeOne(self, channel=None, request=None):
        if channel is None:
            channel = DummyChannel()
        if request is None:
            request = DummyParser()
        from waitress.task import WSGITask
        return WSGITask(channel, request)

    def test_service(self):
        inst = self._makeOne()
        def execute():
            inst.executed = True
        inst.execute = execute
        inst.complete = True
        inst.service()
        self.assertTrue(inst.start_time)
        self.assertTrue(inst.close_on_finish)
        self.assertTrue(inst.channel.written)
        self.assertEqual(inst.executed, True)

    def test_service_server_raises_socket_error(self):
        import socket
        inst = self._makeOne()
        def execute():
            raise socket.error
        inst.execute = execute
        self.assertRaises(socket.error, inst.service)
        self.assertTrue(inst.start_time)
        self.assertTrue(inst.close_on_finish)
        self.assertFalse(inst.channel.written)

    def test_execute_app_calls_start_response_twice_wo_exc_info(self):
        def app(environ, start_response):
            start_response('200 OK', [])
            start_response('200 OK', [])
        inst = self._makeOne()
        inst.channel.server.application = app
        self.assertRaises(AssertionError, inst.execute)

    def test_execute_app_calls_start_response_w_exc_info_complete(self):
        def app(environ, start_response):
            start_response('200 OK', [], [ValueError, ValueError(), None])
            return [b'a']
        inst = self._makeOne()
        inst.complete = True
        inst.channel.server.application = app
        inst.execute()
        self.assertTrue(inst.complete)
        self.assertEqual(inst.status, '200 OK')
        self.assertTrue(inst.channel.written)

    def test_execute_app_calls_start_response_w_excinf_headers_unwritten(self):
        def app(environ, start_response):
            start_response('200 OK', [], [ValueError, None, None])
            return [b'a']
        inst = self._makeOne()
        inst.wrote_header = False
        inst.channel.server.application = app
        inst.response_headers = [('a', 'b')]
        inst.execute()
        self.assertTrue(inst.complete)
        self.assertEqual(inst.status, '200 OK')
        self.assertTrue(inst.channel.written)
        self.assertFalse(('a','b') in inst.response_headers)

    def test_execute_app_calls_start_response_w_excinf_headers_written(self):
        def app(environ, start_response):
            start_response('200 OK', [], [ValueError, ValueError(), None])
        inst = self._makeOne()
        inst.complete = True
        inst.wrote_header = True
        inst.channel.server.application = app
        self.assertRaises(ValueError, inst.execute)

    def test_execute_bad_header_key(self):
        def app(environ, start_response):
            start_response('200 OK', [(None, 'a')])
        inst = self._makeOne()
        inst.channel.server.application = app
        self.assertRaises(AssertionError, inst.execute)

    def test_execute_bad_header_value(self):
        def app(environ, start_response):
            start_response('200 OK', [('a', None)])
        inst = self._makeOne()
        inst.channel.server.application = app
        self.assertRaises(AssertionError, inst.execute)

    def test_execute_hopbyhop_header(self):
        def app(environ, start_response):
            start_response('200 OK', [('Connection', 'close')])
        inst = self._makeOne()
        inst.channel.server.application = app
        self.assertRaises(AssertionError, inst.execute)

    def test_execute_bad_header_value_control_characters(self):
        def app(environ, start_response):
            start_response('200 OK', [('a', '\n')])
        inst = self._makeOne()
        inst.channel.server.application = app
        self.assertRaises(ValueError, inst.execute)

    def test_execute_bad_header_name_control_characters(self):
        def app(environ, start_response):
            start_response('200 OK', [('a\r', 'value')])
        inst = self._makeOne()
        inst.channel.server.application = app
        self.assertRaises(ValueError, inst.execute)

    def test_execute_bad_status_control_characters(self):
        def app(environ, start_response):
            start_response('200 OK\r', [])
        inst = self._makeOne()
        inst.channel.server.application = app
        self.assertRaises(ValueError, inst.execute)

    def test_preserve_header_value_order(self):
        def app(environ, start_response):
            write = start_response('200 OK', [('C', 'b'), ('A', 'b'), ('A', 'a')])
            write(b'abc')
            return []
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.execute()
        self.assertTrue(b'A: b\r\nA: a\r\nC: b\r\n' in inst.channel.written)

    def test_execute_bad_status_value(self):
        def app(environ, start_response):
            start_response(None, [])
        inst = self._makeOne()
        inst.channel.server.application = app
        self.assertRaises(AssertionError, inst.execute)

    def test_execute_with_content_length_header(self):
        def app(environ, start_response):
            start_response('200 OK', [('Content-Length', '1')])
            return [b'a']
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.execute()
        self.assertEqual(inst.content_length, 1)

    def test_execute_app_calls_write(self):
        def app(environ, start_response):
            write = start_response('200 OK', [('Content-Length', '3')])
            write(b'abc')
            return []
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.execute()
        self.assertEqual(inst.channel.written[-3:], b'abc')

    def test_execute_app_returns_len1_chunk_without_cl(self):
        def app(environ, start_response):
            start_response('200 OK', [])
            return [b'abc']
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.execute()
        self.assertEqual(inst.content_length, 3)

    def test_execute_app_returns_empty_chunk_as_first(self):
        def app(environ, start_response):
            start_response('200 OK', [])
            return ['', b'abc']
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.execute()
        self.assertEqual(inst.content_length, None)

    def test_execute_app_returns_too_many_bytes(self):
        def app(environ, start_response):
            start_response('200 OK', [('Content-Length', '1')])
            return [b'abc']
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.logger = DummyLogger()
        inst.execute()
        self.assertEqual(inst.close_on_finish, True)
        self.assertEqual(len(inst.logger.logged), 1)

    def test_execute_app_returns_too_few_bytes(self):
        def app(environ, start_response):
            start_response('200 OK', [('Content-Length', '3')])
            return [b'a']
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.logger = DummyLogger()
        inst.execute()
        self.assertEqual(inst.close_on_finish, True)
        self.assertEqual(len(inst.logger.logged), 1)

    def test_execute_app_do_not_warn_on_head(self):
        def app(environ, start_response):
            start_response('200 OK', [('Content-Length', '3')])
            return [b'']
        inst = self._makeOne()
        inst.request.command = 'HEAD'
        inst.channel.server.application = app
        inst.logger = DummyLogger()
        inst.execute()
        self.assertEqual(inst.close_on_finish, True)
        self.assertEqual(len(inst.logger.logged), 0)

    def test_execute_app_without_body_204_logged(self):
        def app(environ, start_response):
            start_response('204 No Content', [('Content-Length', '3')])
            return [b'abc']
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.logger = DummyLogger()
        inst.execute()
        self.assertEqual(inst.close_on_finish, True)
        self.assertNotIn(b'abc', inst.channel.written)
        self.assertNotIn(b'Content-Length', inst.channel.written)
        self.assertNotIn(b'Transfer-Encoding', inst.channel.written)
        self.assertEqual(len(inst.logger.logged), 1)

    def test_execute_app_without_body_304_logged(self):
        def app(environ, start_response):
            start_response('304 Not Modified', [('Content-Length', '3')])
            return [b'abc']
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.logger = DummyLogger()
        inst.execute()
        self.assertEqual(inst.close_on_finish, True)
        self.assertNotIn(b'abc', inst.channel.written)
        self.assertNotIn(b'Content-Length', inst.channel.written)
        self.assertNotIn(b'Transfer-Encoding', inst.channel.written)
        self.assertEqual(len(inst.logger.logged), 1)

    def test_execute_app_returns_closeable(self):
        class closeable(list):
            def close(self):
                self.closed = True
        foo = closeable([b'abc'])
        def app(environ, start_response):
            start_response('200 OK', [('Content-Length', '3')])
            return foo
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.execute()
        self.assertEqual(foo.closed, True)

    def test_execute_app_returns_filewrapper_prepare_returns_True(self):
        from waitress.buffers import ReadOnlyFileBasedBuffer
        f = io.BytesIO(b'abc')
        app_iter = ReadOnlyFileBasedBuffer(f, 8192)
        def app(environ, start_response):
            start_response('200 OK', [('Content-Length', '3')])
            return app_iter
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.execute()
        self.assertTrue(inst.channel.written) # header
        self.assertEqual(inst.channel.otherdata, [app_iter])

    def test_execute_app_returns_filewrapper_prepare_returns_True_nocl(self):
        from waitress.buffers import ReadOnlyFileBasedBuffer
        f = io.BytesIO(b'abc')
        app_iter = ReadOnlyFileBasedBuffer(f, 8192)
        def app(environ, start_response):
            start_response('200 OK', [])
            return app_iter
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.execute()
        self.assertTrue(inst.channel.written) # header
        self.assertEqual(inst.channel.otherdata, [app_iter])
        self.assertEqual(inst.content_length, 3)

    def test_execute_app_returns_filewrapper_prepare_returns_True_badcl(self):
        from waitress.buffers import ReadOnlyFileBasedBuffer
        f = io.BytesIO(b'abc')
        app_iter = ReadOnlyFileBasedBuffer(f, 8192)
        def app(environ, start_response):
            start_response('200 OK', [])
            return app_iter
        inst = self._makeOne()
        inst.channel.server.application = app
        inst.content_length = 10
        inst.response_headers = [('Content-Length', '10')]
        inst.execute()
        self.assertTrue(inst.channel.written) # header
        self.assertEqual(inst.channel.otherdata, [app_iter])
        self.assertEqual(inst.content_length, 3)
        self.assertEqual(dict(inst.response_headers)['Content-Length'], '3')

    def test_get_environment_already_cached(self):
        inst = self._makeOne()
        inst.environ = object()
        self.assertEqual(inst.get_environment(), inst.environ)

    def test_get_environment_path_startswith_more_than_one_slash(self):
        inst = self._makeOne()
        request = DummyParser()
        request.path = '///abc'
        inst.request = request
        environ = inst.get_environment()
        self.assertEqual(environ['PATH_INFO'], '/abc')

    def test_get_environment_path_empty(self):
        inst = self._makeOne()
        request = DummyParser()
        request.path = ''
        inst.request = request
        environ = inst.get_environment()
        self.assertEqual(environ['PATH_INFO'], '')

    def test_get_environment_no_query(self):
        inst = self._makeOne()
        request = DummyParser()
        inst.request = request
        environ = inst.get_environment()
        self.assertEqual(environ['QUERY_STRING'], '')

    def test_get_environment_with_query(self):
        inst = self._makeOne()
        request = DummyParser()
        request.query = 'abc'
        inst.request = request
        environ = inst.get_environment()
        self.assertEqual(environ['QUERY_STRING'], 'abc')

    def test_get_environ_with_url_prefix_miss(self):
        inst = self._makeOne()
        inst.channel.server.adj.url_prefix = '/foo'
        request = DummyParser()
        request.path = '/bar'
        inst.request = request
        environ = inst.get_environment()
        self.assertEqual(environ['PATH_INFO'], '/bar')
        self.assertEqual(environ['SCRIPT_NAME'], '/foo')

    def test_get_environ_with_url_prefix_hit(self):
        inst = self._makeOne()
        inst.channel.server.adj.url_prefix = '/foo'
        request = DummyParser()
        request.path = '/foo/fuz'
        inst.request = request
        environ = inst.get_environment()
        self.assertEqual(environ['PATH_INFO'], '/fuz')
        self.assertEqual(environ['SCRIPT_NAME'], '/foo')

    def test_get_environ_with_url_prefix_empty_path(self):
        inst = self._makeOne()
        inst.channel.server.adj.url_prefix = '/foo'
        request = DummyParser()
        request.path = '/foo'
        inst.request = request
        environ = inst.get_environment()
        self.assertEqual(environ['PATH_INFO'], '')
        self.assertEqual(environ['SCRIPT_NAME'], '/foo')

    def test_get_environment_values(self):
        import sys
        inst = self._makeOne()
        request = DummyParser()
        request.headers = {
            'CONTENT_TYPE': 'abc',
            'CONTENT_LENGTH': '10',
            'X_FOO': 'BAR',
            'CONNECTION': 'close',
        }
        request.query = 'abc'
        inst.request = request
        environ = inst.get_environment()

        # nail the keys of environ
        self.assertEqual(sorted(environ.keys()), [
            'CONTENT_LENGTH', 'CONTENT_TYPE', 'HTTP_CONNECTION', 'HTTP_X_FOO',
            'PATH_INFO', 'QUERY_STRING', 'REMOTE_ADDR', 'REMOTE_HOST',
            'REMOTE_PORT', 'REQUEST_METHOD', 'SCRIPT_NAME', 'SERVER_NAME',
            'SERVER_PORT', 'SERVER_PROTOCOL', 'SERVER_SOFTWARE', 'wsgi.errors',
            'wsgi.file_wrapper', 'wsgi.input', 'wsgi.input_terminated',
            'wsgi.multiprocess', 'wsgi.multithread', 'wsgi.run_once',
            'wsgi.url_scheme', 'wsgi.version'
        ])

        self.assertEqual(environ['REQUEST_METHOD'], 'GET')
        self.assertEqual(environ['SERVER_PORT'], '80')
        self.assertEqual(environ['SERVER_NAME'], 'localhost')
        self.assertEqual(environ['SERVER_SOFTWARE'], 'waitress')
        self.assertEqual(environ['SERVER_PROTOCOL'], 'HTTP/1.0')
        self.assertEqual(environ['SCRIPT_NAME'], '')
        self.assertEqual(environ['HTTP_CONNECTION'], 'close')
        self.assertEqual(environ['PATH_INFO'], '/')
        self.assertEqual(environ['QUERY_STRING'], 'abc')
        self.assertEqual(environ['REMOTE_ADDR'], '127.0.0.1')
        self.assertEqual(environ['REMOTE_HOST'], '127.0.0.1')
        self.assertEqual(environ['REMOTE_PORT'], '39830')
        self.assertEqual(environ['CONTENT_TYPE'], 'abc')
        self.assertEqual(environ['CONTENT_LENGTH'], '10')
        self.assertEqual(environ['HTTP_X_FOO'], 'BAR')
        self.assertEqual(environ['wsgi.version'], (1, 0))
        self.assertEqual(environ['wsgi.url_scheme'], 'http')
        self.assertEqual(environ['wsgi.errors'], sys.stderr)
        self.assertEqual(environ['wsgi.multithread'], True)
        self.assertEqual(environ['wsgi.multiprocess'], False)
        self.assertEqual(environ['wsgi.run_once'], False)
        self.assertEqual(environ['wsgi.input'], 'stream')
        self.assertEqual(environ['wsgi.input_terminated'], True)
        self.assertEqual(inst.environ, environ)

    def test_get_environment_values_w_scheme_override_untrusted(self):
        inst = self._makeOne()
        request = DummyParser()
        request.headers = {
            'CONTENT_TYPE': 'abc',
            'CONTENT_LENGTH': '10',
            'X_FOO': 'BAR',
            'X_FORWARDED_PROTO': 'https',
            'CONNECTION': 'close',
        }
        request.query = 'abc'
        inst.request = request
        environ = inst.get_environment()
        self.assertEqual(environ['wsgi.url_scheme'], 'http')

    def test_get_environment_values_w_scheme_override_trusted(self):
        import sys
        inst = self._makeOne()
        inst.channel.addr = ['192.168.1.1', 8080]
        inst.channel.server.adj.trusted_proxy = '192.168.1.1'
        inst.channel.server.adj.trusted_proxy_headers = {'x-forwarded-proto'}
        request = DummyParser()
        request.headers = {
            'CONTENT_TYPE': 'abc',
            'CONTENT_LENGTH': '10',
            'X_FOO': 'BAR',
            'X_FORWARDED_PROTO': 'https',
            'CONNECTION': 'close',
        }
        request.query = 'abc'
        inst.request = request
        environ = inst.get_environment()

        # nail the keys of environ
        self.assertEqual(sorted(environ.keys()), [
            'CONTENT_LENGTH', 'CONTENT_TYPE', 'HTTP_CONNECTION', 'HTTP_X_FOO',
            'HTTP_X_FORWARDED_PROTO', 'PATH_INFO', 'QUERY_STRING',
            'REMOTE_ADDR', 'REMOTE_HOST', 'REQUEST_METHOD', 'SCRIPT_NAME',
            'SERVER_NAME', 'SERVER_PORT', 'SERVER_PROTOCOL', 'SERVER_SOFTWARE',
            'wsgi.errors', 'wsgi.file_wrapper', 'wsgi.input',
            'wsgi.input_terminated', 'wsgi.multiprocess', 'wsgi.multithread',
            'wsgi.run_once', 'wsgi.url_scheme', 'wsgi.version'
            ])

        self.assertEqual(environ['REQUEST_METHOD'], 'GET')
        self.assertEqual(environ['SERVER_PORT'], '443')
        self.assertEqual(environ['SERVER_NAME'], 'localhost')
        self.assertEqual(environ['SERVER_SOFTWARE'], 'waitress')
        self.assertEqual(environ['SERVER_PROTOCOL'], 'HTTP/1.0')
        self.assertEqual(environ['SCRIPT_NAME'], '')
        self.assertEqual(environ['HTTP_CONNECTION'], 'close')
        self.assertEqual(environ['PATH_INFO'], '/')
        self.assertEqual(environ['QUERY_STRING'], 'abc')
        self.assertEqual(environ['REMOTE_ADDR'], '192.168.1.1')
        self.assertEqual(environ['CONTENT_TYPE'], 'abc')
        self.assertEqual(environ['CONTENT_LENGTH'], '10')
        self.assertEqual(environ['HTTP_X_FOO'], 'BAR')
        self.assertEqual(environ['wsgi.version'], (1, 0))
        self.assertEqual(environ['wsgi.url_scheme'], 'https')
        self.assertEqual(environ['wsgi.errors'], sys.stderr)
        self.assertEqual(environ['wsgi.multithread'], True)
        self.assertEqual(environ['wsgi.multiprocess'], False)
        self.assertEqual(environ['wsgi.run_once'], False)
        self.assertEqual(environ['wsgi.input'], 'stream')
        self.assertEqual(environ['wsgi.input_terminated'], True)
        self.assertEqual(inst.environ, environ)

    def test_get_environment_values_w_bogus_scheme_override(self):
        inst = self._makeOne()
        inst.channel.addr = ['192.168.1.1', 80]
        inst.channel.server.adj.trusted_proxy = '192.168.1.1'
        inst.channel.server.adj.trusted_proxy_headers = {'x-forwarded-proto'}
        request = DummyParser()
        request.headers = {
            'CONTENT_TYPE': 'abc',
            'CONTENT_LENGTH': '10',
            'X_FOO': 'BAR',
            'X_FORWARDED_PROTO': 'http://p02n3e.com?url=http',
            'CONNECTION': 'close',
        }
        request.query = 'abc'
        inst.request = request
        self.assertRaises(ValueError, inst.get_environment)

    def test_get_environment_warning_other_proxy_headers(self):
        inst = self._makeOne()
        inst.logger = DummyLogger()

        inst.request.headers = {
            'X_FORWARDED_FOR': '[2001:db8::1]',
            'FORWARDED': 'For=198.51.100.2;host=example.com:8080;proto=https'
        }
        inst.channel.addr = ['192.168.1.1', 80]
        inst.channel.server.adj.trusted_proxy = '192.168.1.1'
        inst.channel.server.adj.trusted_proxy_count = 1
        inst.channel.server.adj.trusted_proxy_headers = {'forwarded'}
        inst.channel.server.adj.log_untrusted_proxy_headers = True
        environ = inst.get_environment()

        self.assertEqual(len(inst.logger.logged), 1)
        self.assertNotIn('HTTP_X_FORWARDED_FOR', environ)

        self.assertEqual(environ['REMOTE_ADDR'], '198.51.100.2')
        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com:8080')
        self.assertEqual(environ['SERVER_PORT'], '8080')
        self.assertEqual(environ['wsgi.url_scheme'], 'https')

    def test_get_environment_contains_all_headers_including_untrusted(self):
        inst = self._makeOne()
        inst.logger = DummyLogger()

        inst.request.headers = {
            'X_FORWARDED_FOR': '198.51.100.2',
            'X_FORWARDED_BY': 'Waitress',
            'X_FORWARDED_PROTO': 'https',
            'X_FORWARDED_HOST': 'example.org',
        }
        headers_orig = inst.request.headers.copy()
        inst.channel.addr = ['192.168.1.1', 80]
        inst.channel.server.adj.trusted_proxy = '192.168.1.1'
        inst.channel.server.adj.trusted_proxy_count = 1
        inst.channel.server.adj.trusted_proxy_headers = {'x-forwarded-by'}
        inst.channel.server.adj.clear_untrusted_proxy_headers = False
        environ = inst.get_environment()

        for k, expected in headers_orig.items():
            result = environ['HTTP_%s' % k]
            self.assertEqual(result, expected)

    def test_get_environment_contains_only_trusted_headers(self):
        inst = self._makeOne()
        inst.logger = DummyLogger()

        inst.request.headers = {
            'X_FORWARDED_FOR': '198.51.100.2',
            'X_FORWARDED_BY': 'Waitress',
            'X_FORWARDED_PROTO': 'https',
            'X_FORWARDED_HOST': 'example.org',
        }
        inst.channel.addr = ['192.168.1.1', 80]
        inst.channel.server.adj.trusted_proxy = '192.168.1.1'
        inst.channel.server.adj.trusted_proxy_count = 1
        inst.channel.server.adj.trusted_proxy_headers = {'x-forwarded-by'}
        inst.channel.server.adj.clear_untrusted_proxy_headers = True
        environ = inst.get_environment()

        self.assertEqual(environ['HTTP_X_FORWARDED_BY'], 'Waitress')
        self.assertNotIn('HTTP_X_FORWARDED_FOR', environ)
        self.assertNotIn('HTTP_X_FORWARDED_PROTO', environ)
        self.assertNotIn('HTTP_X_FORWARDED_HOST', environ)

    def test_get_environment_clears_headers_if_untrusted_proxy(self):
        inst = self._makeOne()
        inst.logger = DummyLogger()

        inst.request.headers = {
            'X_FORWARDED_FOR': '198.51.100.2',
            'X_FORWARDED_BY': 'Waitress',
            'X_FORWARDED_PROTO': 'https',
            'X_FORWARDED_HOST': 'example.org',
        }
        inst.channel.addr = ['192.168.1.255', 80]
        inst.channel.server.adj.trusted_proxy = '192.168.1.1'
        inst.channel.server.adj.trusted_proxy_count = 1
        inst.channel.server.adj.trusted_proxy_headers = {'x-forwarded-by'}
        inst.channel.server.adj.clear_untrusted_proxy_headers = True
        environ = inst.get_environment()

        self.assertNotIn('HTTP_X_FORWARDED_BY', environ)
        self.assertNotIn('HTTP_X_FORWARDED_FOR', environ)
        self.assertNotIn('HTTP_X_FORWARDED_PROTO', environ)
        self.assertNotIn('HTTP_X_FORWARDED_HOST', environ)

    def test_parse_proxy_headers_forwarded_for(self):
        inst = self._makeOne()

        headers = {
            'X_FORWARDED_FOR': '192.0.2.1'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
            trusted_proxy_headers={'x-forwarded-for'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '192.0.2.1')

    def test_parse_proxy_headers_forwarded_for_v6_missing_brackets(self):
        inst = self._makeOne()

        headers = {
            'X_FORWARDED_FOR': '2001:db8::0'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
            trusted_proxy_headers={'x-forwarded-for'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '2001:db8::0')

    def test_parse_proxy_headers_forwared_for_multiple(self):
        inst = self._makeOne()

        headers = {
            'X_FORWARDED_FOR': '192.0.2.1, 198.51.100.2, 203.0.113.1'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=2,
            trusted_proxy_headers={'x-forwarded-for'}
        )
        self.assertEqual(environ['REMOTE_ADDR'], '198.51.100.2')

    def test_parse_forwarded_multiple_proxies_trust_only_two(self):
        inst = self._makeOne()

        headers = {
            'FORWARDED': 'For=192.0.2.1;host=fake.com, For=198.51.100.2;host=example.com:8080, For=203.0.113.1'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=2,
            trusted_proxy_headers={'forwarded'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '198.51.100.2')
        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com:8080')
        self.assertEqual(environ['SERVER_PORT'], '8080')

    def test_parse_forwarded_multiple_proxies(self):
        inst = self._makeOne()

        headers = {
            'FORWARDED': 'for="[2001:db8::1]:3821";host="example.com:8443";proto="https", for=192.0.2.1;host="example.internal:8080"'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=2,
            trusted_proxy_headers={'forwarded'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '2001:db8::1')
        self.assertEqual(environ['REMOTE_PORT'], '3821')
        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com:8443')
        self.assertEqual(environ['SERVER_PORT'], '8443')
        self.assertEqual(environ['wsgi.url_scheme'], 'https')

    def test_parse_forwarded_multiple_proxies_minimal(self):
        inst = self._makeOne()

        headers = {
            'FORWARDED': 'for="[2001:db8::1]";proto="https", for=192.0.2.1;host="example.org"'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=2,
            trusted_proxy_headers={'forwarded'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '2001:db8::1')
        self.assertEqual(environ['SERVER_NAME'], 'example.org')
        self.assertEqual(environ['HTTP_HOST'], 'example.org')
        self.assertEqual(environ['SERVER_PORT'], '443')
        self.assertEqual(environ['wsgi.url_scheme'], 'https')

    def test_parse_proxy_headers_forwarded_host_with_port(self):
        inst = self._makeOne()

        headers = {
            'X_FORWARDED_FOR': '192.0.2.1, 198.51.100.2, 203.0.113.1',
            'X_FORWARDED_PROTO': 'http',
            'X_FORWARDED_HOST': 'example.com:8080',
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=2,
            trusted_proxy_headers={'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-host'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '198.51.100.2')
        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com:8080')
        self.assertEqual(environ['SERVER_PORT'], '8080')

    def test_parse_proxy_headers_forwarded_host_without_port(self):
        inst = self._makeOne()

        headers = {
            'X_FORWARDED_FOR': '192.0.2.1, 198.51.100.2, 203.0.113.1',
            'X_FORWARDED_PROTO': 'http',
            'X_FORWARDED_HOST': 'example.com',
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=2,
            trusted_proxy_headers={'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-host'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '198.51.100.2')
        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com')
        self.assertEqual(environ['SERVER_PORT'], '80')

    def test_parse_proxy_headers_forwarded_host_with_forwarded_port(self):
        inst = self._makeOne()

        headers = {
            'X_FORWARDED_FOR': '192.0.2.1, 198.51.100.2, 203.0.113.1',
            'X_FORWARDED_PROTO': 'http',
            'X_FORWARDED_HOST': 'example.com',
            'X_FORWARDED_PORT': '8080'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=2,
            trusted_proxy_headers={'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-host', 'x-forwarded-port'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '198.51.100.2')
        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com:8080')
        self.assertEqual(environ['SERVER_PORT'], '8080')

    def test_parse_proxy_headers_forwarded_host_multiple_with_forwarded_port(self):
        inst = self._makeOne()

        headers = {
            'X_FORWARDED_FOR': '192.0.2.1, 198.51.100.2, 203.0.113.1',
            'X_FORWARDED_PROTO': 'http',
            'X_FORWARDED_HOST': 'example.com, example.org',
            'X_FORWARDED_PORT': '8080'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=2,
            trusted_proxy_headers={'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-host', 'x-forwarded-port'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '198.51.100.2')
        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com:8080')
        self.assertEqual(environ['SERVER_PORT'], '8080')

    def test_parse_proxy_headers_forwarded_host_multiple_with_forwarded_port_limit_one_trusted(self):
        inst = self._makeOne()

        headers = {
            'X_FORWARDED_FOR': '192.0.2.1, 198.51.100.2, 203.0.113.1',
            'X_FORWARDED_PROTO': 'http',
            'X_FORWARDED_HOST': 'example.com, example.org',
            'X_FORWARDED_PORT': '8080'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
            trusted_proxy_headers={'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-host', 'x-forwarded-port'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '203.0.113.1')
        self.assertEqual(environ['SERVER_NAME'], 'example.org')
        self.assertEqual(environ['HTTP_HOST'], 'example.org:8080')
        self.assertEqual(environ['SERVER_PORT'], '8080')

    def test_parse_forwarded(self):
        inst = self._makeOne()

        headers = {
            'FORWARDED': 'For=198.51.100.2:5858;host=example.com:8080;proto=https'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
            trusted_proxy_headers={'forwarded'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '198.51.100.2')
        self.assertEqual(environ['REMOTE_PORT'], '5858')
        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com:8080')
        self.assertEqual(environ['SERVER_PORT'], '8080')
        self.assertEqual(environ['wsgi.url_scheme'], 'https')

    def test_parse_forwarded_empty_pair(self):
        inst = self._makeOne()

        headers = {
            'FORWARDED': 'For=198.51.100.2;;proto=https;by=_unused'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
            trusted_proxy_headers={'forwarded'}
        )

        self.assertEqual(environ['REMOTE_ADDR'], '198.51.100.2')

    def test_parse_forwarded_pair_token_whitespace(self):
        inst = self._makeOne()

        headers = {
            'FORWARDED': 'For=198.51.100.2; proto =https'
        }
        environ = {}

        with self.assertRaises(ValueError):
            inst.parse_proxy_headers(
                environ,
                headers,
                trusted_proxy_count=1,
                trusted_proxy_headers={'forwarded'}
            )

    def test_parse_forwarded_pair_value_whitespace(self):
        inst = self._makeOne()

        headers = {
            'FORWARDED': 'For= "198.51.100.2"; proto =https'
        }
        environ = {}

        with self.assertRaises(ValueError):
            inst.parse_proxy_headers(
                environ,
                headers,
                trusted_proxy_count=1,
                trusted_proxy_headers={'forwarded'}
            )

    def test_parse_forwarded_pair_no_equals(self):
        inst = self._makeOne()

        headers = {
            'FORWARDED': 'For'
        }
        environ = {}

        with self.assertRaises(ValueError):
            inst.parse_proxy_headers(
                environ,
                headers,
                trusted_proxy_count=1,
                trusted_proxy_headers={'forwarded'}
            )

    def test_parse_forwarded_warning_unknown_token(self):
        inst = self._makeOne()
        inst.logger = DummyLogger()

        headers = {
            'FORWARDED': 'For=198.51.100.2;host=example.com:8080;proto=https;unknown="yolo"'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
            trusted_proxy_headers={'forwarded'}
        )

        self.assertEqual(len(inst.logger.logged), 1)
        self.assertIn('Unknown Forwarded token', inst.logger.logged[0])

        self.assertEqual(environ['REMOTE_ADDR'], '198.51.100.2')
        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com:8080')
        self.assertEqual(environ['SERVER_PORT'], '8080')
        self.assertEqual(environ['wsgi.url_scheme'], 'https')

    def test_parse_no_valid_proxy_headers(self):
        inst = self._makeOne()
        inst.logger = DummyLogger()

        headers = {
            'X_FORWARDED_FOR': '198.51.100.2',
            'FORWARDED': 'For=198.51.100.2;host=example.com:8080;proto=https'
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
        )

        self.assertEqual(environ, {})

    def test_parse_multiple_x_forwarded_proto(self):
        inst = self._makeOne()
        inst.logger = DummyLogger()

        headers = {
            'X_FORWARDED_PROTO': 'http, https',
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
            trusted_proxy_headers={'x-forwarded-proto'}
        )

        self.assertEqual(environ, {})
        self.assertEqual(len(inst.logger.logged), 1)
        self.assertIn("Found multiple values in X-Forwarded-Proto", inst.logger.logged[0])

    def test_parse_multiple_x_forwarded_port(self):
        inst = self._makeOne()
        inst.logger = DummyLogger()

        headers = {
            'X_FORWARDED_PORT': '443, 80',
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
            trusted_proxy_headers={'x-forwarded-port'}
        )

        self.assertEqual(environ, {})
        self.assertEqual(len(inst.logger.logged), 1)
        self.assertIn("Found multiple values in X-Forwarded-Port", inst.logger.logged[0])

    def test_parse_forwarded_port_wrong_proto_port_80(self):
        inst = self._makeOne()
        inst.logger = DummyLogger()

        headers = {
            'X_FORWARDED_PORT': '80',
            'X_FORWARDED_PROTO': 'https',
            'X_FORWARDED_HOST': 'example.com',
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
            trusted_proxy_headers={'x-forwarded-port', 'x-forwarded-host', 'x-forwarded-proto'}
        )

        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com:80')
        self.assertEqual(environ['SERVER_PORT'], '80')
        self.assertEqual(environ['wsgi.url_scheme'], 'https')

    def test_parse_forwarded_port_wrong_proto_port_443(self):
        inst = self._makeOne()
        inst.logger = DummyLogger()

        headers = {
            'X_FORWARDED_PORT': '443',
            'X_FORWARDED_PROTO': 'http',
            'X_FORWARDED_HOST': 'example.com',
        }
        environ = {}
        inst.parse_proxy_headers(
            environ,
            headers,
            trusted_proxy_count=1,
            trusted_proxy_headers={'x-forwarded-port', 'x-forwarded-host', 'x-forwarded-proto'}
        )

        self.assertEqual(environ['SERVER_NAME'], 'example.com')
        self.assertEqual(environ['HTTP_HOST'], 'example.com:443')
        self.assertEqual(environ['SERVER_PORT'], '443')
        self.assertEqual(environ['wsgi.url_scheme'], 'http')


class TestErrorTask(unittest.TestCase):

    def _makeOne(self, channel=None, request=None):
        if channel is None:
            channel = DummyChannel()
        if request is None:
            request = DummyParser()
            request.error = DummyError()
        from waitress.task import ErrorTask
        return ErrorTask(channel, request)

    def test_execute_http_10(self):
        inst = self._makeOne()
        inst.execute()
        lines = filter_lines(inst.channel.written)
        self.assertEqual(len(lines), 9)
        self.assertEqual(lines[0], b'HTTP/1.0 432 Too Ugly')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertEqual(lines[2], b'Content-Length: 43')
        self.assertEqual(lines[3], b'Content-Type: text/plain')
        self.assertTrue(lines[4])
        self.assertEqual(lines[5], b'Server: waitress')
        self.assertEqual(lines[6], b'Too Ugly')
        self.assertEqual(lines[7], b'body')
        self.assertEqual(lines[8], b'(generated by waitress)')

    def test_execute_http_11(self):
        inst = self._makeOne()
        inst.version = '1.1'
        inst.execute()
        lines = filter_lines(inst.channel.written)
        self.assertEqual(len(lines), 8)
        self.assertEqual(lines[0], b'HTTP/1.1 432 Too Ugly')
        self.assertEqual(lines[1], b'Content-Length: 43')
        self.assertEqual(lines[2], b'Content-Type: text/plain')
        self.assertTrue(lines[3])
        self.assertEqual(lines[4], b'Server: waitress')
        self.assertEqual(lines[5], b'Too Ugly')
        self.assertEqual(lines[6], b'body')
        self.assertEqual(lines[7], b'(generated by waitress)')

    def test_execute_http_11_close(self):
        inst = self._makeOne()
        inst.version = '1.1'
        inst.request.headers['CONNECTION'] = 'close'
        inst.execute()
        lines = filter_lines(inst.channel.written)
        self.assertEqual(len(lines), 9)
        self.assertEqual(lines[0], b'HTTP/1.1 432 Too Ugly')
        self.assertEqual(lines[1], b'Connection: close')
        self.assertEqual(lines[2], b'Content-Length: 43')
        self.assertEqual(lines[3], b'Content-Type: text/plain')
        self.assertTrue(lines[4])
        self.assertEqual(lines[5], b'Server: waitress')
        self.assertEqual(lines[6], b'Too Ugly')
        self.assertEqual(lines[7], b'body')
        self.assertEqual(lines[8], b'(generated by waitress)')

    def test_execute_http_11_keep(self):
        inst = self._makeOne()
        inst.version = '1.1'
        inst.request.headers['CONNECTION'] = 'keep-alive'
        inst.execute()
        lines = filter_lines(inst.channel.written)
        self.assertEqual(len(lines), 8)
        self.assertEqual(lines[0], b'HTTP/1.1 432 Too Ugly')
        self.assertEqual(lines[1], b'Content-Length: 43')
        self.assertEqual(lines[2], b'Content-Type: text/plain')
        self.assertTrue(lines[3])
        self.assertEqual(lines[4], b'Server: waitress')
        self.assertEqual(lines[5], b'Too Ugly')
        self.assertEqual(lines[6], b'body')
        self.assertEqual(lines[7], b'(generated by waitress)')


class DummyError(object):
    code = '432'
    reason = 'Too Ugly'
    body = 'body'

class DummyTask(object):
    serviced = False
    cancelled = False

    def service(self):
        self.serviced = True

    def cancel(self):
        self.cancelled = True

class DummyAdj(object):
    log_socket_errors = True
    ident = 'waitress'
    host = '127.0.0.1'
    port = 80
    url_prefix = ''
    trusted_proxy = None
    trusted_proxy_count = 1
    trusted_proxy_headers = set()
    log_untrusted_proxy_headers = True
    clear_untrusted_proxy_headers = True

class DummyServer(object):
    server_name = 'localhost'
    effective_port = 80

    def __init__(self):
        self.adj = DummyAdj()

class DummyChannel(object):
    closed_when_done = False
    adj = DummyAdj()
    creation_time = 0
    addr = ('127.0.0.1', 39830)

    def __init__(self, server=None):
        if server is None:
            server = DummyServer()
        self.server = server
        self.written = b''
        self.otherdata = []

    def write_soon(self, data):
        if isinstance(data, bytes):
            self.written += data
        else:
            self.otherdata.append(data)
        return len(data)

class DummyParser(object):
    version = '1.0'
    command = 'GET'
    path = '/'
    query = ''
    url_scheme = 'http'
    expect_continue = False
    headers_finished = False

    def __init__(self):
        self.headers = {}

    def get_body_stream(self):
        return 'stream'

def filter_lines(s):
    return list(filter(None, s.split(b'\r\n')))

class DummyLogger(object):

    def __init__(self):
        self.logged = []

    def warning(self, msg, *args):
        self.logged.append(msg % args)

    def exception(self, msg, *args):
        self.logged.append(msg % args)
