diff --git a/falcon/response.py b/falcon/response.py index 4812815..be45fe3 100644 --- a/falcon/response.py +++ b/falcon/response.py @@ -272,15 +272,14 @@ class Response(object): For setting cookies, see instead :meth:`~.set_cookie` Args: - name (str): Header name to set (case-insensitive). Must be of - type ``str`` or ``StringType``, and only character values 0x00 - through 0xFF may be used on platforms that use wide - characters. + name (str): Header name (case-insensitive). The restrictions + noted below for the header's value also apply here. value (str): Value for the header. Must be of type ``str`` or - ``StringType``, and only character values 0x00 through 0xFF - may be used on platforms that use wide characters. - + ``StringType`` and contain only ISO-8859-1 characters. + Under Python 2.x, the ``unicode`` type is also accepted, + although such strings are also limited to ISO-8859-1. """ + name, value = self._encode_header(name, value) # NOTE(kgriffs): normalize name by lowercasing it self._headers[name.lower()] = value @@ -297,15 +296,16 @@ class Response(object): For setting cookies, see :py:meth:`~.set_cookie` Args: - name (str): Header name to set (case-insensitive). Must be of - type ``str`` or ``StringType``, and only character values 0x00 - through 0xFF may be used on platforms that use wide - characters. + name (str): Header name (case-insensitive). The restrictions + noted below for the header's value also apply here. value (str): Value for the header. Must be of type ``str`` or - ``StringType``, and only character values 0x00 through 0xFF - may be used on platforms that use wide characters. + ``StringType`` and contain only ISO-8859-1 characters. + Under Python 2.x, the ``unicode`` type is also accepted, + although such strings are also limited to ISO-8859-1. """ + name, value = self._encode_header(name, value) + name = name.lower() if name in self._headers: value = self._headers[name] + ',' + value @@ -320,10 +320,11 @@ class Response(object): Args: headers (dict or list): A dictionary of header names and values - to set, or ``list`` of (*name*, *value*) tuples. Both *name* - and *value* must be of type ``str`` or ``StringType``, and - only character values 0x00 through 0xFF may be used on - platforms that use wide characters. + to set, or a ``list`` of (*name*, *value*) tuples. Both *name* + and *value* must be of type ``str`` or ``StringType`` and + contain only ISO-8859-1 characters. Under Python 2.x, the + ``unicode`` type is also accepted, although such strings are + also limited to ISO-8859-1. Note: Falcon can process a list of tuples slightly faster @@ -341,6 +342,7 @@ class Response(object): # normalize the header names. _headers = self._headers for name, value in headers: + name, value = self._encode_header(name, value) _headers[name.lower()] = value def add_link(self, target, rel, title=None, title_star=None, @@ -541,6 +543,16 @@ class Response(object): """, lambda v: ', '.join(v)) + def _encode_header(self, name, value, py2=PY2): + if py2: # pragma: no cover + if isinstance(name, unicode): + name = name.encode('ISO-8859-1') + + if isinstance(value, unicode): + value = value.encode('ISO-8859-1') + + return name, value + def _wsgi_headers(self, media_type=None, py2=PY2): """Convert headers into the format expected by WSGI servers. diff --git a/tests/test_headers.py b/tests/test_headers.py index 783e59d..0863b98 100644 --- a/tests/test_headers.py +++ b/tests/test_headers.py @@ -117,6 +117,17 @@ class LocationHeaderUnicodeResource: resp.content_location = self.URL1 +class UnicodeHeaderResource: + + def on_get(self, req, resp): + resp.set_headers([ + (u'X-auTH-toKEN', 'toomanysecrets'), + ('Content-TYpE', u'application/json'), + (u'X-symBOl', u'\u0040'), + (u'X-symb\u00F6l', u'\u00FF'), + ]) + + class VaryHeaderResource: def __init__(self, vary): @@ -361,6 +372,22 @@ class TestHeaders(testing.TestBase): content_location = ('content-location', '/%C3%A7runchy/bacon') self.assertIn(content_location, self.srmock.headers) + def test_unicode_headers(self): + self.api.add_route(self.test_route, UnicodeHeaderResource()) + self.simulate_request(self.test_route) + + expect = ('x-auth-token', 'toomanysecrets') + self.assertIn(expect, self.srmock.headers) + + expect = ('content-type', 'application/json') + self.assertIn(expect, self.srmock.headers) + + expect = ('x-symbol', '@') + self.assertIn(expect, self.srmock.headers) + + expect = ('x-symb\xF6l', '\xFF') + self.assertIn(expect, self.srmock.headers) + def test_response_set_and_get_header(self): self.resource = HeaderHelpersResource() self.api.add_route(self.test_route, self.resource)