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 2026-06-13 18:45:08
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-tornado6 (Old)
 and      /work/SRC/openSUSE:Factory/.python-tornado6.new.1981 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-tornado6"

Sat Jun 13 18:45:08 2026 rev:24 rq:1358805 version:6.5.7

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-tornado6/python-tornado6.changes  
2026-03-27 06:49:17.957457574 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-tornado6.new.1981/python-tornado6.changes    
    2026-06-13 18:45:31.012894504 +0200
@@ -1,0 +2,27 @@
+Fri Jun 12 03:01:06 UTC 2026 - Steve Kowalik <[email protected]>
+
+- Update to 6.5.7:
+  ## Security fixes
+  * CurlAsyncHTTPClient now fully resets the curl object before reusing it.
+    This prevents incorrectly reusing options from a previous request,
+    specifically including client SSL and credentials used for accessing
+    proxies.
+  * SimpleAsyncHTTPClient now strips the Authorization and Cookie headers
+    from the request when following a redirect to a different origin. This
+    matches the default behavior of CurlAsyncHTTPClient. Applications that
+    need different behavior here can set follow_redirects=False and handle
+    redirects manually. CVE-2026-49853
+  * SimpleAsyncHTTPClient now enforces max_body_size on the decompressed size
+    of the response, rather than the compressed size. This prevents a
+    denial-of-service attack via a very large compressed response.
+    CVE-2026-49855
+  * Fixed a bug in the C extension that could have read up to three bytes
+    past the end of an input array. CVE-2026-49854
+  * OpenIDMixin has improved parsing for the check_authentication response.
+  ## Bug fixes
+  * CurlAsyncHTTPClient has been updated to use non-deprecated APIs, avoiding
+    deprecation warnings with recent versions of pycurl.
+- Refreshed patch ignore-resourcewarning-doctests.patch
+- Drop patch fix-tests-with-curl-8-19.patch, merged upstream.
+
+-------------------------------------------------------------------

Old:
----
  fix-tests-with-curl-8-19.patch
  tornado-6.5.5.tar.gz

New:
----
  tornado-6.5.7.tar.gz

----------(Old B)----------
  Old:- Refreshed patch ignore-resourcewarning-doctests.patch
- Drop patch fix-tests-with-curl-8-19.patch, merged upstream.
----------(Old E)----------

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

Other differences:
------------------
++++++ python-tornado6.spec ++++++
--- /var/tmp/diff_new_pack.axWOVi/_old  2026-06-13 18:45:31.732924379 +0200
+++ /var/tmp/diff_new_pack.axWOVi/_new  2026-06-13 18:45:31.732924379 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-tornado6
-Version:        6.5.5
+Version:        6.5.7
 Release:        0
 Summary:        Open source version of scalable, non-blocking web server that 
power FriendFeed
 License:        Apache-2.0
@@ -27,10 +27,9 @@
 Source99:       python-tornado6-rpmlintrc
 # PATCH-FIX-OPENSUSE ignore-resourcewarning-doctests.patch -- ignore resource 
warnings on OBS
 Patch0:         ignore-resourcewarning-doctests.patch
-# PATCH-FIX-UPSTREAM fix-tests-with-curl-8-19.patch 
gh#tornadoweb/tornado@de5e943
-Patch1:         fix-tests-with-curl-8-19.patch
-Patch2:         pycares-getaddrinfo.patch
-BuildRequires:  %{python_module base >= 3.8}
+# PATCH-FIX-OPENSUSE increase compatibility with newer pycares versions
+Patch1:         pycares-getaddrinfo.patch
+BuildRequires:  %{python_module base >= 3.9}
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module pycares}

++++++ ignore-resourcewarning-doctests.patch ++++++
--- /var/tmp/diff_new_pack.axWOVi/_old  2026-06-13 18:45:31.768925873 +0200
+++ /var/tmp/diff_new_pack.axWOVi/_new  2026-06-13 18:45:31.772926039 +0200
@@ -1,8 +1,8 @@
-Index: tornado-6.5.5/tornado/util.py
+Index: tornado-6.5.7/tornado/util.py
 ===================================================================
---- tornado-6.5.5.orig/tornado/util.py
-+++ tornado-6.5.5/tornado/util.py
-@@ -441,5 +441,7 @@ else:
+--- tornado-6.5.7.orig/tornado/util.py
++++ tornado-6.5.7/tornado/util.py
+@@ -443,5 +443,7 @@ else:
  def doctests():
      # type: () -> unittest.TestSuite
      import doctest
@@ -10,10 +10,10 @@
 +    warnings.simplefilter("ignore", ResourceWarning)
  
      return doctest.DocTestSuite()
-Index: tornado-6.5.5/tornado/httputil.py
+Index: tornado-6.5.7/tornado/httputil.py
 ===================================================================
