diff options
author | Michael Merickel <michael@merickel.org> | 2019-07-19 03:02:19 -0500 |
---|---|---|
committer | Michael Merickel <michael@merickel.org> | 2019-07-19 03:02:19 -0500 |
commit | a58c8889a5be154a1e616881cabfefeee337c718 (patch) | |
tree | 36806cfdb9da44d31c4a4456ca63081b80d2479c | |
parent | 9cf94669a9bb21d51741f5840b765e4ab5f94d48 (diff) | |
download | waitress-a58c8889a5be154a1e616881cabfefeee337c718.tar.gz |
add proxy header tests
-rw-r--r-- | .coveragerc | 12 | ||||
-rw-r--r-- | setup.cfg | 1 | ||||
-rw-r--r-- | tox.ini | 11 | ||||
-rw-r--r-- | waitress/server.py | 4 | ||||
-rw-r--r-- | waitress/tests/fixtureapps/echo.py | 12 | ||||
-rw-r--r-- | waitress/tests/test_functional.py | 56 | ||||
-rw-r--r-- | waitress/tests/test_proxy_headers.py | 31 |
7 files changed, 105 insertions, 22 deletions
diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..c2c3d96 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,12 @@ +[run] +parallel = true +concurrency = thread,multiprocessing +source = + waitress + +omit = + waitress/tests/fixtureapps/getline.py + +[report] +show_missing = true +precision = 2 @@ -3,7 +3,6 @@ zip_ok = false [nosetests] match=^test -where=waitress nocapture=1 cover-package=waitress cover-erase=1 @@ -21,9 +21,7 @@ extras = [py-cover] commands = - coverage run --source=waitress --parallel-mode {envbindir}/nosetests - coverage combine - coverage xml -o {envname}.xml + coverage run {envbindir}/nosetests waitress extras = testing @@ -31,22 +29,17 @@ extras = [testenv:py27-cover] commands = {[py-cover]commands} -setenv = - COVERAGE_FILE=.coverage.py2 [testenv:py35-cover] commands = {[py-cover]commands} -setenv = - COVERAGE_FILE=.coverage.py3 [testenv:coverage] basepython = python3.5 commands = - coverage erase coverage combine coverage xml - coverage report --show-missing --fail-under=100 --omit=waitress/tests/fixtureapps/getline.py + coverage report --show-missing --fail-under=100 deps = coverage setenv = diff --git a/waitress/server.py b/waitress/server.py index bc68c77..307c377 100644 --- a/waitress/server.py +++ b/waitress/server.py @@ -179,15 +179,11 @@ class BaseWSGIServer(wasyncore.dispatcher, object): adj=None, # adjustments sockinfo=None, # opaque object bind_socket=True, - logger=None, # test shim **kw ): if adj is None: adj = Adjustments(**kw) - if logger is not None: - self.logger = logger - if adj.trusted_proxy or adj.clear_untrusted_proxy_headers: # wrap the application to deal with proxy headers # we wrap it here because webtest subclasses the TcpWSGIServer diff --git a/waitress/tests/fixtureapps/echo.py b/waitress/tests/fixtureapps/echo.py index 8a01ea8..5509703 100644 --- a/waitress/tests/fixtureapps/echo.py +++ b/waitress/tests/fixtureapps/echo.py @@ -1,6 +1,18 @@ from collections import namedtuple import json +def app_body_only(environ, start_response): # pragma: no cover + cl = environ.get('CONTENT_LENGTH', None) + if cl is not None: + cl = int(cl) + body = environ['wsgi.input'].read(cl) + cl = str(len(body)) + start_response('200 OK', [ + ('Content-Length', cl), + ('Content-Type', 'text/plain'), + ]) + return [body] + def app(environ, start_response): # pragma: no cover cl = environ.get('CONTENT_LENGTH', None) if cl is not None: diff --git a/waitress/tests/test_functional.py b/waitress/tests/test_functional.py index 6748c5f..0a39771 100644 --- a/waitress/tests/test_functional.py +++ b/waitress/tests/test_functional.py @@ -2,6 +2,7 @@ import errno import logging import multiprocessing import os +import signal import socket import string import subprocess @@ -28,8 +29,23 @@ def start_server(app, svr, queue, **kwargs): # pragma: no cover """Run a fixture application. """ logging.getLogger('waitress').addHandler(NullHandler()) + try_register_coverage() svr(app, queue, **kwargs).run() +def try_register_coverage(): # pragma: no cover + # Hack around multiprocessing exiting early and not triggering coverage's + # atexit handler by trapping the SIGTERM and saving coverage explicitly. + if '_COVERAGE_RCFILE' in os.environ: + import coverage + cov = coverage.Coverage(config_file=os.getenv('_COVERAGE_RCFILE')) + cov.start() + + def sigterm(*args): + cov.stop() + cov.save() + sys.exit(0) + signal.signal(signal.SIGTERM, sigterm) + class FixtureTcpWSGIServer(server.TcpWSGIServer): """A version of TcpWSGIServer that relays back what it's bound to. """ @@ -139,15 +155,21 @@ class EchoTests(object): def setUp(self): from waitress.tests.fixtureapps import echo - self.start_subprocess(echo.app) + self.start_subprocess( + echo.app, + trusted_proxy='*', + trusted_proxy_count=1, + trusted_proxy_headers={'x-forwarded-for', 'x-forwarded-proto'}, + clear_untrusted_proxy_headers=True, + ) def tearDown(self): self.stop_subprocess() def _read_echo(self, fp): from waitress.tests.fixtureapps import echo - line, headers, response_body = read_http(fp) - return line, headers, echo.parse_response(response_body) + line, headers, body = read_http(fp) + return line, headers, echo.parse_response(body) def test_date_and_server(self): to_send = ("GET / HTTP/1.0\n" @@ -416,11 +438,35 @@ class EchoTests(object): self.assertEqual(int(response.status), 200) self.assertEqual(response.getheader('connection'), 'close') + def test_proxy_headers(self): + to_send = ( + "GET / HTTP/1.0\n" + "Content-Length: 0\n" + "Host: www.google.com:8080\n" + "X-Forwarded-For: 192.168.1.1\n" + "X-Forwarded-Proto: https\n" + "X-Forwarded-Port: 5000\n\n" + ) + to_send = tobytes(to_send) + self.connect() + self.sock.send(to_send) + fp = self.sock.makefile('rb', 0) + line, headers, echo = self._read_echo(fp) + self.assertline(line, '200', 'OK', 'HTTP/1.0') + self.assertEqual(headers.get('server'), 'waitress') + self.assertTrue(headers.get('date')) + self.assertIsNone(echo.headers.get('X_FORWARDED_PORT')) + self.assertEqual(echo.headers['HOST'], 'www.google.com:8080') + self.assertEqual(echo.scheme, 'https') + self.assertEqual(echo.remote_addr, '192.168.1.1') + self.assertEqual(echo.remote_host, '192.168.1.1') + + class PipeliningTests(object): def setUp(self): from waitress.tests.fixtureapps import echo - self.start_subprocess(echo.app) + self.start_subprocess(echo.app_body_only) def tearDown(self): self.stop_subprocess() @@ -459,7 +505,7 @@ class ExpectContinueTests(object): def setUp(self): from waitress.tests.fixtureapps import echo - self.start_subprocess(echo.app) + self.start_subprocess(echo.app_body_only) def tearDown(self): self.stop_subprocess() diff --git a/waitress/tests/test_proxy_headers.py b/waitress/tests/test_proxy_headers.py index aa4c907..f3af58a 100644 --- a/waitress/tests/test_proxy_headers.py +++ b/waitress/tests/test_proxy_headers.py @@ -622,6 +622,34 @@ class TestProxyHeadersMiddleware(unittest.TestCase): self.assertEqual(environ['SERVER_PORT'], '443') self.assertEqual(environ['wsgi.url_scheme'], 'http') + def test_parse_forwarded_for_bad_quote(self): + inner = DummyApp() + app = self._makeOne( + inner, + trusted_proxy='*', + trusted_proxy_count=1, + trusted_proxy_headers={'x-forwarded-for'}, + ) + response = self._callFUT(app, headers={ + 'X_FORWARDED_FOR': '"foo' + }) + self.assertEqual(response.status, '400 Bad Request') + self.assertIn(b'Header "X-Forwarded-For" malformed', response.body) + + def test_parse_forwarded_host_bad_quote(self): + inner = DummyApp() + app = self._makeOne( + inner, + trusted_proxy='*', + trusted_proxy_count=1, + trusted_proxy_headers={'x-forwarded-host'}, + ) + response = self._callFUT(app, headers={ + 'X_FORWARDED_HOST': '"foo' + }) + self.assertEqual(response.status, '400 Bad Request') + self.assertIn(b'Header "X-Forwarded-Host" malformed', response.body) + class DummyLogger(object): def __init__(self): @@ -630,9 +658,6 @@ class DummyLogger(object): def warning(self, msg, *args): self.logged.append(msg % args) - def exception(self, msg, *args): - self.logged.append(msg % args) - class DummyApp(object): def __call__(self, environ, start_response): |