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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/waitress/task.py
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions src/waitress/utilities.py
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions tests/fixtureapps/error_traceback.py
@@ -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
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1538,6 +1581,12 @@ class TcpInternalServerErrorTests(
pass


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


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

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

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

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

Expand Down
4 changes: 2 additions & 2 deletions tests/test_proxy_headers.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions tests/test_task.py
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand Down