Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-tornado6 for openSUSE:Factory 
checked in at 2025-12-20 21:45:03
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-tornado6 (Old)
 and      /work/SRC/openSUSE:Factory/.python-tornado6.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-tornado6"

Sat Dec 20 21:45:03 2025 rev:21 rq:1323582 version:6.5.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-tornado6/python-tornado6.changes  
2025-05-23 14:27:30.792860059 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-tornado6.new.1928/python-tornado6.changes    
    2025-12-20 21:45:08.635851013 +0100
@@ -1,0 +2,45 @@
+Tue Dec 16 13:42:10 UTC 2025 - Nico Krapp <[email protected]>
+
+- Update to 6.5.4
+  * The in operator for HTTPHeaders was incorrectly case-sensitive, causing
+    lookups to fail for headers with different casing than the original header
+    name. This was a regression in version 6.5.3 and has been fixed to restore
+    the intended case-insensitive behavior from version 6.5.2 and earlier.
+- Update to 6.5.3 (bsc#1254903, bsc#1254905, bsc#1254904)
+  * Fixed a denial-of-service vulnerability involving quadratic computation
+    when parsing multipart/form-data request bodies. CVE-2025-67726
+    Thanks to Finder16 for reporting this issue.
+  * Fixed a denial-of-service vulnerability involving quadratic computation 
when
+    parsing repeated HTTP headers. CVE-2025-67725.
+    Thanks to Finder16 for reporting this issue.
+  * Fixed a header injection and XSS vulnerability involving the reason 
argument
+    to .RequestHandler.set_status and tornado.web.HTTPError. CVE-2025-67724.
+    Thanks to Finder16 and Cheshire1225 for reporting this issue.
+  * Several demo applications bundled with the Tornado repo (blog, chat,
+    facebook) had an open redirect vulnerability which has been fixed. This is
+    not covered by a CVE or security advisory since the demo applications are
+    not included as a part of the Tornado package when installed, but 
developers
+    who have copied code from these demos may which to review their own
+    applications for open redirects.
+    Thanks to J1vvoo for reporting this issue.
+  * he s3server demo application contained some path traversal vulnerabilities.
+    Since this demo application was not demonstrating any interesting aspects 
of
+    Tornado, it has been deleted rather than being fixed.
+    Thanks to J1vvoo for reporting this issue.
+- Update to 6.5.2
+  * Fixed a bug that resulted in WebSocket pings not being sent at the
+    configured interval.
+  * Improved logging for invalid Host headers. This was previously logged as an
+    uncaught exception with a stack trace, now it is simply a 400 response
+    (logged as a warning in the access log).
+  * Restored the host argument to .HTTPServerRequest. This argument is
+    deprecated and will be removed in the future, but its removal with no
+    warning in 6.5.0 was a mistake.
+  * Removed a debugging print statement that was left in the code.
+  * Improved type hints for gen.multi.
+- Update to 6.5.1
+  * Fixed a bug in multipart/form-data parsing that could incorrectly reject
+    filenames containing characters above U+00FF (i.e. most characters outside
+    the Latin alphabet).
+
+-------------------------------------------------------------------

Old:
----
  tornado-6.5.tar.gz

New:
----
  tornado-6.5.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-tornado6.spec ++++++
--- /var/tmp/diff_new_pack.DTQR1G/_old  2025-12-20 21:45:10.427925160 +0100
+++ /var/tmp/diff_new_pack.DTQR1G/_new  2025-12-20 21:45:10.439925657 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-tornado6
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2025 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-tornado6
-Version:        6.5
+Version:        6.5.4
 Release:        0
 Summary:        Open source version of scalable, non-blocking web server that 
power FriendFeed
 License:        Apache-2.0

++++++ tornado-6.5.tar.gz -> tornado-6.5.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/PKG-INFO new/tornado-6.5.4/PKG-INFO
--- old/tornado-6.5/PKG-INFO    2025-05-15 22:19:11.890474600 +0200
+++ new/tornado-6.5.4/PKG-INFO  2025-12-15 19:43:01.912789000 +0100
@@ -1,13 +1,12 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: tornado
-Version: 6.5
+Version: 6.5.4
 Summary: Tornado is a Python web framework and asynchronous networking 
library, originally developed at FriendFeed.
 Home-page: http://www.tornadoweb.org/
 Author: Facebook
 Author-email: [email protected]
 License: Apache-2.0
 Project-URL: Source, https://github.com/tornadoweb/tornado
-Platform: UNKNOWN
 Classifier: License :: OSI Approved :: Apache Software License
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.9
@@ -20,6 +19,17 @@
 Requires-Python: >= 3.9
 Description-Content-Type: text/x-rst
 License-File: LICENSE
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: description-content-type
+Dynamic: home-page
+Dynamic: license
+Dynamic: license-file
+Dynamic: project-url
+Dynamic: requires-python
+Dynamic: summary
 
 Tornado Web Server
 ==================
@@ -72,5 +82,3 @@
 
 Documentation and links to additional resources are available at
 https://www.tornadoweb.org
-
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/docs/caresresolver.rst 
new/tornado-6.5.4/docs/caresresolver.rst
--- old/tornado-6.5/docs/caresresolver.rst      2025-05-15 22:19:08.000000000 
+0200
+++ new/tornado-6.5.4/docs/caresresolver.rst    2025-12-15 19:42:58.000000000 
+0100
@@ -19,6 +19,9 @@
     the default for ``tornado.simple_httpclient``, but other libraries
     may default to ``AF_UNSPEC``.
 
+    This class requires ``pycares`` version 4. Since this class is deprecated, 
it will not be
+    updated to support ``pycares`` version 5.
+
     .. deprecated:: 6.2
        This class is deprecated and will be removed in Tornado 7.0. Use the 
default
        thread-based resolver instead.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/docs/releases/v6.5.1.rst 
new/tornado-6.5.4/docs/releases/v6.5.1.rst
--- old/tornado-6.5/docs/releases/v6.5.1.rst    1970-01-01 01:00:00.000000000 
+0100
+++ new/tornado-6.5.4/docs/releases/v6.5.1.rst  2025-12-15 19:42:58.000000000 
+0100
@@ -0,0 +1,11 @@
+What's new in Tornado 6.5.1
+===========================
+
+May 22, 2025
+------------
+
+Bug fixes
+~~~~~~~~~
+
+- Fixed a bug in ``multipart/form-data`` parsing that could incorrectly reject 
filenames containing
+  characters above U+00FF (i.e. most characters outside the Latin alphabet).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/docs/releases/v6.5.2.rst 
new/tornado-6.5.4/docs/releases/v6.5.2.rst
--- old/tornado-6.5/docs/releases/v6.5.2.rst    1970-01-01 01:00:00.000000000 
+0100
+++ new/tornado-6.5.4/docs/releases/v6.5.2.rst  2025-12-15 19:42:58.000000000 
+0100
@@ -0,0 +1,17 @@
+What's new in Tornado 6.5.2
+===========================
+
+Aug 8, 2025
+-----------
+
+Bug fixes
+~~~~~~~~~
+
+- Fixed a bug that resulted in WebSocket pings not being sent at the 
configured interval.
+- Improved logging for invalid ``Host`` headers. This was previously logged as 
an uncaught
+  exception with a stack trace, now it is simply a 400 response (logged as a 
warning in the
+  access log).
+- Restored the ``host`` argument to `.HTTPServerRequest`. This argument is 
deprecated
+  and will be removed in the future, but its removal with no warning in 6.5.0 
was a mistake.
+- Removed a debugging print statement that was left in the code.
+- Improved type hints for ``gen.multi``.
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/docs/releases/v6.5.3.rst 
new/tornado-6.5.4/docs/releases/v6.5.3.rst
--- old/tornado-6.5/docs/releases/v6.5.3.rst    1970-01-01 01:00:00.000000000 
+0100
+++ new/tornado-6.5.4/docs/releases/v6.5.3.rst  2025-12-15 19:42:58.000000000 
+0100
@@ -0,0 +1,33 @@
+What's new in Tornado 6.5.3
+===========================
+
+Dec 10, 2025
+------------
+
+Security fixes
+~~~~~~~~~~~~~~
+- Fixed a denial-of-service vulnerability involving quadratic computation when 
parsing
+  ``multipart/form-data`` request bodies.
+  `CVE-2025-67726 
<https://github.com/tornadoweb/tornado/security/advisories/GHSA-jhmp-mqwm-3gq8>`_
+  Thanks to `Finder16 <https://github.com/Finder16>`_ for reporting this issue.
+- Fixed a denial-of-service vulnerability involving quadratic computation when 
parsing repeated HTTP
+  headers.
+  `CVE-2025-67725 
<https://github.com/tornadoweb/tornado/security/advisories/GHSA-c98p-7wgm-6p64>`_.
+  Thanks to `Finder16 <https://github.com/Finder16>`_ for reporting this issue.
+- Fixed a header injection and XSS vulnerability involving the ``reason`` 
argument to
+  `.RequestHandler.set_status` and `tornado.web.HTTPError`.
+  `CVE-2025-67724 
<https://github.com/tornadoweb/tornado/security/advisories/GHSA-pr2v-jx2c-wg9f>`_.
+  Thanks to `Finder16 <https://github.com/Finder16>`_ and
+  `Cheshire1225 <https://github.com/Cheshire1225>`_ for reporting this issue.
+
+Demo changes
+~~~~~~~~~~~~
+- Several demo applications bundled with the Tornado repo (``blog``, ``chat``, 
``facebook``) had an
+  open redirect vulnerability which has been fixed. This is not covered by a 
CVE or security
+  advisory since the demo applications are not included as a part of the 
Tornado package when
+  installed, but developers who have copied code from these demos may which to 
review their own
+  applications for open redirects. Thanks to `J1vvoo 
<https://github.com/J1vvoo>`_ for reporting this
+  issue.
+- The ``s3server`` demo application contained some path traversal 
vulnerabilities. Since this demo
+  application was not demonstrating any interesting aspects of Tornado, it has 
been deleted rather
+  than being fixed. Thanks to `J1vvoo <https://github.com/J1vvoo>`_ for 
reporting this issue.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/docs/releases/v6.5.4.rst 
new/tornado-6.5.4/docs/releases/v6.5.4.rst
--- old/tornado-6.5/docs/releases/v6.5.4.rst    1970-01-01 01:00:00.000000000 
+0100
+++ new/tornado-6.5.4/docs/releases/v6.5.4.rst  2025-12-15 19:42:58.000000000 
+0100
@@ -0,0 +1,13 @@
+What's new in Tornado 6.5.4
+===========================
+
+Dec 15, 2025
+------------
+
+Bug fixes
+~~~~~~~~~
+
+- The ``in`` operator for ``HTTPHeaders`` was incorrectly case-sensitive, 
causing
+  lookups to fail for headers with different casing than the original header 
name.
+  This was a regression in version 6.5.3 and has been fixed to restore the 
intended
+  case-insensitive behavior from version 6.5.2 and earlier.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/docs/releases.rst 
new/tornado-6.5.4/docs/releases.rst
--- old/tornado-6.5/docs/releases.rst   2025-05-15 22:19:08.000000000 +0200
+++ new/tornado-6.5.4/docs/releases.rst 2025-12-15 19:42:58.000000000 +0100
@@ -4,6 +4,10 @@
 .. toctree::
    :maxdepth: 2
 
+   releases/v6.5.4
+   releases/v6.5.3
+   releases/v6.5.2
+   releases/v6.5.1
    releases/v6.5.0
    releases/v6.4.2
    releases/v6.4.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/docs/web.rst 
new/tornado-6.5.4/docs/web.rst
--- old/tornado-6.5/docs/web.rst        2025-05-15 22:19:08.000000000 +0200
+++ new/tornado-6.5.4/docs/web.rst      2025-12-15 19:42:58.000000000 +0100
@@ -224,14 +224,22 @@
            of `UIModule` or UI methods to be made available to templates.
            May be set to a module, dictionary, or a list of modules
            and/or dicts.  See :ref:`ui-modules` for more details.
-         * ``websocket_ping_interval``: If set to a number, all websockets will
-           be pinged every n seconds. This can help keep the connection alive
-           through certain proxy servers which close idle connections, and it
-           can detect if the websocket has failed without being properly 
closed.
-         * ``websocket_ping_timeout``: If the ping interval is set, and the
-           server doesn't receive a 'pong' in this many seconds, it will close
-           the websocket. The default is three times the ping interval, with a
-           minimum of 30 seconds. Ignored if the ping interval is not set.
+         * ``websocket_ping_interval``: If the ping interval has a non-zero
+           value, a ping will be sent periodically every
+           ``websocket_ping_interval`` seconds, and the connection will be
+           closed if a response is not received before the
+           ``websocket_ping_timeout``.
+           This can help keep the connection alive through certain proxy
+           servers which close idle connections, and it can detect if the
+           websocket has failed without being properly closed.
+         * ``websocket_ping_timeout``: For use with 
``websocket_ping_interval``,
+           if the server does not receive a pong within this many seconds, it
+           will close the websocket_ping_timeout.
+           The default timeout is equal to the ping interval. The ping timeout
+           will be turned off if the ping interval is not set or if the
+           timeout is set to ``0``.
+           This can help to detect disconnected clients to avoid keeping
+           inactive connections open.
 
          Authentication and security settings:
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/__init__.py 
new/tornado-6.5.4/tornado/__init__.py
--- old/tornado-6.5/tornado/__init__.py 2025-05-15 22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tornado/__init__.py       2025-12-15 19:42:59.000000000 
+0100
@@ -22,8 +22,8 @@
 # is zero for an official release, positive for a development branch,
 # or negative for a release candidate or beta (after the base version
 # number has been incremented)
-version = "6.5"
-version_info = (6, 5, 0, 0)
+version = "6.5.4"
+version_info = (6, 5, 4, 0)
 
 import importlib
 import typing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/__init__.pyi 
new/tornado-6.5.4/tornado/__init__.pyi
--- old/tornado-6.5/tornado/__init__.pyi        1970-01-01 01:00:00.000000000 
+0100
+++ new/tornado-6.5.4/tornado/__init__.pyi      2025-12-15 19:42:59.000000000 
+0100
@@ -0,0 +1,33 @@
+import typing
+
+version: str
+version_info: typing.Tuple[int, int, int, int]
+
+from . import auth
+from . import autoreload
+from . import concurrent
+from . import curl_httpclient
+from . import escape
+from . import gen
+from . import http1connection
+from . import httpclient
+from . import httpserver
+from . import httputil
+from . import ioloop
+from . import iostream
+from . import locale
+from . import locks
+from . import log
+from . import netutil
+from . import options
+from . import platform
+from . import process
+from . import queues
+from . import routing
+from . import simple_httpclient
+from . import tcpclient
+from . import tcpserver
+from . import template
+from . import testing
+from . import util
+from . import web
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/gen.py 
new/tornado-6.5.4/tornado/gen.py
--- old/tornado-6.5/tornado/gen.py      2025-05-15 22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tornado/gen.py    2025-12-15 19:42:59.000000000 +0100
@@ -437,6 +437,20 @@
         return self.next()
 
 
+@overload
+def multi(
+    children: Sequence[_Yieldable],
+    quiet_exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]] = (),
+) -> Future[List]: ...
+
+
+@overload
+def multi(
+    children: Mapping[Any, _Yieldable],
+    quiet_exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]] = (),
+) -> Future[Dict]: ...
+
+
 def multi(
     children: Union[Sequence[_Yieldable], Mapping[Any, _Yieldable]],
     quiet_exceptions: "Union[Type[Exception], Tuple[Type[Exception], ...]]" = 
(),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/http1connection.py 
new/tornado-6.5.4/tornado/http1connection.py
--- old/tornado-6.5/tornado/http1connection.py  2025-05-15 22:19:08.000000000 
+0200
+++ new/tornado-6.5.4/tornado/http1connection.py        2025-12-15 
19:42:59.000000000 +0100
@@ -66,6 +66,9 @@
     ) -> None:
         if value is not None:
             assert typ is not None
