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