---- tornado-6.5.5.orig/tornado/httputil.py
-+++ tornado-6.5.5/tornado/httputil.py
+--- tornado-6.5.7.orig/tornado/httputil.py
++++ tornado-6.5.7/tornado/httputil.py
 @@ -1295,6 +1295,8 @@ def encode_username_password(
  def doctests():
      # type: () -> unittest.TestSuite
@@ -23,10 +23,10 @@
  
      return doctest.DocTestSuite(optionflags=doctest.ELLIPSIS)
  
-Index: tornado-6.5.5/tornado/iostream.py
+Index: tornado-6.5.7/tornado/iostream.py
 ===================================================================
---- tornado-6.5.5.orig/tornado/iostream.py
-+++ tornado-6.5.5/tornado/iostream.py
+--- tornado-6.5.7.orig/tornado/iostream.py
++++ tornado-6.5.7/tornado/iostream.py
 @@ -1613,5 +1613,7 @@ class PipeIOStream(BaseIOStream):
  
  def doctests() -> Any:

++++++ tornado-6.5.5.tar.gz -> tornado-6.5.7.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/PKG-INFO new/tornado-6.5.7/PKG-INFO
--- old/tornado-6.5.5/PKG-INFO  2026-03-10 22:14:52.714759300 +0100
+++ new/tornado-6.5.7/PKG-INFO  2026-06-08 19:18:41.907536000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: tornado
-Version: 6.5.5
+Version: 6.5.7
 Summary: Tornado is a Python web framework and asynchronous networking 
library, originally developed at FriendFeed.
 Home-page: http://www.tornadoweb.org/
 Author: Facebook
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/docs/releases/v6.5.5.rst 
new/tornado-6.5.7/docs/releases/v6.5.5.rst
--- old/tornado-6.5.5/docs/releases/v6.5.5.rst  2026-03-10 22:14:45.000000000 
+0100
+++ new/tornado-6.5.7/docs/releases/v6.5.5.rst  2026-06-08 19:18:33.000000000 
+0200
@@ -10,10 +10,10 @@
 - ``multipart/form-data`` requests are now limited to 100 parts by default, to 
prevent a
   denial-of-service attack via very large requests with many parts. This limit 
is configurable
   via `tornado.httputil.ParseMultipartConfig`. Multipart parsing can also be 
disabled completely
-  if not required for the application. Thanks to 
[0x-Apollyon](https://github.com/0x-Apollyon) and
-  [bekkaze](https://github.com/bekkaze) for reporting this issue.
+  if not required for the application. Thanks to `0x-Apollyon 
<https://github.com/0x-Apollyon>`_ and
+  `bekkaze <https://github.com/bekkaze>`_ for reporting this issue.
 - The ``domain``, ``path``, and ``samesite`` arguments to 
`.RequestHandler.set_cookie` are now
   validated for illegal characters, which could be abused to inject other 
attributes on the cookie.
   Thanks to Dhiral Vyas (Praetorian) for reporting this issue.
 - Carriage return characters are no longer accepted in ``multipart/form-data`` 
headers. Thanks to 
-  [sergeykochanov](https://github.com/sergeykochanov) for reporting this issue.
\ No newline at end of file
+  `sergeykochanov <https://github.com/sergeykochanov>`_ for reporting this 
issue.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/docs/releases/v6.5.6.rst 
new/tornado-6.5.7/docs/releases/v6.5.6.rst
--- old/tornado-6.5.5/docs/releases/v6.5.6.rst  1970-01-01 01:00:00.000000000 
+0100
+++ new/tornado-6.5.7/docs/releases/v6.5.6.rst  2026-06-08 19:18:33.000000000 
+0200
@@ -0,0 +1,33 @@
+What's new in Tornado 6.5.6
+===========================
+
+May 27, 2026
+------------
+
+Security fixes
+~~~~~~~~~~~~~~
+
+- ``SimpleAsyncHTTPClient`` now strips the ``Authorization`` and ``Cookie`` 
headers from the request
+  when following a redirect to a different origin. This matches the default 
behavior of
+  ``CurlAsyncHTTPClient``. Applications that need different behavior here can 
set
+  ``follow_redirects=False`` and handle redirects manually. Thanks to `Yannick
+  Wang <https://github.com/noobone123>`_ for being first to report this issue, 
as well as
+  additional reporters `Kai Aizen <https://github.com/SnailSploit>`_,
+  `HunSec <https://github.com/0xHunSec>`_, and `Thai Son Dinh 
<https://github.com/sondt99>`_.
+  `CVE-2026-49853 
<https://github.com/tornadoweb/tornado/security/advisories/GHSA-3x9g-8vmp-wqvf>`_
+- ``SimpleAsyncHTTPClient`` now enforces ``max_body_size`` on the decompressed 
size of the response,
+  rather than the compressed size. This prevents a denial-of-service attack 
via a very large
+  compressed response. Thanks to `Yuichiro Kedashiro 
<https://github.com/yuui25>`_ for reporting this
+  issue.
+  `CVE-2026-49855 
<https://github.com/tornadoweb/tornado/security/advisories/GHSA-mgf9-4vpg-hj56>`_
+- Fixed a bug in the C extension that could have read up to three bytes past 
the end of an input
+  array. Thanks to `Thai Son Dinh <https://github.com/sondt99>`_ for reporting 
this issue.
+  `CVE-2026-49854 
<https://github.com/tornadoweb/tornado/security/advisories/GHSA-cx3h-4qpv-8hc9>`_
+- ``OpenIDMixin`` has improved parsing for the ``check_authentication`` 
response. Thanks to
+  `Yannick Wang <https://github.com/noobone123>`_ for reporting this issue.
+
+Bug fixes
+~~~~~~~~~
+
+- ``CurlAsyncHTTPClient`` has been updated to use non-deprecated APIs, 
avoiding deprecation
+  warnings with recent versions of ``pycurl``.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/docs/releases/v6.5.7.rst 
new/tornado-6.5.7/docs/releases/v6.5.7.rst
--- old/tornado-6.5.5/docs/releases/v6.5.7.rst  1970-01-01 01:00:00.000000000 
+0100
+++ new/tornado-6.5.7/docs/releases/v6.5.7.rst  2026-06-08 19:18:33.000000000 
+0200
@@ -0,0 +1,13 @@
+What's new in Tornado 6.5.7
+===========================
+
+Jun 8, 2026
+-----------
+
+Security fixes
+~~~~~~~~~~~~~~
+
+- ``CurlAsyncHTTPClient`` now fully resets the curl object before reusing it. 
This prevents
+  incorrectly reusing options from a previous request, specifically including 
client SSL and
+  credentials used for accessing proxies. Thanks to `Koh Jun Sheng 
<https://github.com/seankohjs>`_
+  for reporting this issue.
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/docs/releases.rst 
new/tornado-6.5.7/docs/releases.rst
--- old/tornado-6.5.5/docs/releases.rst 2026-03-10 22:14:45.000000000 +0100
+++ new/tornado-6.5.7/docs/releases.rst 2026-06-08 19:18:33.000000000 +0200
@@ -4,6 +4,8 @@
 .. toctree::
    :maxdepth: 2
 
+   releases/v6.5.7
+   releases/v6.5.6
    releases/v6.5.5
    releases/v6.5.4
    releases/v6.5.3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/__init__.py 
new/tornado-6.5.7/tornado/__init__.py
--- old/tornado-6.5.5/tornado/__init__.py       2026-03-10 22:14:45.000000000 
+0100
+++ new/tornado-6.5.7/tornado/__init__.py       2026-06-08 19:18:33.000000000 
+0200
@@ -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.5"
-version_info = (6, 5, 5, 0)
+version = "6.5.7"
+version_info = (6, 5, 7, 0)
 
 import importlib
 import typing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/auth.py 
new/tornado-6.5.7/tornado/auth.py
--- old/tornado-6.5.5/tornado/auth.py   2026-03-10 22:14:45.000000000 +0100
+++ new/tornado-6.5.7/tornado/auth.py   2026-06-08 19:18:33.000000000 +0200
@@ -73,6 +73,7 @@
 import binascii
 import hashlib
 import hmac
+import re
 import time
 import urllib.parse
 import uuid
@@ -217,7 +218,7 @@
         self, response: httpclient.HTTPResponse
     ) -> Dict[str, Any]:
         handler = cast(RequestHandler, self)
-        if b"is_valid:true" not in response.body:
+        if re.search(rb"(?m)^is_valid:true$", response.body) is None:
             raise AuthError("Invalid OpenID response: %r" % response.body)
 
         # Make sure we got back at least an email from attribute exchange
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/curl_httpclient.py 
new/tornado-6.5.7/tornado/curl_httpclient.py
--- old/tornado-6.5.5/tornado/curl_httpclient.py        2026-03-10 
22:14:45.000000000 +0100
+++ new/tornado-6.5.7/tornado/curl_httpclient.py        2026-06-08 
19:18:33.000000000 +0200
@@ -65,15 +65,6 @@
         self._fds = {}  # type: Dict[int, int]
         self._timeout = None  # type: Optional[object]
 
-        # libcurl has bugs that sometimes cause it to not report all
-        # relevant file descriptors and timeouts to TIMERFUNCTION/
-        # SOCKETFUNCTION.  Mitigate the effects of such bugs by
-        # forcing a periodic scan of all active requests.
-        self._force_timeout_callback = ioloop.PeriodicCallback(
-            self._handle_force_timeout, 1000
-        )
-        self._force_timeout_callback.start()
-
         # Work around a bug in libcurl 7.29.0: Some fields in the curl
         # multi object are initialized lazily, and its destructor will
         # segfault if it is destroyed without having been used.  Add
@@ -84,7 +75,6 @@
         self._multi.remove_handle(dummy_curl_handle)
 
     def close(self) -> None:
-        self._force_timeout_callback.stop()
         if self._timeout is not None:
             self.io_loop.remove_timeout(self._timeout)
         for curl in self._curls:
@@ -95,7 +85,6 @@
         # Set below properties to None to reduce the reference count of current
         # instance, because those properties hold some methods of current
         # instance that will case circular reference.
-        self._force_timeout_callback = None  # type: ignore
         self._multi = None
 
     def fetch_impl(
@@ -189,19 +178,6 @@
         if new_timeout >= 0:
             self._set_timeout(new_timeout)
 
-    def _handle_force_timeout(self) -> None:
-        """Called by IOLoop periodically to ask libcurl to process any
-        events it may have forgotten about.
-        """
-        while True:
-            try:
-                ret, num_handles = self._multi.socket_all()
-            except pycurl.error as e:
-                ret = e.args[0]
-            if ret != pycurl.E_CALL_MULTI_PERFORM:
-                break
-        self._finish_pending_requests()
-
     def _finish_pending_requests(self) -> None:
         """Process any requests that were completed by the last
         call to multi.socket_action.
@@ -249,6 +225,7 @@
                     # _process_queue() is called from
                     # _finish_pending_requests the exceptions have
                     # nowhere to go.
+                    curl.reset()
                     self._free_list.append(curl)
                     callback(HTTPResponse(request=request, code=599, error=e))
                 else:
@@ -266,7 +243,6 @@
         info = curl.info  # type: ignore
         curl.info = None  # type: ignore
         self._multi.remove_handle(curl)
-        self._free_list.append(curl)
         buffer = info["buffer"]
         if curl_error:
             assert curl_message is not None
@@ -310,12 +286,22 @@
             )
         except Exception:
             self.handle_callback_exception(info["callback"])
+        curl.reset()
+        self._free_list.append(curl)
 
     def handle_callback_exception(self, callback: Any) -> None:
         app_log.error("Exception in callback %r", callback, exc_info=True)
 
     def _curl_create(self) -> pycurl.Curl:
-        curl = pycurl.Curl()
+        return pycurl.Curl()
+
+    def _curl_setup_request(
+        self,
+        curl: pycurl.Curl,
+        request: HTTPRequest,
+        buffer: BytesIO,
+        headers: httputil.HTTPHeaders,
+    ) -> None:
         if curl_log.isEnabledFor(logging.DEBUG):
             curl.setopt(pycurl.VERBOSE, 1)
             curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)
@@ -324,15 +310,7 @@
         ):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)
             curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | 
pycurl.PROTO_HTTPS)
             curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | 
pycurl.PROTO_HTTPS)
-        return curl
 
-    def _curl_setup_request(
-        self,
-        curl: pycurl.Curl,
-        request: HTTPRequest,
-        buffer: BytesIO,
-        headers: httputil.HTTPHeaders,
-    ) -> None:
         curl.setopt(pycurl.URL, native_str(request.url))
 
         # libcurl's magic "Expect: 100-continue" behavior causes delays
@@ -484,12 +462,12 @@
                 raise ValueError("Body must be None for GET request")
             request_buffer = BytesIO(utf8(request.body or ""))
 
-            def ioctl(cmd: int) -> None:
-                if cmd == curl.IOCMD_RESTARTREAD:  # type: ignore
-                    request_buffer.seek(0)
+            def seek(offset: int, origin: int) -> int:
+                request_buffer.seek(offset, origin)
+                return pycurl.SEEKFUNC_OK
 
             curl.setopt(pycurl.READFUNCTION, request_buffer.read)
-            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)
+            curl.setopt(pycurl.SEEKFUNCTION, seek)
             if request.method == "POST":
                 curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ""))
             else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/http1connection.py 
new/tornado-6.5.7/tornado/http1connection.py
--- old/tornado-6.5.5/tornado/http1connection.py        2026-03-10 
22:14:45.000000000 +0100
+++ new/tornado-6.5.7/tornado/http1connection.py        2026-06-08 
19:18:33.000000000 +0200
@@ -182,7 +182,9 @@
         been read. The result is true if the stream is still open.
         """
         if self.params.decompress:
-            delegate = _GzipMessageDelegate(delegate, self.params.chunk_size)
+            delegate = _GzipMessageDelegate(
+                delegate, self.params.chunk_size, self._max_body_size
+            )
         return self._read_message(delegate)
 
     async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> 
bool:
@@ -705,9 +707,16 @@
 class _GzipMessageDelegate(httputil.HTTPMessageDelegate):
     """Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``."""
 
-    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: 
int) -> None:
+    def __init__(
+        self,
+        delegate: httputil.HTTPMessageDelegate,
+        chunk_size: int,
+        max_body_size: int,
+    ) -> None:
         self._delegate = delegate
         self._chunk_size = chunk_size
+        self._max_body_size = max_body_size
+        self._decompressed_body_size = 0
         self._decompressor = None  # type: Optional[GzipDecompressor]
 
     def headers_received(
@@ -732,6 +741,9 @@
                     compressed_data, self._chunk_size
                 )
                 if decompressed:
+                    self._decompressed_body_size += len(decompressed)
+                    if self._decompressed_body_size > self._max_body_size:
+                        raise httputil.HTTPInputError("decompressed body too 
large")
                     ret = self._delegate.data_received(decompressed)
                     if ret is not None:
                         await ret
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/simple_httpclient.py 
new/tornado-6.5.7/tornado/simple_httpclient.py
--- old/tornado-6.5.5/tornado/simple_httpclient.py      2026-03-10 
22:14:45.000000000 +0100
+++ new/tornado-6.5.7/tornado/simple_httpclient.py      2026-06-08 
19:18:33.000000000 +0200
@@ -631,6 +631,42 @@
             new_request.url = urllib.parse.urljoin(
                 self.request.url, self.headers["Location"]
             )
+            new_request.headers = self.request.headers.copy()
+            parsed_orig_url = urllib.parse.urlsplit(original_request.url)
+            parsed_new_url = urllib.parse.urlsplit(new_request.url)
+            if (
+                parsed_orig_url.scheme != parsed_new_url.scheme
+                or parsed_orig_url.netloc != parsed_new_url.netloc
+            ):
+                # Cross-origin redirect: strip auth headers.
+                # Note that while there is no formal specification of headers 
that should be
+                # stripped here, libcurl strips the Authorization and Cookie 
headers, so we
+                # do the same.
+                # Reference:
+                # 
https://github.com/curl/curl/blob/01d8191b25a05e8fa91553a6c0d48acb99907d26/lib/http.c#L1827-L1828
+                #
+                # Note that checking for cross-origin redirects is a crude 
heuristic. It is both
+                # too weak (e.g. cookies that have a path attribute may need 
to be stripped even on
+                # same-origin redirects) and too strong (e.g. cookies may be 
kept on cross-host
+                # redirects within the same domain). However, we cannot know 
the full details of
+                # the cookie policy at this layer, so we use the same 
heuristic as libcurl.
+                # Applications that need more control over behavior on 
redirects can set
+                # follow_redirects=False and handle 3xx responses themselves.
+                new_request.auth_username = None
+                new_request.auth_password = None
+                if "@" in parsed_new_url.netloc:
+                    if parsed_new_url.port is not None:
+                        new_netloc = 
f"{parsed_new_url.hostname}:{parsed_new_url.port}"
+                    else:
+                        assert parsed_new_url.hostname is not None
+                        new_netloc = parsed_new_url.hostname
+                    parsed_new_url = parsed_new_url._replace(netloc=new_netloc)
+                new_request.url = urllib.parse.urlunsplit(parsed_new_url)
+                for h in ["Authorization", "Cookie"]:
+                    try:
+                        del new_request.headers[h]
+                    except KeyError:
+                        pass
             assert self.request.max_redirects is not None
             new_request.max_redirects = self.request.max_redirects - 1
             del new_request.headers["Host"]
@@ -655,7 +691,7 @@
                     "Transfer-Encoding",
                 ]:
                     try:
-                        del self.request.headers[h]
+                        del new_request.headers[h]
                     except KeyError:
                         pass
             new_request.original_request = original_request  # type: ignore
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/speedups.c 
new/tornado-6.5.7/tornado/speedups.c
--- old/tornado-6.5.5/tornado/speedups.c        2026-03-10 22:14:45.000000000 
+0100
+++ new/tornado-6.5.7/tornado/speedups.c        2026-06-08 19:18:33.000000000 
+0200
@@ -2,63 +2,76 @@
 #include <Python.h>
 #include <stdint.h>
 
-static PyObject* websocket_mask(PyObject* self, PyObject* args) {
-    const char* mask;
+static PyObject *websocket_mask(PyObject *self, PyObject *args)
+{
+    const char *mask;
     Py_ssize_t mask_len;
     uint32_t uint32_mask;
     uint64_t uint64_mask;
-    const char* data;
+    const char *data;
     Py_ssize_t data_len;
     Py_ssize_t i;
-    PyObject* result;
-    char* buf;
+    PyObject *result;
+    char *buf;
 
-    if (!PyArg_ParseTuple(args, "s#s#", &mask, &mask_len, &data, &data_len)) {
+    if (!PyArg_ParseTuple(args, "s#s#", &mask, &mask_len, &data, &data_len))
+    {
         return NULL;
     }
 
-    uint32_mask = ((uint32_t*)mask)[0];
+    if (mask_len != 4)
+    {
+        PyErr_SetString(PyExc_ValueError, "mask must be 4 bytes");
+        return NULL;
+    }
+
+    uint32_mask = ((uint32_t *)mask)[0];
 
     result = PyBytes_FromStringAndSize(NULL, data_len);
-    if (!result) {
+    if (!result)
+    {
         return NULL;
     }
     buf = PyBytes_AsString(result);
 
-    if (sizeof(size_t) >= 8) {
+    if (sizeof(size_t) >= 8)
+    {
         uint64_mask = uint32_mask;
         uint64_mask = (uint64_mask << 32) | uint32_mask;
 
-        while (data_len >= 8) {
-            ((uint64_t*)buf)[0] = ((uint64_t*)data)[0] ^ uint64_mask;
+        while (data_len >= 8)
+        {
+            ((uint64_t *)buf)[0] = ((uint64_t *)data)[0] ^ uint64_mask;
             data += 8;
             buf += 8;
             data_len -= 8;
         }
     }
 
-    while (data_len >= 4) {
-        ((uint32_t*)buf)[0] = ((uint32_t*)data)[0] ^ uint32_mask;
+    while (data_len >= 4)
+    {
+        ((uint32_t *)buf)[0] = ((uint32_t *)data)[0] ^ uint32_mask;
         data += 4;
         buf += 4;
         data_len -= 4;
     }
 
-    for (i = 0; i < data_len; i++) {
+    for (i = 0; i < data_len; i++)
+    {
         buf[i] = data[i] ^ mask[i];
     }
 
     return result;
 }
 
-static int speedups_exec(PyObject *module) {
+static int speedups_exec(PyObject *module)
+{
     return 0;
 }
 
 static PyMethodDef methods[] = {
-    {"websocket_mask",  websocket_mask, METH_VARARGS, ""},
-    {NULL, NULL, 0, NULL}
-};
+    {"websocket_mask", websocket_mask, METH_VARARGS, ""},
+    {NULL, NULL, 0, NULL}};
 
 static PyModuleDef_Slot slots[] = {
     {Py_mod_exec, speedups_exec},
@@ -68,19 +81,19 @@
 #if (!defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030d0000) || 
Py_LIMITED_API >= 0x030d0000
     {Py_mod_gil, Py_MOD_GIL_NOT_USED},
 #endif
-    {0, NULL}
-};
+    {0, NULL}};
 
 static struct PyModuleDef speedupsmodule = {
-   PyModuleDef_HEAD_INIT,
-   "speedups",
-   NULL,
-   0,
-   methods,
-   slots,
+    PyModuleDef_HEAD_INIT,
+    "speedups",
+    NULL,
+    0,
+    methods,
+    slots,
 };
 
 PyMODINIT_FUNC
-PyInit_speedups(void) {
+PyInit_speedups(void)
+{
     return PyModuleDef_Init(&speedupsmodule);
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/test/auth_test.py 
new/tornado-6.5.7/tornado/test/auth_test.py
--- old/tornado-6.5.5/tornado/test/auth_test.py 2026-03-10 22:14:45.000000000 
+0100
+++ new/tornado-6.5.7/tornado/test/auth_test.py 2026-06-08 19:18:33.000000000 
+0200
@@ -46,10 +46,20 @@
 
 
 class OpenIdServerAuthenticateHandler(RequestHandler):
+    flip_flop = False
+
     def post(self):
         if self.get_argument("openid.mode") != "check_authentication":
             raise Exception("incorrect openid.mode %r")
-        self.write("is_valid:true")
+        # Cover both orderings of the response parameters if we call this 
handler twice.
+        # (the flip_flop side effect is simpler than plumbing parameters 
around).
+        # We check both orderings to catch mistaken uses of re.match instead 
of re.search
+        # or incorrect matching of the newline characters.
+        if type(self).flip_flop:
+            self.write("is_valid:true\nns:http://specs.openid.net/auth/2.0\n";)
+        else:
+            self.write("ns:http://specs.openid.net/auth/2.0\nis_valid:true\n";)
+        type(self).flip_flop = not type(self).flip_flop
 
 
 class OAuth1ClientLoginHandler(RequestHandler, OAuthMixin):
@@ -344,15 +354,17 @@
         self.assertIn("/openid/server/authenticate?", 
response.headers["Location"])
 
     def test_openid_get_user(self):
-        response = self.fetch(
-            "/openid/client/login?openid.mode=blah"
-            "&openid.ns.ax=http://openid.net/srv/ax/1.0";
-            "&openid.ax.type.email=http://axschema.org/contact/email";
-            "&[email protected]"
-        )
-        response.rethrow()
-        parsed = json_decode(response.body)
-        self.assertEqual(parsed["email"], "[email protected]")
+        for i in range(2):
+            with self.subTest(i=i):
+                response = self.fetch(
+                    "/openid/client/login?openid.mode=blah"
+                    "&openid.ns.ax=http://openid.net/srv/ax/1.0";
+                    "&openid.ax.type.email=http://axschema.org/contact/email";
+                    "&[email protected]"
+                )
+                response.rethrow()
+                parsed = json_decode(response.body)
+                self.assertEqual(parsed["email"], "[email protected]")
 
     def test_oauth10_redirect(self):
         response = self.fetch("/oauth10/client/login", follow_redirects=False)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/test/curl_httpclient_test.py 
new/tornado-6.5.7/tornado/test/curl_httpclient_test.py
--- old/tornado-6.5.5/tornado/test/curl_httpclient_test.py      2026-03-10 
22:14:45.000000000 +0100
+++ new/tornado-6.5.7/tornado/test/curl_httpclient_test.py      2026-06-08 
19:18:33.000000000 +0200
@@ -1,12 +1,14 @@
 from hashlib import md5
+import os
+import ssl
 import unittest
 
 from tornado.escape import utf8
-from tornado.testing import AsyncHTTPTestCase
+from tornado.netutil import ssl_options_to_context
 from tornado.test import httpclient_test
+from tornado.testing import AsyncHTTPSTestCase, AsyncHTTPTestCase
 from tornado.web import Application, RequestHandler
 
-
 try:
     import pycurl
 except ImportError:
@@ -123,3 +125,98 @@
             auth_password="barユ£",
         )
         self.assertEqual(response.body, b"ok")
+
+
+class ProxyAuthEchoHandler(RequestHandler):
+    def get(self):
+        if self.request.headers.get("Proxy-Authorization", None) is not None:
+            self.write(f"proxy auth: 
{self.request.headers['Proxy-Authorization']}")
+        else:
+            self.write("no proxy auth")
+
+
[email protected](pycurl is None, "pycurl module not present")
+class CurlHTTPClientReuseProxyAuthTestCase(AsyncHTTPTestCase):
+    def get_app(self):
+        # Note that we don't properly support proxy-style requests, but it 
works well enough
+        # for this test if we start the url matcher with a wildcard.
+        return Application([(".*/proxy_auth", ProxyAuthEchoHandler)])
+
+    def get_http_client(self):
+        # max_clients=1 forces us to reuse curl "easy handles". This is a 
regression test for
+        # a bug in which proxy credentials were not cleared between requests.
+        return CurlAsyncHTTPClient(
+            force_instance=True,
+            defaults=dict(
+                allow_ipv6=False,
+            ),
+            max_clients=1,
+        )
+
+    def test_reuse_proxy_credentials(self):
+        # Proxy credentials used on one request should not be automatically 
reused
+        # by another request.
+        response = self.fetch(
+            "/proxy_auth",
+            proxy_host="127.0.0.1",
+            proxy_port=self.get_http_port(),
+            proxy_username="foo",
+            proxy_password="bar",
+        )
+        self.assertEqual(response.body, b"proxy auth: Basic Zm9vOmJhcg==")
+        response = self.fetch(
+            "/proxy_auth",
+            proxy_host="127.0.0.1",
+            proxy_port=self.get_http_port(),
+        )
+        self.assertEqual(response.body, b"no proxy auth")
+
+
+class ClientCertEchoHandler(RequestHandler):
+    def get(self):
+        cert = self.request.get_ssl_certificate()
+        if cert is not None:
+            assert isinstance(cert, dict)
+            self.write(f"client cert: {cert['subject']}")
+        else:
+            self.write("no client cert")
+
+
[email protected](pycurl is None, "pycurl module not present")
+class CurlHTTPClientReuseCertsTestCase(AsyncHTTPSTestCase):
+    def get_app(self):
+        return Application([(".*/client_cert", ClientCertEchoHandler)])
+
+    def get_http_client(self):
+        return CurlAsyncHTTPClient(
+            force_instance=True,
+            defaults=dict(
+                allow_ipv6=False,
+                validate_cert=False,
+            ),
+            max_clients=1,
+        )
+
+    def get_httpserver_options(self):
+        ssl_ctx = ssl_options_to_context(self.get_ssl_options(), 
server_side=True)
+        ssl_ctx.verify_mode = ssl.CERT_OPTIONAL
+        return dict(ssl_options=ssl_ctx)
+
+    def get_ssl_options(self):
+        opts = super().get_ssl_options()
+        opts["ca_certs"] = os.path.join(os.path.dirname(__file__), "test.crt")
+        return opts
+
+    def test_reuse_certs(self):
+        # Client certs used on one request should not be automatically reused
+        # by another request.
+        response = self.fetch(
+            self.get_url("/client_cert"),
+            client_cert=os.path.join(os.path.dirname(__file__), "test.crt"),
+            client_key=os.path.join(os.path.dirname(__file__), "test.key"),
+        )
+        self.assertEqual(
+            response.body, b"client cert: ((('commonName', 
'foo.example.com'),),)"
+        )
+        response = self.fetch(self.get_url("/client_cert"))
+        self.assertEqual(response.body, b"no client cert")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/test/httpclient_test.py 
new/tornado-6.5.7/tornado/test/httpclient_test.py
--- old/tornado-6.5.5/tornado/test/httpclient_test.py   2026-03-10 
22:14:45.000000000 +0100
+++ new/tornado-6.5.7/tornado/test/httpclient_test.py   2026-06-08 
19:18:33.000000000 +0200
@@ -13,7 +13,7 @@
 import unicodedata
 import unittest
 
-from tornado.escape import utf8, native_str, to_unicode
+from tornado.escape import utf8, native_str, to_unicode, json_encode, 
json_decode
 from tornado import gen
 from tornado.httpclient import (
     HTTPRequest,
@@ -156,6 +156,11 @@
         self.finish(self.request.headers["Foo"].encode("ISO8859-1"))
 
 
+class EchoHeadersHandler(RequestHandler):
+    def get(self):
+        self.write(json_encode(dict(self.request.headers.get_all())))
+
+
 # These tests end up getting run redundantly: once here with the default
 # HTTPClient implementation, and then again in each implementation's own
 # test suite.
@@ -181,10 +186,23 @@
                 url("/set_header", SetHeaderHandler),
                 url("/invalid_gzip", InvalidGzipHandler),
                 url("/header-encoding", HeaderEncodingHandler),
+                url("/echo_headers", EchoHeadersHandler),
             ],
             gzip=True,
         )
 
+    def setUp(self):
+        super().setUp()
+
+        # Add a second port (serving the same app) to the HTTP server, so we 
can test the effects
+        # of redirects that span different origins.
+        sock, port = bind_unused_port()
+        self.http_server.add_socket(sock)
+        self.__port2 = port
+
+    def get_url2(self, path: str) -> str:
+        return f"{self.get_protocol()}://127.0.0.1:{self.__port2}{path}"
+
     def test_patch_receives_payload(self):
         body = b"some patch data"
         response = self.fetch("/patch", method="PATCH", body=body)
@@ -622,7 +640,10 @@
         with self.assertRaises((ValueError, HTTPError)) as context:  # type: 
ignore
             request = HTTPRequest(url, network_interface="not-interface-or-ip")
             yield self.http_client.fetch(request)
-        self.assertIn("not-interface-or-ip", str(context.exception))
+        self.assertTrue(
+            "not-interface-or-ip" in str(context.exception)
+            or "Failed binding local connection end" in str(context.exception)
+        )
 
     def test_all_methods(self):
         for method in ["GET", "DELETE", "OPTIONS"]:
@@ -759,6 +780,47 @@
                 with self.assertRaises(ValueError):
                     self.fetch("/hello", headers={header: "foo"})
 
+    def test_strip_headers_on_redirect(self):
+        # Ensure that headers that should be stripped on cross-origin redirects
+        # are stripped, even if the redirect is to a different port on 
localhost.
+        test_cases: list[tuple[str, dict, str]] = [
+            ("manual auth header", dict(headers={"Authorization": "secret"}), 
""),
+            ("credentials in URL", dict(), "me:secret"),
+            ("auth parameters", dict(auth_username="me", 
auth_password="secret"), ""),
+            ("manual cookie header", dict(headers={"Cookie": "secret"}), ""),
+        ]
+        for name, kwargs, url_creds in test_cases:
+            with self.subTest(name=name, origin="different"):
+                url = self.get_url(
+                    "/redirect?url=%s&status=302" % 
self.get_url2("/echo_headers")
+                )
+                if url_creds:
+                    url = url.replace("http://";, "http://%s@"; % url_creds)
+                response = self.fetch(**dict(path=url) | kwargs)
+                response.rethrow()
+                echoed_headers = json_decode(response.body)
+                # Confirm that non-auth headers are getting through
+                self.assertIn("User-Agent", echoed_headers)
+                # Auth headers are stripped, however they were set.
+                self.assertNotIn("Authorization", echoed_headers)
+                self.assertNotIn("Cookie", echoed_headers)
+            with self.subTest(name=name, origin="same"):
+                url = self.get_url(
+                    "/redirect?url=%s&status=302" % 
self.get_url("/echo_headers")
+                )
+                if url_creds:
+                    url = url.replace("http://";, "http://%s@"; % url_creds)
+                response = self.fetch(**dict(path=url) | kwargs)
+                response.rethrow()
+                echoed_headers = json_decode(response.body)
+                # Confirm that non-auth headers are getting through
+                self.assertIn("User-Agent", echoed_headers)
+                # Auth headers are not stripped when the redirect is 
same-origin.
+                # Each of our tests uses one of these headers, but not both.
+                self.assertTrue(
+                    "Authorization" in echoed_headers or "Cookie" in 
echoed_headers
+                )
+
 
 class RequestProxyTest(unittest.TestCase):
     def test_request_set(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/test/httpserver_test.py 
new/tornado-6.5.7/tornado/test/httpserver_test.py
--- old/tornado-6.5.5/tornado/test/httpserver_test.py   2026-03-10 
22:14:45.000000000 +0100
+++ new/tornado-6.5.7/tornado/test/httpserver_test.py   2026-06-08 
19:18:33.000000000 +0200
@@ -1133,7 +1133,7 @@
 
 class GzipTest(GzipBaseTest, AsyncHTTPTestCase):
     def get_httpserver_options(self):
-        return dict(decompress_request=True)
+        return dict(decompress_request=True, max_body_size=100)
 
     def test_gzip(self):
         response = self.post_gzip("foo=bar")
@@ -1154,6 +1154,10 @@
         )
         self.assertEqual(json_decode(response.body), {"foo": ["bar"]})
 
+    def test_size_limit(self):
+        with ExpectLog(gen_log, ".*decompressed body too large", 
level=logging.INFO):
+            self.post_gzip("x" * 101)
+
 
 class GzipUnsupportedTest(GzipBaseTest, AsyncHTTPTestCase):
     def test_gzip_unsupported(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/test/websocket_test.py 
new/tornado-6.5.7/tornado/test/websocket_test.py
--- old/tornado-6.5.5/tornado/test/websocket_test.py    2026-03-10 
22:14:45.000000000 +0100
+++ new/tornado-6.5.7/tornado/test/websocket_test.py    2026-06-08 
19:18:33.000000000 +0200
@@ -794,6 +794,13 @@
             b"\xff\xfa\xff\xff\xfb\xfe",
         )
 
+    def test_length_validation(self: typing.Any):
+        # Test all lengths of mask that are not 4 bytes.
+        for mask in (b"", b"a", b"ab", b"abc", b"abcde", b"abcdef"):
+            with self.subTest(mask=mask):
+                with self.assertRaises(ValueError):
+                    self.mask(mask, b"data asdf")
+
 
 class PythonMaskFunctionTest(MaskFunctionMixin):
     def mask(self, mask, data):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado/util.py 
new/tornado-6.5.7/tornado/util.py
--- old/tornado-6.5.5/tornado/util.py   2026-03-10 22:14:45.000000000 +0100
+++ new/tornado-6.5.7/tornado/util.py   2026-06-08 19:18:33.000000000 +0200
@@ -145,7 +145,7 @@
 
 
 def raise_exc_info(
-    exc_info: Tuple[Optional[type], Optional[BaseException], 
Optional["TracebackType"]]
+    exc_info: Tuple[Optional[type], Optional[BaseException], 
Optional["TracebackType"]],
 ) -> typing.NoReturn:
     try:
         if exc_info[1] is not None:
@@ -418,6 +418,8 @@
 
     This pure-python implementation may be replaced by an optimized version 
when available.
     """
+    if len(mask) != 4:
+        raise ValueError("mask must be 4 bytes")
     mask_arr = array.array("B", mask)
     unmasked_arr = array.array("B", data)
     for i in range(len(data)):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado.egg-info/PKG-INFO 
new/tornado-6.5.7/tornado.egg-info/PKG-INFO
--- old/tornado-6.5.5/tornado.egg-info/PKG-INFO 2026-03-10 22:14:52.000000000 
+0100
+++ new/tornado-6.5.7/tornado.egg-info/PKG-INFO 2026-06-08 19:18:41.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: tornado
-Version: 6.5.5
+Version: 6.5.7
 Summary: Tornado is a Python web framework and asynchronous networking 
library, originally developed at FriendFeed.
 Home-page: http://www.tornadoweb.org/
 Author: Facebook
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tornado-6.5.5/tornado.egg-info/SOURCES.txt 
new/tornado-6.5.7/tornado.egg-info/SOURCES.txt
--- old/tornado-6.5.5/tornado.egg-info/SOURCES.txt      2026-03-10 
22:14:52.000000000 +0100
+++ new/tornado-6.5.7/tornado.egg-info/SOURCES.txt      2026-06-08 
19:18:41.000000000 +0200
@@ -122,6 +122,8 @@
 docs/releases/v6.5.3.rst
 docs/releases/v6.5.4.rst
 docs/releases/v6.5.5.rst
+docs/releases/v6.5.6.rst
+docs/releases/v6.5.7.rst
 tornado/__init__.py
 tornado/__init__.pyi
 tornado/_locale_data.py

Reply via email to