+            # Let HTTPInputError pass through to higher-level handler
+            if isinstance(value, httputil.HTTPInputError):
+                return None
             self.logger.error("Uncaught exception", exc_info=(typ, value, tb))
             raise _QuietException
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/httputil.py 
new/tornado-6.5.4/tornado/httputil.py
--- old/tornado-6.5/tornado/httputil.py 2025-05-15 22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tornado/httputil.py       2025-12-15 19:42:59.000000000 
+0100
@@ -70,6 +70,10 @@
 # To be used with str.strip() and related methods.
 HTTP_WHITESPACE = " \t"
 
+# Roughly the inverse of RequestHandler._VALID_HEADER_CHARS, but permits
+# chars greater than \xFF (which may appear after decoding utf8).
+_FORBIDDEN_HEADER_CHARS_RE = re.compile(r"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]")
+
 
 class _ABNF:
     """Class that holds a subset of ABNF rules from RFC 9110 and friends.
@@ -183,8 +187,14 @@
         pass
 
     def __init__(self, *args: typing.Any, **kwargs: str) -> None:  # noqa: F811
-        self._dict = {}  # type: typing.Dict[str, str]
-        self._as_list = {}  # type: typing.Dict[str, typing.List[str]]
+        # Formally, HTTP headers are a mapping from a field name to a 
"combined field value",
+        # which may be constructed from multiple field lines by joining them 
with commas.
+        # In practice, however, some headers (notably Set-Cookie) do not 
follow this convention,
+        # so we maintain a mapping from field name to a list of field lines in 
self._as_list.
+        # self._combined_cache is a cache of the combined field values derived 
from self._as_list
+        # on demand (and cleared whenever the list is modified).
+        self._as_list: dict[str, list[str]] = {}
+        self._combined_cache: dict[str, str] = {}
         self._last_key = None  # type: Optional[str]
         if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], 
HTTPHeaders):
             # Copy constructor
@@ -196,20 +206,22 @@
 
     # new public methods
 
-    def add(self, name: str, value: str) -> None:
+    def add(self, name: str, value: str, *, _chars_are_bytes: bool = True) -> 
None:
         """Adds a new value for the given key."""
         if not _ABNF.field_name.fullmatch(name):
             raise HTTPInputError("Invalid header name %r" % name)
-        if not _ABNF.field_value.fullmatch(to_unicode(value)):
-            # TODO: the fact we still support bytes here (contrary to type 
annotations)
-            # and still test for it should probably be changed.
-            raise HTTPInputError("Invalid header value %r" % value)
+        if _chars_are_bytes:
+            if not _ABNF.field_value.fullmatch(to_unicode(value)):
+                # TODO: the fact we still support bytes here (contrary to type 
annotations)
+                # and still test for it should probably be changed.
+                raise HTTPInputError("Invalid header value %r" % value)
+        else:
+            if _FORBIDDEN_HEADER_CHARS_RE.search(value):
+                raise HTTPInputError("Invalid header value %r" % value)
         norm_name = _normalize_header(name)
         self._last_key = norm_name
         if norm_name in self:
-            self._dict[norm_name] = (
-                native_str(self[norm_name]) + "," + native_str(value)
-            )
+            self._combined_cache.pop(norm_name, None)
             self._as_list[norm_name].append(value)
         else:
             self[norm_name] = value
@@ -229,7 +241,7 @@
             for value in values:
                 yield (name, value)
 
-    def parse_line(self, line: str) -> None:
+    def parse_line(self, line: str, *, _chars_are_bytes: bool = True) -> None:
         r"""Updates the dictionary with a single header line.
 
         >>> h = HTTPHeaders()
