From a8113e64026e6488feb8af51a0360c69a5e1aef3 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 11 Jul 2015 09:46:23 -0400 Subject: [PATCH] fix(Response): Instruct browser to remove cookies Modify the Response object to actually set the expires token for the cookie that is being unset to a point in the past, causing the browser to immediately remove the now-expired cookie, which will remove the cookie from future Request objects. The previous behavior of Response's unset_cookie function was just to delete the given name from the SimpleCookie object if it existed. However, this causes a problem in that the browser is not told to remove the cookie on its side, causing all future Request objects to still contain the cookie that was supposed to have been unset/removed. Co-Authored-By: Kurt Griffiths --- falcon/response.py | 21 ++++++++++++++++++--- falcon/util/misc.py | 1 + tests/test_cookies.py | 25 +++++++++++++++++++------ tests/test_utils.py | 9 +++++++++ 4 files changed, 47 insertions(+), 9 deletions(-) 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')