Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: expose_tracebacks encode error #378

Merged
merged 4 commits into from
Apr 27, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/waitress/task.py
Original file line number Diff line number Diff line change
@@ -355,7 +355,7 @@ def execute(self):
self.response_headers.append(("Connection", "close"))
self.close_on_finish = True
self.content_length = len(body)
self.write(body.encode("latin-1"))
self.write(body)


class WSGITask(Task):
4 changes: 2 additions & 2 deletions src/waitress/utilities.py
Original file line number Diff line number Diff line change
@@ -262,8 +262,8 @@ def to_response(self):
status = "%s %s" % (self.code, self.reason)
body = "%s\r\n\r\n%s" % (self.reason, self.body)
tag = "\r\n\r\n(generated by waitress)"
body = body + tag
headers = [("Content-Type", "text/plain")]
body = (body + tag).encode("utf-8")
headers = [("Content-Type", "text/plain; charset=utf-8")]

return status, headers, body

2 changes: 2 additions & 0 deletions tests/fixtureapps/error_traceback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def app(environ, start_response): # pragma: no cover
raise ValueError("Invalid application: " + chr(8364))
64 changes: 59 additions & 5 deletions tests/test_functional.py
Original file line number Diff line number Diff line change
@@ -359,7 +359,7 @@ def test_broken_chunked_encoding(self):
sorted(headers.keys()),
["connection", "content-length", "content-type", "date", "server"],
)
self.assertEqual(headers["content-type"], "text/plain")
self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -381,7 +381,7 @@ def test_broken_chunked_encoding_invalid_hex(self):
sorted(headers.keys()),
["connection", "content-length", "content-type", "date", "server"],
)
self.assertEqual(headers["content-type"], "text/plain")
self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -403,7 +403,7 @@ def test_broken_chunked_encoding_invalid_extension(self):
sorted(headers.keys()),
["connection", "content-length", "content-type", "date", "server"],
)
self.assertEqual(headers["content-type"], "text/plain")
self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -428,7 +428,7 @@ def test_broken_chunked_encoding_missing_chunk_end(self):
sorted(headers.keys()),
["connection", "content-length", "content-type", "date", "server"],
)
self.assertEqual(headers["content-type"], "text/plain")
self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -1121,7 +1121,7 @@ def test_request_body_too_large_chunked_encoding(self):
self.assertline(line, "413", "Request Entity Too Large", "HTTP/1.1")
cl = int(headers["content-length"])
self.assertEqual(cl, len(response_body))
self.assertEqual(headers["content-type"], "text/plain")
self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -1269,6 +1269,49 @@ def test_in_generator(self):
self.assertRaises(ConnectionClosed, read_http, fp)


class InternalServerErrorTestsWithTraceback:
def setUp(self):
from tests.fixtureapps import error_traceback

self.start_subprocess(error_traceback.app, expose_tracebacks=True)

def tearDown(self):
self.stop_subprocess()

def test_expose_tracebacks_http_10(self):
to_send = b"GET / HTTP/1.0\r\n\r\n"
self.connect()
self.sock.send(to_send)
with self.sock.makefile("rb", 0) as fp:
line, headers, response_body = read_http(fp)
self.assertline(line, "500", "Internal Server Error", "HTTP/1.0")
cl = int(headers["content-length"])
self.assertEqual(cl, len(response_body))
self.assertTrue(response_body.startswith(b"Internal Server Error"))
self.assertEqual(headers["connection"], "close")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)

def test_expose_tracebacks_http_11(self):
to_send = b"GET / HTTP/1.1\r\n\r\n"
self.connect()
self.sock.send(to_send)
with self.sock.makefile("rb", 0) as fp:
line, headers, response_body = read_http(fp)
self.assertline(line, "500", "Internal Server Error", "HTTP/1.1")
cl = int(headers["content-length"])
self.assertEqual(cl, len(response_body))
self.assertTrue(response_body.startswith(b"Internal Server Error"))
self.assertEqual(
sorted(headers.keys()),
["connection", "content-length", "content-type", "date", "server"],
)
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)


class FileWrapperTests:
def setUp(self):
from tests.fixtureapps import filewrapper
@@ -1538,6 +1581,12 @@ class TcpInternalServerErrorTests(
pass


class TcpInternalServerErrorTestsWithTraceback(
InternalServerErrorTestsWithTraceback, TcpTests, unittest.TestCase
):
pass


class TcpFileWrapperTests(FileWrapperTests, TcpTests, unittest.TestCase):
pass

@@ -1604,6 +1653,11 @@ class UnixInternalServerErrorTests(
):
pass

class UnixInternalServerErrorTestsWithTraceback(
InternalServerErrorTestsWithTraceback, UnixTests, unittest.TestCase
):
pass

class UnixFileWrapperTests(FileWrapperTests, UnixTests, unittest.TestCase):
pass

4 changes: 2 additions & 2 deletions tests/test_proxy_headers.py
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ def start_response(status, response_headers):
response.headers = response_headers

response.steps = list(app(environ, start_response))
response.body = b"".join(s.encode("latin-1") for s in response.steps)
response.body = b"".join(s for s in response.steps)
return response

def test_get_environment_values_w_scheme_override_untrusted(self):
@@ -727,7 +727,7 @@ class DummyApp:
def __call__(self, environ, start_response):
self.environ = environ
start_response("200 OK", [("Content-Type", "text/plain")])
yield "hello"
yield b"hello"


class DummyResponse:
8 changes: 4 additions & 4 deletions tests/test_task.py
Original file line number Diff line number Diff line change
@@ -869,7 +869,7 @@ def test_execute_http_10(self):
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.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
self.assertTrue(lines[4])
self.assertEqual(lines[5], b"Server: waitress")
self.assertEqual(lines[6], b"Too Ugly")
@@ -885,7 +885,7 @@ def test_execute_http_11(self):
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.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
self.assertTrue(lines[4])
self.assertEqual(lines[5], b"Server: waitress")
self.assertEqual(lines[6], b"Too Ugly")
@@ -902,7 +902,7 @@ def test_execute_http_11_close(self):
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.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
self.assertTrue(lines[4])
self.assertEqual(lines[5], b"Server: waitress")
self.assertEqual(lines[6], b"Too Ugly")
@@ -919,7 +919,7 @@ def test_execute_http_11_keep_forces_close(self):
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.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
self.assertTrue(lines[4])
self.assertEqual(lines[5], b"Server: waitress")
self.assertEqual(lines[6], b"Too Ugly")