From 296e6430c505e369ecf620404b28287395369385 Mon Sep 17 00:00:00 2001 From: Jan-Philip Gehrcke Date: Tue, 26 Apr 2016 18:24:26 +0200 Subject: [PATCH] feat(set_cookie): Convert max-age value to int implicitly (#711) Make set_cookie() more robust in handling max-age values, without introducing significant overhead. Fixes #704 --- AUTHORS | 1 + falcon/response.py | 11 +++++++++-- tests/test_cookies.py | 20 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 8e80a86..a868bc2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -60,6 +60,7 @@ below in order of date of first contribution: * Yohan Boniface (yohanboniface) * Steven Colby (StevenWColby) * M Somerville (dracos) +* Jan-Philip Gehrcke (jgehrcke) * Abhilash Raj (maxking) * David Larlet (davidbgk) * Fran Fitzpatrick (fxfitz) diff --git a/falcon/response.py b/falcon/response.py index dd34f70..ddeeebb 100644 --- a/falcon/response.py +++ b/falcon/response.py @@ -154,7 +154,8 @@ class Response(object): default, cookies expire when the user agent exits. max_age (int): Defines the lifetime of the cookie in seconds. After the specified number of seconds elapse, the client - should discard the cookie. + should discard the cookie. Coercion to `int` is attempted + if provided with `float` or `str`. domain (str): Specifies the domain for which the cookie is valid. An explicitly specified domain must always start with a dot. A value of 0 means the cookie should be discarded immediately. @@ -218,7 +219,13 @@ class Response(object): self._cookies[name]['expires'] = gmt_expires.strftime(fmt) if max_age: - self._cookies[name]['max-age'] = max_age + # RFC 6265 section 5.2.2 says about the max-age value: + # "If the remainder of attribute-value contains a non-DIGIT + # character, ignore the cookie-av." + # That is, RFC-compliant response parsers will ignore the max-age + # attribute if the value contains a dot, as in floating point + # numbers. Therefore, attempt to convert the value to an integer. + self._cookies[name]['max-age'] = int(max_age) if domain: self._cookies[name]['domain'] = domain diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 3d0dac7..8bc487d 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -50,6 +50,15 @@ class CookieResource: resp.unset_cookie('bad') +class CookieResourceMaxAgeFloatString: + + def on_get(self, req, resp): + resp.set_cookie( + 'foofloat', 'bar', max_age=15.3, secure=False, http_only=False) + resp.set_cookie( + 'foostring', 'bar', max_age='15', secure=False, http_only=False) + + @ddt.ddt class TestCookies(testing.TestBase): @@ -113,6 +122,17 @@ class TestCookies(testing.TestBase): self.assertEqual(morsel.value, 'bar') self.assertEqual(morsel['max-age'], 300) + def test_cookie_max_age_float_and_string(self): + # Falcon implicitly converts max-age values to integers, + # for ensuring RFC 6265-compliance of the attribute value. + self.resource = CookieResourceMaxAgeFloatString() + self.api.add_route(self.test_route, self.resource) + self.simulate_request(self.test_route, method='GET') + self.assertIn( + ('set-cookie', 'foofloat=bar; Max-Age=15'), self.srmock.headers) + self.assertIn( + ('set-cookie', 'foostring=bar; Max-Age=15'), self.srmock.headers) + def test_response_unset_cookie(self): resp = falcon.Response() resp.unset_cookie('bad')