Skip to content

Commit 3066fdd

Browse files
authored
Merge pull request #378 from Pylons/bugfix/expose_tracebacks-encode-error
Bugfix: expose_tracebacks encode error
2 parents 603d2c1 + 4467d76 commit 3066fdd

File tree

6 files changed

+70
-14
lines changed

6 files changed

+70
-14
lines changed

src/waitress/task.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ def execute(self):
355355
self.response_headers.append(("Connection", "close"))
356356
self.close_on_finish = True
357357
self.content_length = len(body)
358-
self.write(body.encode("latin-1"))
358+
self.write(body)
359359

360360

361361
class WSGITask(Task):

src/waitress/utilities.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,8 @@ def to_response(self):
262262
status = "%s %s" % (self.code, self.reason)
263263
body = "%s\r\n\r\n%s" % (self.reason, self.body)
264264
tag = "\r\n\r\n(generated by waitress)"
265-
body = body + tag
266-
headers = [("Content-Type", "text/plain")]
265+
body = (body + tag).encode("utf-8")
266+
headers = [("Content-Type", "text/plain; charset=utf-8")]
267267

268268
return status, headers, body
269269

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def app(environ, start_response): # pragma: no cover
2+
raise ValueError("Invalid application: " + chr(8364))

tests/test_functional.py

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ def test_broken_chunked_encoding(self):
359359
sorted(headers.keys()),
360360
["connection", "content-length", "content-type", "date", "server"],
361361
)
362-
self.assertEqual(headers["content-type"], "text/plain")
362+
self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
363363
# connection has been closed
364364
self.send_check_error(to_send)
365365
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -381,7 +381,7 @@ def test_broken_chunked_encoding_invalid_hex(self):
381381
sorted(headers.keys()),
382382
["connection", "content-length", "content-type", "date", "server"],
383383
)
384-
self.assertEqual(headers["content-type"], "text/plain")
384+
self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
385385
# connection has been closed
386386
self.send_check_error(to_send)
387387
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -403,7 +403,7 @@ def test_broken_chunked_encoding_invalid_extension(self):
403403
sorted(headers.keys()),
404404
["connection", "content-length", "content-type", "date", "server"],
405405
)
406-
self.assertEqual(headers["content-type"], "text/plain")
406+
self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
407407
# connection has been closed
408408
self.send_check_error(to_send)
409409
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -428,7 +428,7 @@ def test_broken_chunked_encoding_missing_chunk_end(self):
428428
sorted(headers.keys()),
429429
["connection", "content-length", "content-type", "date", "server"],
430430
)
431-
self.assertEqual(headers["content-type"], "text/plain")
431+
self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
432432
# connection has been closed
433433
self.send_check_error(to_send)
434434
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -1121,7 +1121,7 @@ def test_request_body_too_large_chunked_encoding(self):
11211121
self.assertline(line, "413", "Request Entity Too Large", "HTTP/1.1")
11221122
cl = int(headers["content-length"])
11231123
self.assertEqual(cl, len(response_body))
1124-
self.assertEqual(headers["content-type"], "text/plain")
1124+
self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
11251125
# connection has been closed
11261126
self.send_check_error(to_send)
11271127
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -1269,6 +1269,49 @@ def test_in_generator(self):
12691269
self.assertRaises(ConnectionClosed, read_http, fp)
12701270

12711271

