summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Hellkamp <marc@gsites.de>2019-12-18 11:46:18 +0100
committerMarcel Hellkamp <marc@gsites.de>2019-12-18 11:46:18 +0100
commit5c52eb0c0b7f21836375b3584896094735171a9f (patch)
tree72ed031c3ce7114ffa231f996f83ccdbc9ae64a7
parentd9903c7508294e10b932099afb1e7f108f9a9022 (diff)
downloadbottle-5c52eb0c0b7f21836375b3584896094735171a9f.tar.gz
fix #1187: Allow non-ASCII characters in status_line
Uncommon, but valid.
-rwxr-xr-xbottle.py17
-rwxr-xr-xtest/test_environ.py9
2 files changed, 25 insertions, 1 deletions
diff --git a/bottle.py b/bottle.py
index 9368c46..1063e2a 100755
--- a/bottle.py
+++ b/bottle.py
@@ -730,6 +730,13 @@ class Bottle(object):
def start_response(status, headerlist, exc_info=None):
if exc_info:
_raise(*exc_info)
+ if py3k:
+ # Errors here mean that the mounted WSGI app did not
+ # follow PEP-3333 (which requires latin1) or used a
+ # pre-encoding other than utf8 :/
+ status = status.encode('latin1').decode('utf8')
+ headerlist = [k, v.encode('latin1').decode('utf8')
+ for k, v in headerlist]
rs.status = status
for name, value in headerlist:
rs.add_header(name, value)
@@ -1108,7 +1115,7 @@ class Bottle(object):
or environ['REQUEST_METHOD'] == 'HEAD':
if hasattr(out, 'close'): out.close()
out = []
- start_response(response._status_line, response.headerlist)
+ start_response(response._wsgi_status_line(), response.headerlist)
return out
except (KeyboardInterrupt, SystemExit, MemoryError):
raise
@@ -1708,6 +1715,8 @@ class BaseResponse(object):
if isinstance(status, int):
code, status = status, _HTTP_STATUS_LINES.get(status)
elif ' ' in status:
+ if '\n' in status or '\r' in status or '\0' in status:
+ raise ValueError('Status line must not include control chars.')
status = status.strip()
code = int(status.split()[0])
else:
@@ -1768,6 +1777,12 @@ class BaseResponse(object):
allowed with the current response status code. """
return self.headerlist
+ def _wsgi_status_line(self):
+ """ WSGI conform status line (latin1-encodeable) """
+ if py3k:
+ return self._status_line.encode('utf8').decode('latin1')
+ return self._status_line
+
@property
def headerlist(self):
""" WSGI conform list of (header, value) tuples. """
diff --git a/test/test_environ.py b/test/test_environ.py
index f0b8874..3e6e484 100755
--- a/test/test_environ.py
+++ b/test/test_environ.py
@@ -556,6 +556,15 @@ class TestResponse(unittest.TestCase):
self.assertEqual(rs.status_code, 404)
self.assertEqual(rs.status_line, '404 Brain not Found')
+ # Unicode in status line (thanks RFC7230 :/)
+ if bottle.py3k:
+ def test(): rs.status = '400 Non-ASCÜ'
+ self.assertRaises(ValueError, test)
+ self.assertEqual(rs.status, rs.status_line)
+ self.assertEqual(rs.status_code, 400)
+ wire = rs._wsgi_status_line().encode('latin1')
+ self.assertEqual(rs.status, wire.decode('utf8'))
+
def test(): rs.status = '5 Illegal Code'
self.assertRaises(ValueError, test)
self.assertEqual(rs.status, rs.status_line) # last value