diff --git a/falcon/response.py b/falcon/response.py index 7cdbc71..6c8223c 100644 --- a/falcon/response.py +++ b/falcon/response.py @@ -251,9 +251,24 @@ class Response(object): self._cookies[name]["httponly"] = http_only def unset_cookie(self, name): - """Unset a cookie in the response.""" - if self._cookies is not None and name in self._cookies: - del self._cookies[name] + """Unset a cookie in the response + + Note: + This will clear the contents of the cookie, and instruct + the browser to immediately expire its own copy of the + cookie, if any. + """ + if self._cookies is None: + self._cookies = SimpleCookie() + + self._cookies[name] = "" + + # NOTE(Freezerburn): SimpleCookie apparently special cases the + # expires attribute to automatically use strftime and set the + # time as a delta from the current time. We use -1 here to + # basically tell the browser to immediately expire the cookie, + # thus removing it from future request objects. + self._cookies[name]["expires"] = -1 def get_header(self, name): """Retrieve the raw string value for the given header. diff --git a/falcon/util/misc.py b/falcon/util/misc.py index 00db982..7c39661 100644 --- a/falcon/util/misc.py +++ b/falcon/util/misc.py @@ -128,6 +128,7 @@ def http_date_to_dt(http_date, obs_date=False): time_formats = ( '%a, %d %b %Y %H:%M:%S %Z', + '%a, %d-%b-%Y %H:%M:%S %Z', '%A, %d-%b-%y %H:%M:%S %Z', '%a %b %d %H:%M:%S %Y', ) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index fe0fa8c..07408c2 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -1,11 +1,13 @@ +import re import sys -import falcon -import falcon.testing as testing - -from falcon.util import TimezoneGMT from datetime import datetime, timedelta, tzinfo from six.moves.http_cookies import Morsel +from testtools.matchers import LessThan + +import falcon +import falcon.testing as testing +from falcon.util import TimezoneGMT, http_date_to_dt class TimezoneGMTPlus1(tzinfo): @@ -109,12 +111,23 @@ class TestCookies(testing.TestBase): def test_response_unset_cookie(self): resp = falcon.Response() resp.unset_cookie("bad") - resp.set_cookie("bad", "cookie", max_age=301) + resp.set_cookie("bad", "cookie", max_age=300) resp.unset_cookie("bad") morsels = list(resp._cookies.values()) + self.assertEqual(len(morsels), 1) - self.assertEqual(len(morsels), 0) + bad_cookie = morsels[0] + self.assertEqual(bad_cookie['expires'], -1) + + output = bad_cookie.OutputString() + self.assertTrue('bad=;' in output or 'bad="";' in output) + + match = re.search('expires=([^;]+)', output) + self.assertIsNotNone(match) + + expiration = http_date_to_dt(match.group(1), obs_date=True) + self.assertThat(expiration, LessThan(datetime.utcnow())) def test_cookie_timezone(self): tz = TimezoneGMT() diff --git a/tests/test_utils.py b/tests/test_utils.py index 5507361..b082c38 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -79,6 +79,15 @@ class TestFalconUtils(testtools.TestCase): falcon.http_date_to_dt('Thu, 04 Apr 2013 10:28:54 GMT'), datetime(2013, 4, 4, 10, 28, 54)) + self.assertRaises( + ValueError, + falcon.http_date_to_dt, 'Thu, 04-Apr-2013 10:28:54 GMT') + + self.assertEqual( + falcon.http_date_to_dt('Thu, 04-Apr-2013 10:28:54 GMT', + obs_date=True), + datetime(2013, 4, 4, 10, 28, 54)) + self.assertRaises( ValueError, falcon.http_date_to_dt, 'Sun Nov 6 08:49:37 1994')