Skip to content

Commit

Permalink
fix(Response): HTTP headers as strings
Browse files Browse the repository at this point in the history
Ensure HTTP headers and values are string types per PEP3333. This
fixes WSGI servers running under py2k.

Fixes #413

Co-Authored-By: Kurt Griffiths <mail@kgriffs.com>
  • Loading branch information
Gino Ledesma and kgriffs committed Aug 21, 2015
1 parent a2a1177 commit 0c67a3e
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 17 deletions.
46 changes: 29 additions & 17 deletions falcon/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,14 @@ def set_header(self, name, value):
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
Expand All @@ -297,15 +296,16 @@ def append_header(self, name, value):
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
Expand All @@ -320,10 +320,11 @@ def set_headers(self, headers):
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
Expand All @@ -341,6 +342,7 @@ def set_headers(self, headers):
# 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,
Expand Down Expand Up @@ -541,6 +543,16 @@ def add_link(self, target, rel, title=None, title_star=None,
""",
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.
Expand Down
27 changes: 27 additions & 0 deletions tests/test_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ def on_head(self, req, resp):
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):
Expand Down Expand Up @@ -361,6 +372,22 @@ def test_unicode_location_headers(self):
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)
Expand Down

0 comments on commit 0c67a3e

Please sign in to comment.