Skip to content

Commit

Permalink
Merge f15cf5b into 1baff46
Browse files Browse the repository at this point in the history
  • Loading branch information
d-maurer committed Nov 13, 2020
2 parents 1baff46 + f15cf5b commit d3f15a2
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 6 deletions.
8 changes: 8 additions & 0 deletions CHANGES.rst
Expand Up @@ -12,6 +12,14 @@ https://zope.readthedocs.io/en/2.13/CHANGES.html

- Update dependencies to the latest releases that still support Python 2.

Fixes
+++++

- Convert ``bytes`` (Python 3) and ``unicode`` (Python 2) values for
HTTP response headers into native strings using the HTTP/1.1
stipulated ``ISO-8859-1`` encoding. This makes ``waitress`` happy
which insists on native strings for those values.


4.5.2 (2020-11-12)
------------------
Expand Down
13 changes: 11 additions & 2 deletions src/ZPublisher/HTTPResponse.py
Expand Up @@ -113,6 +113,10 @@


def _scrubHeader(name, value):
if PY3 and isinstance(value, bytes):
# handle ``bytes`` values correctly
# we assume that the provider knows that HTTP 1.1 stipulates ISO-8859-1
value = value.decode('ISO-8859-1')
if not isinstance(value, string_types):
value = str(value)
return ''.join(_CRLF.split(str(name))), ''.join(_CRLF.split(value))
Expand Down Expand Up @@ -723,7 +727,12 @@ def listHeaders(self):
('X-Powered-By', 'Zope (www.zope.org), Python (www.python.org)')
]

encode = header_encoding_registry.encode
def encode(key, value, henc=header_encoding_registry.encode):
value = henc(key, value)
if PY2 and not isinstance(value, str):
# ``value`` is ``unicode``
value = value.encode('ISO-8859-1')
return value
for key, value in self.headers.items():
if key.lower() == key:
# only change non-literal header names
Expand Down Expand Up @@ -800,7 +809,7 @@ def _traceback(self, t, v, tb, as_html=1):
def _error_html(self, title, body):
return ("""<!DOCTYPE html><html>
<head><title>Site Error</title><meta charset="utf-8" /></head>
<body bgcolor="#FFFFFF">
<body bgcolor="white">
<h2>Site Error</h2>
<p>An error was encountered while publishing this resource.
</p>
Expand Down
14 changes: 10 additions & 4 deletions src/ZPublisher/tests/testHTTPResponse.py
Expand Up @@ -4,6 +4,8 @@
import unittest
from io import BytesIO

from six import PY2

from zExceptions import BadRequest
from zExceptions import Forbidden
from zExceptions import InternalError
Expand Down Expand Up @@ -1384,20 +1386,24 @@ def test_isHTML_not_decodable_bytes(self):

def test_header_encoding(self):
r = self._makeOne()
r.setHeader("unencoded1", u"€")
ae = u"ä"
ae_enc = ae.encode("ISO-8859-1") if PY2 else ae
r.setHeader("unencoded1", ae)
r.setHeader("content-disposition", u"a; p=€")
r.addHeader("unencoded2", u"€")
r.addHeader("unencoded2", ae)
r.addHeader("content-disposition", u"a2; p2=€")
r.addHeader("bytes", b"abc")
hdrs = r.listHeaders()[1:] # drop `X-Powered...`
shdrs, ahdrs = dict(hdrs[:2]), dict(hdrs[2:])
# for some reasons, `set` headers change their name
# while `add` headers do not
self.assertEqual(shdrs["Unencoded1"], u"€")
self.assertEqual(ahdrs["unencoded2"], u"€")
self.assertEqual(shdrs["Unencoded1"], ae_enc)
self.assertEqual(ahdrs["unencoded2"], ae_enc)
self.assertEqual(shdrs["Content-Disposition"],
u"a; p=?; p*=utf-8''%E2%82%AC")
self.assertEqual(ahdrs["content-disposition"],
u"a2; p2=?; p2*=utf-8''%E2%82%AC")
self.assertEqual(ahdrs["bytes"], "abc")


class TestHeaderEncodingRegistry(unittest.TestCase):
Expand Down

0 comments on commit d3f15a2

Please sign in to comment.