1272+
class InternalServerErrorTestsWithTraceback:
1273+
def setUp(self):
1274+
from tests.fixtureapps import error_traceback
1275+
1276+
self.start_subprocess(error_traceback.app, expose_tracebacks=True)
1277+
1278+
def tearDown(self):
1279+
self.stop_subprocess()
1280+
1281+
def test_expose_tracebacks_http_10(self):
1282+
to_send = b"GET / HTTP/1.0\r\n\r\n"
1283+
self.connect()
1284+
self.sock.send(to_send)
1285+
with self.sock.makefile("rb", 0) as fp:
1286+
line, headers, response_body = read_http(fp)
1287+
self.assertline(line, "500", "Internal Server Error", "HTTP/1.0")
1288+
cl = int(headers["content-length"])
1289+
self.assertEqual(cl, len(response_body))
1290+
self.assertTrue(response_body.startswith(b"Internal Server Error"))
1291+
self.assertEqual(headers["connection"], "close")
1292+
# connection has been closed
1293+
self.send_check_error(to_send)
1294+
self.assertRaises(ConnectionClosed, read_http, fp)
1295+
1296+
def test_expose_tracebacks_http_11(self):
1297+
to_send = b"GET / HTTP/1.1\r\n\r\n"
1298+
self.connect()
1299+
self.sock.send(to_send)
1300+
with self.sock.makefile("rb", 0) as fp:
1301+
line, headers, response_body = read_http(fp)
1302+
self.assertline(line, "500", "Internal Server Error", "HTTP/1.1")
1303+
cl = int(headers["content-length"])
1304+
self.assertEqual(cl, len(response_body))
1305+
self.assertTrue(response_body.startswith(b"Internal Server Error"))
1306+
self.assertEqual(
1307+
sorted(headers.keys()),
1308+
["connection", "content-length", "content-type", "date", "server"],
1309+
)
1310+
# connection has been closed
1311+
self.send_check_error(to_send)
1312+
self.assertRaises(ConnectionClosed, read_http, fp)
1313+
1314+
12721315
class FileWrapperTests:
12731316
def setUp(self):
12741317
from tests.fixtureapps import filewrapper
@@ -1538,6 +1581,12 @@ class TcpInternalServerErrorTests(
15381581
pass
15391582

15401583

1584+
class TcpInternalServerErrorTestsWithTraceback(
1585+
InternalServerErrorTestsWithTraceback, TcpTests, unittest.TestCase
1586+
):
1587+
pass
1588+
1589+
15411590
class TcpFileWrapperTests(FileWrapperTests, TcpTests, unittest.TestCase):
15421591
pass
15431592

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

1656+
class UnixInternalServerErrorTestsWithTraceback(
1657+
InternalServerErrorTestsWithTraceback, UnixTests, unittest.TestCase
1658+
):
1659+
pass
1660+
16071661
class UnixFileWrapperTests(FileWrapperTests, UnixTests, unittest.TestCase):
16081662
pass
16091663

tests/test_proxy_headers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def start_response(status, response_headers):
1616
response.headers = response_headers
1717

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

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

732732

733733
class DummyResponse:

tests/test_task.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,7 @@ def test_execute_http_10(self):
869869
self.assertEqual(lines[0], b"HTTP/1.0 432 Too Ugly")
870870
self.assertEqual(lines[1], b"Connection: close")
871871
self.assertEqual(lines[2], b"Content-Length: 43")
872-
self.assertEqual(lines[3], b"Content-Type: text/plain")
872+
self.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
873873
self.assertTrue(lines[4])
874874
self.assertEqual(lines[5], b"Server: waitress")
875875
self.assertEqual(lines[6], b"Too Ugly")
@@ -885,7 +885,7 @@ def test_execute_http_11(self):
885885
self.assertEqual(lines[0], b"HTTP/1.1 432 Too Ugly")
886886
self.assertEqual(lines[1], b"Connection: close")
887887
self.assertEqual(lines[2], b"Content-Length: 43")
888-
self.assertEqual(lines[3], b"Content-Type: text/plain")
888+
self.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
889889
self.assertTrue(lines[4])
890890
self.assertEqual(lines[5], b"Server: waitress")
891891
self.assertEqual(lines[6], b"Too Ugly")
@@ -902,7 +902,7 @@ def test_execute_http_11_close(self):
902902
self.assertEqual(lines[0], b"HTTP/1.1 432 Too Ugly")
903903
self.assertEqual(lines[1], b"Connection: close")
904904
self.assertEqual(lines[2], b"Content-Length: 43")
905-
self.assertEqual(lines[3], b"Content-Type: text/plain")
905+
self.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
906906
self.assertTrue(lines[4])
907907
self.assertEqual(lines[5], b"Server: waitress")
908908
self.assertEqual(lines[6], b"Too Ugly")
@@ -919,7 +919,7 @@ def test_execute_http_11_keep_forces_close(self):
919919
self.assertEqual(lines[0], b"HTTP/1.1 432 Too Ugly")
920920
self.assertEqual(lines[1], b"Connection: close")
921921
self.assertEqual(lines[2], b"Content-Length: 43")
922-
self.assertEqual(lines[3], b"Content-Type: text/plain")
922+
self.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
923923
self.assertTrue(lines[4])
924924
self.assertEqual(lines[5], b"Server: waitress")
925925
self.assertEqual(lines[6], b"Too Ugly")

0 commit comments

Comments
 (0)