Permalink
Browse files

Document and test for type differences between get_cookie and get_sec…

…ure_cookie.

Closes #501.
  • Loading branch information...
1 parent 088f0c1 commit f5012b423f6586663dc79c0374c6d986a2a5da78 @bdarnell bdarnell committed May 7, 2012
Showing with 29 additions and 5 deletions.
  1. +21 −4 tornado/test/web_test.py
  2. +8 −1 tornado/web.py
View
25 tornado/test/web_test.py
@@ -5,7 +5,7 @@
from tornado.template import DictLoader
from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase
from tornado.util import b, bytes_type, ObjectDict
-from tornado.web import RequestHandler, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature
+from tornado.web import RequestHandler, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature, create_signed_value
import binascii
import logging
@@ -60,6 +60,13 @@ def test_cookie_tampering_future_timestamp(self):
# it gets rejected
assert handler.get_secure_cookie('foo') is None
+ def test_arbitrary_bytes(self):
+ # Secure cookies accept arbitrary data (which is base64 encoded).
+ # Note that normal cookies accept only a subset of ascii.
+ handler = CookieTestRequestHandler()
+ handler.set_secure_cookie('foo', b('\xe9'))
+ self.assertEqual(handler.get_secure_cookie('foo'), b('\xe9'))
+
class CookieTest(AsyncHTTPTestCase, LogTrapTestCase):
def get_app(self):
@@ -294,6 +301,12 @@ def prepare(self):
self.check_type('cookie_key', self.cookies.keys()[0], str)
self.check_type('cookie_value', self.cookies.values()[0].value, str)
+ # Secure cookies return bytes because they can contain arbitrary
+ # data, but regular cookies are native strings.
+ assert self.cookies.keys() == ['asdf']
+ self.check_type('get_secure_cookie', self.get_secure_cookie('asdf'), bytes_type)
+ self.check_type('get_cookie', self.get_cookie('asdf'), str)
+
self.check_type('xsrf_token', self.xsrf_token, bytes_type)
self.check_type('xsrf_form_html', self.xsrf_form_html(), str)
@@ -413,6 +426,7 @@ def get(self):
class WebTest(AsyncHTTPTestCase, LogTrapTestCase):
+ COOKIE_SECRET = "WebTest.COOKIE_SECRET"
def get_app(self):
loader = DictLoader({
"linkify.html": "{% module linkify(message) %}",
@@ -441,21 +455,24 @@ def get_app(self):
]
return Application(urls,
template_loader=loader,
- autoescape="xhtml_escape")
+ autoescape="xhtml_escape",
+ cookie_secret=self.COOKIE_SECRET)
def fetch_json(self, *args, **kwargs):
response = self.fetch(*args, **kwargs)
response.rethrow()
return json_decode(response.body)
def test_types(self):
+ cookie_value = to_unicode(create_signed_value(self.COOKIE_SECRET,
+ "asdf", "qwer"))
response = self.fetch("/typecheck/asdf?foo=bar",
- headers={"Cookie": "cook=ie"})
+ headers={"Cookie": "asdf=" + cookie_value})
data = json_decode(response.body)
self.assertEqual(data, {})
response = self.fetch("/typecheck/asdf?foo=bar", method="POST",
- headers={"Cookie": "cook=ie"},
+ headers={"Cookie": "asdf=" + cookie_value},
body="foo=bar")
def test_decode_argument(self):
View
9 tornado/web.py
@@ -408,6 +408,9 @@ def set_secure_cookie(self, name, value, expires_days=30, **kwargs):
Note that the ``expires_days`` parameter sets the lifetime of the
cookie in the browser, but is independent of the ``max_age_days``
parameter to `get_secure_cookie`.
+
+ Secure cookies may contain arbitrary byte values, not just unicode
+ strings (unlike regular cookies)
"""
self.set_cookie(name, self.create_signed_value(name, value),
expires_days=expires_days, **kwargs)
@@ -424,7 +427,11 @@ def create_signed_value(self, name, value):
name, value)
def get_secure_cookie(self, name, value=None, max_age_days=31):
- """Returns the given signed cookie if it validates, or None."""
+ """Returns the given signed cookie if it validates, or None.
+
+ The decoded cookie value is returned as a byte string (unlike
+ `get_cookie`).
+ """
self.require_setting("cookie_secret", "secure cookies")
if value is None:
value = self.get_cookie(name)

0 comments on commit f5012b4

Please sign in to comment.