From d3e8181c6eb192bb3d83a5483f57ca936c726911 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Fri, 21 Apr 2023 15:30:07 -0400 Subject: [PATCH] web: Restore case-insensitivity of set_cookie args This was an unintended feature that got broken in #3224. Bring it back for now but deprecate it for future cleanup. Fixes #3252 --- docs/releases.rst | 1 + docs/releases/v6.3.1.rst | 12 ++++++++++++ tornado/test/web_test.py | 13 +++++++++++++ tornado/web.py | 17 +++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 docs/releases/v6.3.1.rst diff --git a/docs/releases.rst b/docs/releases.rst index 9cfe0666a2..dd53b12ffe 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -4,6 +4,7 @@ Release notes .. toctree:: :maxdepth: 2 + releases/v6.3.1 releases/v6.3.0 releases/v6.2.0 releases/v6.1.0 diff --git a/docs/releases/v6.3.1.rst b/docs/releases/v6.3.1.rst new file mode 100644 index 0000000000..11886d0079 --- /dev/null +++ b/docs/releases/v6.3.1.rst @@ -0,0 +1,12 @@ +What's new in Tornado 6.3.1 +=========================== + +Apr 21, 2023 +------------ + +``tornado.web`` +~~~~~~~~~~~~~~~ + +- `.RequestHandler.set_cookie` once again accepts capitalized keyword arguments + for backwards compatibility. This is deprecated and in Tornado 7.0 only lowercase + arguments will be accepted. \ No newline at end of file diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index 4b25456865..c2d057c538 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -17,6 +17,7 @@ from tornado.simple_httpclient import SimpleAsyncHTTPClient from tornado.template import DictLoader from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, ExpectLog, gen_test +from tornado.test.util import ignore_deprecation from tornado.util import ObjectDict, unicode_type from tornado.web import ( Application, @@ -318,6 +319,11 @@ def get(self): self.set_cookie("c", "1", httponly=True) self.set_cookie("d", "1", httponly=False) + class SetCookieDeprecatedArgs(RequestHandler): + def get(self): + # Mixed case is supported, but deprecated + self.set_cookie("a", "b", HttpOnly=True, pATH="/foo") + return [ ("/set", SetCookieHandler), ("/get", GetCookieHandler), @@ -327,6 +333,7 @@ def get(self): ("/set_max_age", SetCookieMaxAgeHandler), ("/set_expires_days", SetCookieExpiresDaysHandler), ("/set_falsy_flags", SetCookieFalsyFlags), + ("/set_deprecated", SetCookieDeprecatedArgs), ] def test_set_cookie(self): @@ -413,6 +420,12 @@ def test_set_cookie_false_flags(self): self.assertEqual(headers[2].lower(), "c=1; httponly; path=/") self.assertEqual(headers[3].lower(), "d=1; path=/") + def test_set_cookie_deprecated(self): + with ignore_deprecation(): + response = self.fetch("/set_deprecated") + header = response.headers.get("Set-Cookie") + self.assertEqual(header, "a=b; HttpOnly; Path=/foo") + class AuthRedirectRequestHandler(RequestHandler): def initialize(self, login_url): diff --git a/tornado/web.py b/tornado/web.py index 0a4f409b88..3b676e3c25 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -79,6 +79,7 @@ async def main(): import sys import threading import time +import warnings import tornado import traceback import types @@ -607,6 +608,7 @@ def set_cookie( httponly: bool = False, secure: bool = False, samesite: Optional[str] = None, + **kwargs: Any, ) -> None: """Sets an outgoing cookie name/value with the given options. @@ -623,6 +625,10 @@ def set_cookie( to set an expiration time in days from today (if both are set, ``expires`` is used). + .. deprecated:: 6.3 + Keyword arguments are currently accepted case-insensitively. + In Tornado 7.0 this will be changed to only accept lowercase + arguments. """ # The cookie library only accepts type str, in both python 2 and 3 name = escape.native_str(name) @@ -657,6 +663,17 @@ def set_cookie( morsel["secure"] = True if samesite: morsel["samesite"] = samesite + if kwargs: + # The setitem interface is case-insensitive, so continue to support + # kwargs for backwards compatibility until we can remove deprecated + # features. + for k, v in kwargs.items(): + morsel[k] = v + warnings.warn( + f"Deprecated arguments to set_cookie: {set(kwargs.keys())} " + "(should be lowercase)", + DeprecationWarning, + ) def clear_cookie(self, name: str, **kwargs: Any) -> None: """Deletes the cookie with the given name.