@@ -263,19 +275,25 @@
             if self._last_key is None:
                 raise HTTPInputError("first header line cannot start with 
whitespace")
             new_part = " " + line.strip(HTTP_WHITESPACE)
-            if not _ABNF.field_value.fullmatch(new_part[1:]):
-                raise HTTPInputError("Invalid header continuation %r" % 
new_part)
+            if _chars_are_bytes:
+                if not _ABNF.field_value.fullmatch(new_part[1:]):
+                    raise HTTPInputError("Invalid header continuation %r" % 
new_part)
+            else:
+                if _FORBIDDEN_HEADER_CHARS_RE.search(new_part):
+                    raise HTTPInputError("Invalid header value %r" % new_part)
             self._as_list[self._last_key][-1] += new_part
-            self._dict[self._last_key] += new_part
+            self._combined_cache.pop(self._last_key, None)
         else:
             try:
                 name, value = line.split(":", 1)
             except ValueError:
                 raise HTTPInputError("no colon in header line")
-            self.add(name, value.strip(HTTP_WHITESPACE))
+            self.add(
+                name, value.strip(HTTP_WHITESPACE), 
_chars_are_bytes=_chars_are_bytes
+            )
 
     @classmethod
-    def parse(cls, headers: str) -> "HTTPHeaders":
+    def parse(cls, headers: str, *, _chars_are_bytes: bool = True) -> 
"HTTPHeaders":
         """Returns a dictionary from HTTP header text.
 
         >>> h = HTTPHeaders.parse("Content-Type: 
text/html\\r\\nContent-Length: 42\\r\\n")
@@ -288,39 +306,64 @@
            mix of `KeyError`, and `ValueError`.
 
         """
+        # _chars_are_bytes is a hack. This method is used in two places, HTTP 
headers (in which
+        # non-ascii characters are to be interpreted as latin-1) and 
multipart/form-data (in which
+        # they are to be interpreted as utf-8). For historical reasons, this 
method handled this by
+        # expecting both callers to decode the headers to strings before 
parsing them. This wasn't a
+        # problem until we started doing stricter validation of the characters 
allowed in HTTP
+        # headers (using ABNF rules defined in terms of byte values), which 
inadvertently started
+        # disallowing non-latin1 characters in multipart/form-data filenames.
+        #
+        # This method should have accepted bytes and a desired encoding, but 
this change is being
+        # introduced in a patch release that shouldn't change the API. 
Instead, the _chars_are_bytes
+        # flag decides whether to use HTTP-style ABNF validation (treating the 
string as bytes
+        # smuggled through the latin1 encoding) or to accept any non-control 
unicode characters
+        # as required by multipart/form-data. This method will change to 
accept bytes in a future
+        # release.
         h = cls()
 
         start = 0
         while True:
             lf = headers.find("\n", start)
             if lf == -1:
-                h.parse_line(headers[start:])
+                h.parse_line(headers[start:], 
_chars_are_bytes=_chars_are_bytes)
                 break
             line = headers[start : lf + 1]
             start = lf + 1
-            h.parse_line(line)
+            h.parse_line(line, _chars_are_bytes=_chars_are_bytes)
         return h
 
     # MutableMapping abstract method implementations.
 
     def __setitem__(self, name: str, value: str) -> None:
         norm_name = _normalize_header(name)
-        self._dict[norm_name] = value
+        self._combined_cache[norm_name] = value
         self._as_list[norm_name] = [value]
 
+    def __contains__(self, name: object) -> bool:
+        # This is an important optimization to avoid the expensive 
concatenation
+        # in __getitem__ when it's not needed.
+        if not isinstance(name, str):
+            return False
+        norm_name = _normalize_header(name)
+        return norm_name in self._as_list
+
     def __getitem__(self, name: str) -> str:
-        return self._dict[_normalize_header(name)]
+        header = _normalize_header(name)
+        if header not in self._combined_cache:
+            self._combined_cache[header] = ",".join(self._as_list[header])
+        return self._combined_cache[header]
 
     def __delitem__(self, name: str) -> None:
         norm_name = _normalize_header(name)
-        del self._dict[norm_name]
+        del self._combined_cache[norm_name]
         del self._as_list[norm_name]
 
     def __len__(self) -> int:
-        return len(self._dict)
+        return len(self._as_list)
 
     def __iter__(self) -> Iterator[typing.Any]:
-        return iter(self._dict)
+        return iter(self._as_list)
 
     def copy(self) -> "HTTPHeaders":
         # defined in dict but not in MutableMapping.
@@ -431,6 +474,11 @@
 
     .. versionchanged:: 4.0
        Moved from ``tornado.httpserver.HTTPRequest``.
+
+    .. deprecated:: 6.5.2
+       The ``host`` argument to the ``HTTPServerRequest`` constructor is 
deprecated. Use
+       ``headers["Host"]`` instead. This argument was mistakenly removed in 
Tornado 6.5.0 and
+       temporarily restored in 6.5.2.
     """
 
     path = None  # type: str
@@ -446,7 +494,7 @@
         version: str = "HTTP/1.0",
         headers: Optional[HTTPHeaders] = None,
         body: Optional[bytes] = None,
-        # host: Optional[str] = None,
+        host: Optional[str] = None,
         files: Optional[Dict[str, List["HTTPFile"]]] = None,
         connection: Optional["HTTPConnection"] = None,
         start_line: Optional["RequestStartLine"] = None,
@@ -466,7 +514,7 @@
         self.protocol = getattr(context, "protocol", "http")
 
         try:
-            self.host = self.headers["Host"]
+            self.host = host or self.headers["Host"]
         except KeyError:
             if version == "HTTP/1.0":
                 # HTTP/1.0 does not require the Host header.
@@ -474,7 +522,6 @@
             else:
                 raise HTTPInputError("Missing Host header")
         if not _ABNF.host.fullmatch(self.host):
-            print(_ABNF.host.pattern)
             raise HTTPInputError("Invalid Host header: %r" % self.host)
         if "," in self.host:
             # https://www.rfc-editor.org/rfc/rfc9112.html#name-request-target
@@ -946,7 +993,7 @@
         eoh = part.find(b"\r\n\r\n")
         if eoh == -1:
             raise HTTPInputError("multipart/form-data missing headers")
-        headers = HTTPHeaders.parse(part[:eoh].decode("utf-8"))
+        headers = HTTPHeaders.parse(part[:eoh].decode("utf-8"), 
_chars_are_bytes=False)
         disp_header = headers.get("Content-Disposition", "")
         disposition, disp_params = _parse_header(disp_header)
         if disposition != "form-data" or not part.endswith(b"\r\n"):
@@ -1048,19 +1095,34 @@
 # It has also been modified to support valueless parameters as seen in
 # websocket extension negotiations, and to support non-ascii values in
 # RFC 2231/5987 format.
+#
+# _parseparam has been further modified with the logic from
+# https://github.com/python/cpython/pull/136072/files
+# to avoid quadratic behavior when parsing semicolons in quoted strings.
+#
+# TODO: See if we can switch to email.message.Message for this functionality.
+# This is the suggested replacement for the cgi.py module now that cgi has
+# been removed from recent versions of Python.  We need to verify that
+# the email module is consistent with our existing behavior (and all relevant
+# RFCs for multipart/form-data) before making this change.
 
 
 def _parseparam(s: str) -> Generator[str, None, None]:
-    while s[:1] == ";":
-        s = s[1:]
-        end = s.find(";")
-        while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:
-            end = s.find(";", end + 1)
+    start = 0
+    while s.find(";", start) == start:
+        start += 1
+        end = s.find(";", start)
+        ind, diff = start, 0
+        while end > 0:
+            diff += s.count('"', ind, end) - s.count('\\"', ind, end)
+            if diff % 2 == 0:
+                break
+            end, ind = ind, s.find(";", end + 1)
         if end < 0:
             end = len(s)
-        f = s[:end]
+        f = s[start:end]
         yield f.strip()
-        s = s[end:]
+        start = end
 
 
 def _parse_header(line: str) -> Tuple[str, Dict[str, str]]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/routing.py 
new/tornado-6.5.4/tornado/routing.py
--- old/tornado-6.5/tornado/routing.py  2025-05-15 22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tornado/routing.py        2025-12-15 19:42:59.000000000 
+0100
@@ -279,8 +279,8 @@
         self.delegate.finish()
 
     def on_connection_close(self) -> None:
-        assert self.delegate is not None
-        self.delegate.on_connection_close()
+        if self.delegate is not None:
+            self.delegate.on_connection_close()
 
 
 class _DefaultMessageDelegate(httputil.HTTPMessageDelegate):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/speedups.pyi 
new/tornado-6.5.4/tornado/speedups.pyi
--- old/tornado-6.5/tornado/speedups.pyi        1970-01-01 01:00:00.000000000 
+0100
+++ new/tornado-6.5.4/tornado/speedups.pyi      2025-12-15 19:42:59.000000000 
+0100
@@ -0,0 +1 @@
+def websocket_mask(mask: bytes, data: bytes) -> bytes: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/test/httpclient_test.py 
new/tornado-6.5.4/tornado/test/httpclient_test.py
--- old/tornado-6.5/tornado/test/httpclient_test.py     2025-05-15 
22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tornado/test/httpclient_test.py   2025-12-15 
19:42:59.000000000 +0100
@@ -442,7 +442,7 @@
         # test if client hangs on tricky invalid gzip
         # curl/simple httpclient have different behavior (exception, logging)
         with ExpectLog(
-            app_log, "(Uncaught exception|Exception in callback)", 
required=False
+            gen_log, ".*Malformed HTTP message.*unconsumed gzip data", 
required=False
         ):
             try:
                 response = self.fetch("/invalid_gzip")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/test/httpserver_test.py 
new/tornado-6.5.4/tornado/test/httpserver_test.py
--- old/tornado-6.5/tornado/test/httpserver_test.py     2025-05-15 
22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tornado/test/httpserver_test.py   2025-12-15 
19:42:59.000000000 +0100
@@ -462,6 +462,18 @@
             self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), 
self.stop)
             self.wait()
 
+    def test_invalid_host_header_with_whitespace(self):
+        with ExpectLog(
+            gen_log, ".*Malformed HTTP message.*Invalid Host header", 
level=logging.INFO
+        ):
+            self.stream.write(b"GET / HTTP/1.0\r\nHost: foo bar\r\n\r\n")
+            start_line, headers, response = self.io_loop.run_sync(
+                lambda: read_stream_body(self.stream)
+            )
+            self.assertEqual("HTTP/1.1", start_line.version)
+            self.assertEqual(400, start_line.code)
+            self.assertEqual("Bad Request", start_line.reason)
+
     def test_chunked_request_body(self):
         # Chunked requests are not widely supported and we don't have a way
         # to generate them in AsyncHTTPClient, but HTTPServer will read them.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/test/httputil_test.py 
new/tornado-6.5.4/tornado/test/httputil_test.py
--- old/tornado-6.5/tornado/test/httputil_test.py       2025-05-15 
22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tornado/test/httputil_test.py     2025-12-15 
19:42:59.000000000 +0100
@@ -155,7 +155,7 @@
             self.assertEqual(file["filename"], filename)
             self.assertEqual(file["body"], b"Foo")
 
-    def test_non_ascii_filename(self):
+    def test_non_ascii_filename_rfc5987(self):
         data = b"""\
 --1234
 Content-Disposition: form-data; name="files"; filename="ab.txt"; 
filename*=UTF-8''%C3%A1b.txt
@@ -170,6 +170,23 @@
         self.assertEqual(file["filename"], "áb.txt")
         self.assertEqual(file["body"], b"Foo")
 
+    def test_non_ascii_filename_raw(self):
+        data = """\
+--1234
+Content-Disposition: form-data; name="files"; filename="测试.txt"
+
+Foo
+--1234--""".encode(
+            "utf-8"
+        ).replace(
+            b"\n", b"\r\n"
+        )
+        args, files = form_data_args()
+        parse_multipart_form_data(b"1234", data, args, files)
+        file = files["files"][0]
+        self.assertEqual(file["filename"], "测试.txt")
+        self.assertEqual(file["body"], b"Foo")
+
     def test_boundary_starts_and_ends_with_quotes(self):
         data = b"""\
 --1234
@@ -262,6 +279,29 @@
         self.assertEqual(file["filename"], "ab.txt")
         self.assertEqual(file["body"], b"Foo")
 
+    def test_disposition_param_linear_performance(self):
+        # This is a regression test for performance of parsing parameters
+        # to the content-disposition header, specifically for semicolons within
+        # quoted strings.
+        def f(n):
+            start = time.perf_counter()
+            message = (
+                b"--1234\r\nContent-Disposition: form-data; "
+                + b'x="'
+                + b";" * n
+                + b'"; '
+                + b'name="files"; filename="a.txt"\r\n\r\nFoo\r\n--1234--\r\n'
+            )
+            args: dict[str, list[bytes]] = {}
+            files: dict[str, list[HTTPFile]] = {}
+            parse_multipart_form_data(b"1234", message, args, files)
+            return time.perf_counter() - start
+
+        d1 = f(1_000)
+        d2 = f(10_000)
+        if d2 / d1 > 20:
+            self.fail(f"Disposition param parsing is not linear: {d1=} vs 
{d2=}")
+
 
 class HTTPHeadersTest(unittest.TestCase):
     def test_multi_line(self):
@@ -289,6 +329,9 @@
             sorted(list(headers.get_all())),
             [("Asdf", "qwer zxcv"), ("Foo", "bar baz"), ("Foo", "even more 
lines")],
         )
+        # Verify case insensitivity in-operator
+        self.assertTrue("asdf" in headers)
+        self.assertTrue("Asdf" in headers)
 
     def test_continuation(self):
         data = "Foo: bar\r\n\tasdf"
@@ -454,6 +497,21 @@
             with self.assertRaises(HTTPInputError):
                 headers.add(name, "bar")
 
+    def test_linear_performance(self):
+        def f(n):
+            start = time.perf_counter()
+            headers = HTTPHeaders()
+            for i in range(n):
+                headers.add("X-Foo", "bar")
+            return time.perf_counter() - start
+
+        # This runs under 50ms on my laptop as of 2025-12-09.
+        d1 = f(10_000)
+        d2 = f(100_000)
+        if d2 / d1 > 20:
+            # d2 should be about 10x d1 but allow a wide margin for 
variability.
+            self.fail(f"HTTPHeaders.add() does not scale linearly: {d1=} vs 
{d2=}")
+
 
 class FormatTimestampTest(unittest.TestCase):
     # Make sure that all the input types are supported.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/test/process_test.py 
new/tornado-6.5.4/tornado/test/process_test.py
--- old/tornado-6.5/tornado/test/process_test.py        2025-05-15 
22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tornado/test/process_test.py      2025-12-15 
19:42:59.000000000 +0100
@@ -141,7 +141,7 @@
     @gen_test
     def test_subprocess(self):
         subproc = Subprocess(
-            [sys.executable, "-u", "-i"],
+            [sys.executable, "-u", "-i", "-I"],
             stdin=Subprocess.STREAM,
             stdout=Subprocess.STREAM,
             stderr=subprocess.STDOUT,
@@ -163,7 +163,7 @@
     def test_close_stdin(self):
         # Close the parent's stdin handle and see that the child recognizes it.
         subproc = Subprocess(
-            [sys.executable, "-u", "-i"],
+            [sys.executable, "-u", "-i", "-I"],
             stdin=Subprocess.STREAM,
             stdout=Subprocess.STREAM,
             stderr=subprocess.STDOUT,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/test/web_test.py 
new/tornado-6.5.4/tornado/test/web_test.py
--- old/tornado-6.5/tornado/test/web_test.py    2025-05-15 22:19:08.000000000 
+0200
+++ new/tornado-6.5.4/tornado/test/web_test.py  2025-12-15 19:42:59.000000000 
+0100
@@ -1746,7 +1746,7 @@
     class Handler(RequestHandler):
         def get(self):
             reason = self.request.arguments.get("reason", [])
-            self.set_status(
+            raise HTTPError(
                 int(self.get_argument("code")),
                 reason=to_unicode(reason[0]) if reason else None,
             )
@@ -1769,6 +1769,19 @@
         self.assertEqual(response.code, 682)
         self.assertEqual(response.reason, "Unknown")
 
+    def test_header_injection(self):
+        response = self.fetch("/?code=200&reason=OK%0D%0AX-Injection:injected")
+        self.assertEqual(response.code, 200)
+        self.assertEqual(response.reason, "Unknown")
+        self.assertNotIn("X-Injection", response.headers)
+
+    def test_reason_xss(self):
+        response = self.fetch("/?code=400&reason=<script>alert(1)</script>")
+        self.assertEqual(response.code, 400)
+        self.assertEqual(response.reason, "Unknown")
+        self.assertNotIn(b"script", response.body)
+        self.assertIn(b"Unknown", response.body)
+
 
 class DateHeaderTest(SimpleHandlerTestCase):
     class Handler(RequestHandler):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/test/websocket_test.py 
new/tornado-6.5.4/tornado/test/websocket_test.py
--- old/tornado-6.5/tornado/test/websocket_test.py      2025-05-15 
22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tornado/test/websocket_test.py    2025-12-15 
19:42:59.000000000 +0100
@@ -1,5 +1,6 @@
 import asyncio
 import contextlib
+import datetime
 import functools
 import socket
 import traceback
@@ -861,16 +862,21 @@
         return app
 
     @staticmethod
-    def suppress_pong(ws):
-        """Suppress the client's "pong" response."""
+    def install_hook(ws):
+        """Optionally suppress the client's "pong" response."""
+
+        ws.drop_pongs = False
+        ws.pongs_received = 0
 
         def wrapper(fcn):
-            def _inner(oppcode: int, data: bytes):
-                if oppcode == 0xA:  # NOTE: 0x9=ping, 0xA=pong
-                    # prevent pong responses
-                    return
+            def _inner(opcode: int, data: bytes):
+                if opcode == 0xA:  # NOTE: 0x9=ping, 0xA=pong
+                    ws.pongs_received += 1
+                    if ws.drop_pongs:
+                        # prevent pong responses
+                        return
                 # leave all other responses unchanged
-                return fcn(oppcode, data)
+                return fcn(opcode, data)
 
             return _inner
 
@@ -883,13 +889,14 @@
         ws = yield self.ws_connect(
             "/", ping_interval=interval, ping_timeout=interval / 4
         )
+        self.install_hook(ws)
 
         # websocket handler (server side)
         handler = self.handlers[0]
 
         for _ in range(5):
             # wait for the ping period
-            yield gen.sleep(0.2)
+            yield gen.sleep(interval)
 
             # connection should still be open from the server end
             self.assertIsNone(handler.close_code)
@@ -898,8 +905,12 @@
             # connection should still be open from the client end
             assert ws.protocol.close_code is None
 
+        # Check that our hook is intercepting messages; allow for
+        # some variance in timing (due to e.g. cpu load)
+        self.assertGreaterEqual(ws.pongs_received, 4)
+
         # suppress the pong response message
-        self.suppress_pong(ws)
+        ws.drop_pongs = True
 
         # give the server time to register this
         yield gen.sleep(interval * 1.5)
@@ -912,6 +923,23 @@
         self.assertEqual(ws.protocol.close_code, 1000)
 
 
+class PingCalculationTest(unittest.TestCase):
+    def test_ping_sleep_time(self):
+        from tornado.websocket import WebSocketProtocol13
+
+        now = datetime.datetime(2025, 1, 1, 12, 0, 0, 
tzinfo=datetime.timezone.utc)
+        interval = 10  # seconds
+        last_ping_time = datetime.datetime(
+            2025, 1, 1, 11, 59, 54, tzinfo=datetime.timezone.utc
+        )
+        sleep_time = WebSocketProtocol13.ping_sleep_time(
+            last_ping_time=last_ping_time.timestamp(),
+            interval=interval,
+            now=now.timestamp(),
+        )
+        self.assertEqual(sleep_time, 4)
+
+
 class ManualPingTest(WebSocketBaseTestCase):
     def get_app(self):
         class PingHandler(TestWebSocketHandler):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/web.py 
new/tornado-6.5.4/tornado/web.py
--- old/tornado-6.5/tornado/web.py      2025-05-15 22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tornado/web.py    2025-12-15 19:42:59.000000000 +0100
@@ -359,8 +359,10 @@
 
         :arg int status_code: Response status code.
         :arg str reason: Human-readable reason phrase describing the status
-            code. If ``None``, it will be filled in from
-            `http.client.responses` or "Unknown".
+            code (for example, the "Not Found" in ``HTTP/1.1 404 Not Found``).
+            Normally determined automatically from `http.client.responses`; 
this
+            argument should only be used if you need to use a non-standard
+            status code.
 
         .. versionchanged:: 5.0
 
@@ -369,6 +371,14 @@
         """
         self._status_code = status_code
         if reason is not None:
+            if "<" in reason or not 
httputil._ABNF.reason_phrase.fullmatch(reason):
+                # Logically this would be better as an exception, but this 
method
+                # is called on error-handling paths that would need some 
refactoring
+                # to tolerate internal errors cleanly.
+                #
+                # The check for "<" is a defense-in-depth against XSS attacks 
(we also
+                # escape the reason when rendering error pages).
+                reason = "Unknown"
             self._reason = escape.native_str(reason)
         else:
             self._reason = httputil.responses.get(status_code, "Unknown")
@@ -1345,7 +1355,8 @@
                 reason = exception.reason
         self.set_status(status_code, reason=reason)
         try:
-            self.write_error(status_code, **kwargs)
+            if status_code != 304:
+                self.write_error(status_code, **kwargs)
         except Exception:
             app_log.error("Uncaught exception in write_error", exc_info=True)
         if not self._finished:
@@ -1373,7 +1384,7 @@
             self.finish(
                 "<html><title>%(code)d: %(message)s</title>"
                 "<body>%(code)d: %(message)s</body></html>"
-                % {"code": status_code, "message": self._reason}
+                % {"code": status_code, "message": 
escape.xhtml_escape(self._reason)}
             )
 
     @property
@@ -2520,9 +2531,11 @@
         mode).  May contain ``%s``-style placeholders, which will be filled
         in with remaining positional parameters.
     :arg str reason: Keyword-only argument.  The HTTP "reason" phrase
-        to pass in the status line along with ``status_code``.  Normally
+        to pass in the status line along with ``status_code`` (for example,
+        the "Not Found" in ``HTTP/1.1 404 Not Found``).  Normally
         determined automatically from ``status_code``, but can be used
-        to use a non-standard numeric code.
+        to use a non-standard numeric code. This is not a general-purpose
+        error message.
     """
 
     def __init__(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado/websocket.py 
new/tornado-6.5.4/tornado/websocket.py
--- old/tornado-6.5/tornado/websocket.py        2025-05-15 22:19:08.000000000 
+0200
+++ new/tornado-6.5.4/tornado/websocket.py      2025-12-15 19:42:59.000000000 
+0100
@@ -1346,6 +1346,11 @@
         ):
             self._ping_coroutine = asyncio.create_task(self.periodic_ping())
 
+    @staticmethod
+    def ping_sleep_time(*, last_ping_time: float, interval: float, now: float) 
-> float:
+        """Calculate the sleep time until the next ping should be sent."""
+        return max(0, last_ping_time + interval - now)
+
     async def periodic_ping(self) -> None:
         """Send a ping and wait for a pong if ping_timeout is configured.
 
@@ -1371,7 +1376,13 @@
                 return
 
             # wait until the next scheduled ping
-            await asyncio.sleep(IOLoop.current().time() - ping_time + interval)
+            await asyncio.sleep(
+                self.ping_sleep_time(
+                    last_ping_time=ping_time,
+                    interval=interval,
+                    now=IOLoop.current().time(),
+                )
+            )
 
 
 class WebSocketClientConnection(simple_httpclient._HTTPConnection):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado.egg-info/PKG-INFO 
new/tornado-6.5.4/tornado.egg-info/PKG-INFO
--- old/tornado-6.5/tornado.egg-info/PKG-INFO   2025-05-15 22:19:11.000000000 
+0200
+++ new/tornado-6.5.4/tornado.egg-info/PKG-INFO 2025-12-15 19:43:01.000000000 
+0100
@@ -1,13 +1,12 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: tornado
-Version: 6.5
+Version: 6.5.4
 Summary: Tornado is a Python web framework and asynchronous networking 
library, originally developed at FriendFeed.
 Home-page: http://www.tornadoweb.org/
 Author: Facebook
 Author-email: [email protected]
 License: Apache-2.0
 Project-URL: Source, https://github.com/tornadoweb/tornado
-Platform: UNKNOWN
 Classifier: License :: OSI Approved :: Apache Software License
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.9
@@ -20,6 +19,17 @@
 Requires-Python: >= 3.9
 Description-Content-Type: text/x-rst
 License-File: LICENSE
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: description-content-type
+Dynamic: home-page
+Dynamic: license
+Dynamic: license-file
+Dynamic: project-url
+Dynamic: requires-python
+Dynamic: summary
 
 Tornado Web Server
 ==================
@@ -72,5 +82,3 @@
 
 Documentation and links to additional resources are available at
 https://www.tornadoweb.org
-
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tornado.egg-info/SOURCES.txt 
new/tornado-6.5.4/tornado.egg-info/SOURCES.txt
--- old/tornado-6.5/tornado.egg-info/SOURCES.txt        2025-05-15 
22:19:11.000000000 +0200
+++ new/tornado-6.5.4/tornado.egg-info/SOURCES.txt      2025-12-15 
19:43:01.000000000 +0100
@@ -117,7 +117,12 @@
 docs/releases/v6.4.1.rst
 docs/releases/v6.4.2.rst
 docs/releases/v6.5.0.rst
+docs/releases/v6.5.1.rst
+docs/releases/v6.5.2.rst
+docs/releases/v6.5.3.rst
+docs/releases/v6.5.4.rst
 tornado/__init__.py
+tornado/__init__.pyi
 tornado/_locale_data.py
 tornado/auth.py
 tornado/autoreload.py
@@ -142,6 +147,7 @@
 tornado/routing.py
 tornado/simple_httpclient.py
 tornado/speedups.c
+tornado/speedups.pyi
 tornado/tcpclient.py
 tornado/tcpserver.py
 tornado/template.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5/tox.ini new/tornado-6.5.4/tox.ini
--- old/tornado-6.5/tox.ini     2025-05-15 22:19:08.000000000 +0200
+++ new/tornado-6.5.4/tox.ini   2025-12-15 19:42:59.000000000 +0100
@@ -35,7 +35,10 @@
 deps =
      full: pycurl
      full: twisted
-     full: pycares
+     # Pycares 5 has some backwards-incompatible changes that we don't support.
+     # And since CaresResolver is deprecated, I do not expect to fix it, so 
just
+     # pin the previous version. (This should really be in 
requirements.{in,txt} instead)
+     full: pycares<5
      docs: -r{toxinidir}/requirements.txt
      lint: -r{toxinidir}/requirements.txt
 

Reply via email to