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 2024-08-02 17:26:09 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-tornado6 (Old) and /work/SRC/openSUSE:Factory/.python-tornado6.new.7232 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-tornado6" Fri Aug 2 17:26:09 2024 rev:18 rq:1190823 version:6.4.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-tornado6/python-tornado6.changes 2024-05-20 18:09:31.917764967 +0200 +++ /work/SRC/openSUSE:Factory/.python-tornado6.new.7232/python-tornado6.changes 2024-08-02 17:26:25.355041172 +0200 @@ -1,0 +2,34 @@ +Wed Jul 31 09:32:23 UTC 2024 - Dominique Leuenberger <dims...@opensuse.org> + +- Update to version 6.4.1: + + Security Improvements: + - Parsing of the ``Transfer-Encoding`` header is now stricter. + Unexpected transfer-encoding values were previously ignored + and treated as the HTTP/1.0 default of read-until-close. This + can lead to framing issues with certain proxies. We now treat + any unexpected value as an error. + - Handling of whitespace in headers now matches the RFC more + closely. Only space and tab characters are treated as + whitespace and stripped from the beginning and end of header + values. Other unicode whitespace characters are now left + alone. This could also lead to framing issues with certain + proxies. + - `tornado.curl_httpclient` now prohibits carriage return and + linefeed headers in HTTP headers (matching the behavior of + `simple_httpclient`). These characters could be used for + header injection or request smuggling if untrusted data were + used in headers. + + General Changes: + - `tornado.iostream`: `SLIOStream` now understands changes to + error codes from OpenSSL 3.2. The main result of this change + is to reduce the noise in the logs for certain errors. + - `tornado.simple_httpclient`: `simple_httpclient` now + prohibits carriage return characters in HTTP headers. It had + previously prohibited only linefeed characters. + - `tornado.testing`: `.AsyncTestCase` subclasses can now be + instantiated without being associated with a test method. + Improves compatibility with test discovery in Pytest 8.2. +- Drop support-pytest-8.2.patch: fixed upstream. +- Drop openssl-3.2.patch: fixed upstream. + +------------------------------------------------------------------- Old: ---- openssl-3.2.patch support-pytest-8.2.patch tornado-6.4.tar.gz New: ---- tornado-6.4.1.tar.gz BETA DEBUG BEGIN: Old:- Drop support-pytest-8.2.patch: fixed upstream. - Drop openssl-3.2.patch: fixed upstream. Old: Improves compatibility with test discovery in Pytest 8.2. - Drop support-pytest-8.2.patch: fixed upstream. - Drop openssl-3.2.patch: fixed upstream. BETA DEBUG END: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-tornado6.spec ++++++ --- /var/tmp/diff_new_pack.0016rw/_old 2024-08-02 17:26:26.919105667 +0200 +++ /var/tmp/diff_new_pack.0016rw/_new 2024-08-02 17:26:26.919105667 +0200 @@ -19,7 +19,7 @@ %{?sle15_python_module_pythons} %define skip_python2 1 Name: python-tornado6 -Version: 6.4 +Version: 6.4.1 Release: 0 Summary: Open source version of scalable, non-blocking web server that power FriendFeed License: Apache-2.0 @@ -28,10 +28,6 @@ Source99: python-tornado6-rpmlintrc # PATCH-FIX-OPENSUSE ignore-resourcewarning-doctests.patch -- ignore resource warnings on OBS Patch0: ignore-resourcewarning-doctests.patch -# PATCH-FIX-OPENSUSE openssl-3.2.patch gh#tornadoweb/tornado#3355 -Patch1: openssl-3.2.patch -# PATCH-FIX-UPSTREAM gh#tornadoweb/tornado#3374 -Patch2: support-pytest-8.2.patch BuildRequires: %{python_module base >= 3.8} BuildRequires: %{python_module devel} BuildRequires: %{python_module pip} ++++++ tornado-6.4.tar.gz -> tornado-6.4.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/PKG-INFO new/tornado-6.4.1/PKG-INFO --- old/tornado-6.4/PKG-INFO 2023-11-29 04:20:19.857731000 +0100 +++ new/tornado-6.4.1/PKG-INFO 2024-06-06 20:12:53.604281000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: tornado -Version: 6.4 +Version: 6.4.1 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.4/docs/releases/v6.4.1.rst new/tornado-6.4.1/docs/releases/v6.4.1.rst --- old/tornado-6.4/docs/releases/v6.4.1.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/tornado-6.4.1/docs/releases/v6.4.1.rst 2024-06-06 20:12:50.000000000 +0200 @@ -0,0 +1,41 @@ +What's new in Tornado 6.4.1 +=========================== + +Jun 6, 2024 +----------- + +Security Improvements +~~~~~~~~~~~~~~~~~~~~~ + +- Parsing of the ``Transfer-Encoding`` header is now stricter. Unexpected transfer-encoding values + were previously ignored and treated as the HTTP/1.0 default of read-until-close. This can lead to + framing issues with certain proxies. We now treat any unexpected value as an error. +- Handling of whitespace in headers now matches the RFC more closely. Only space and tab characters + are treated as whitespace and stripped from the beginning and end of header values. Other unicode + whitespace characters are now left alone. This could also lead to framing issues with certain + proxies. +- ``tornado.curl_httpclient`` now prohibits carriage return and linefeed headers in HTTP headers + (matching the behavior of ``simple_httpclient``). These characters could be used for header + injection or request smuggling if untrusted data were used in headers. + +General Changes +~~~~~~~~~~~~~~~ + +`tornado.iostream` +~~~~~~~~~~~~~~~~~~ + +- `.SSLIOStream` now understands changes to error codes from OpenSSL 3.2. The main result of this + change is to reduce the noise in the logs for certain errors. + +``tornado.simple_httpclient`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``simple_httpclient`` now prohibits carriage return characters in HTTP headers. It had previously + prohibited only linefeed characters. + +`tornado.testing` +~~~~~~~~~~~~~~~~~ + +- `.AsyncTestCase` subclasses can now be instantiated without being associated with a test + method. This improves compatibility with test discovery in Pytest 8.2. + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/docs/releases.rst new/tornado-6.4.1/docs/releases.rst --- old/tornado-6.4/docs/releases.rst 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/docs/releases.rst 2024-06-06 20:12:50.000000000 +0200 @@ -4,6 +4,7 @@ .. toctree:: :maxdepth: 2 + releases/v6.4.1 releases/v6.4.0 releases/v6.3.3 releases/v6.3.2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/requirements.txt new/tornado-6.4.1/requirements.txt --- old/tornado-6.4/requirements.txt 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/requirements.txt 2024-06-06 20:12:50.000000000 +0200 @@ -8,7 +8,7 @@ # via sphinx babel==2.11.0 # via sphinx -black==22.12.0 +black==24.4.2 # via -r requirements.in build==0.10.0 # via pip-tools @@ -38,11 +38,11 @@ # virtualenv flake8==6.0.0 # via -r requirements.in -idna==3.4 +idna==3.7 # via requests imagesize==1.4.1 # via sphinx -jinja2==3.1.2 +jinja2==3.1.4 # via sphinx markupsafe==2.1.2 # via jinja2 @@ -56,6 +56,7 @@ # mypy packaging==23.1 # via + # black # build # pyproject-api # sphinx @@ -83,7 +84,7 @@ # via build pytz==2022.7.1 # via babel -requests==2.31.0 +requests==2.32.2 # via sphinx snowballstemmer==2.2.0 # via sphinx diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/__init__.py new/tornado-6.4.1/tornado/__init__.py --- old/tornado-6.4/tornado/__init__.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/__init__.py 2024-06-06 20:12:50.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.4" -version_info = (6, 4, 0, 0) +version = "6.4.1" +version_info = (6, 4, 0, 1) import importlib import typing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/concurrent.py new/tornado-6.4.1/tornado/concurrent.py --- old/tornado-6.4/tornado/concurrent.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/concurrent.py 2024-06-06 20:12:50.000000000 +0200 @@ -118,6 +118,7 @@ The ``callback`` argument was removed. """ + # Fully type-checking decorators is tricky, and this one is # discouraged anyway so it doesn't have all the generic magic. def run_on_executor_decorator(fn: Callable) -> Callable[..., Future]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/curl_httpclient.py new/tornado-6.4.1/tornado/curl_httpclient.py --- old/tornado-6.4/tornado/curl_httpclient.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/curl_httpclient.py 2024-06-06 20:12:50.000000000 +0200 @@ -19,6 +19,7 @@ import functools import logging import pycurl +import re import threading import time from io import BytesIO @@ -44,6 +45,8 @@ curl_log = logging.getLogger("tornado.curl_httpclient") +CR_OR_LF_RE = re.compile(b"\r|\n") + class CurlAsyncHTTPClient(AsyncHTTPClient): def initialize( # type: ignore @@ -347,14 +350,15 @@ if "Pragma" not in request.headers: request.headers["Pragma"] = "" - curl.setopt( - pycurl.HTTPHEADER, - [ - b"%s: %s" - % (native_str(k).encode("ASCII"), native_str(v).encode("ISO8859-1")) - for k, v in request.headers.get_all() - ], - ) + encoded_headers = [ + b"%s: %s" + % (native_str(k).encode("ASCII"), native_str(v).encode("ISO8859-1")) + for k, v in request.headers.get_all() + ] + for line in encoded_headers: + if CR_OR_LF_RE.search(line): + raise ValueError("Illegal characters in header (CR or LF): %r" % line) + curl.setopt(pycurl.HTTPHEADER, encoded_headers) curl.setopt( pycurl.HEADERFUNCTION, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/gen.py new/tornado-6.4.1/tornado/gen.py --- old/tornado-6.4/tornado/gen.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/gen.py 2024-06-06 20:12:50.000000000 +0200 @@ -66,6 +66,7 @@ via ``singledispatch``. """ + import asyncio import builtins import collections @@ -165,13 +166,11 @@ @overload def coroutine( func: Callable[..., "Generator[Any, Any, _T]"] -) -> Callable[..., "Future[_T]"]: - ... +) -> Callable[..., "Future[_T]"]: ... @overload -def coroutine(func: Callable[..., _T]) -> Callable[..., "Future[_T]"]: - ... +def coroutine(func: Callable[..., _T]) -> Callable[..., "Future[_T]"]: ... def coroutine( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/http1connection.py new/tornado-6.4.1/tornado/http1connection.py --- old/tornado-6.4/tornado/http1connection.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/http1connection.py 2024-06-06 20:12:50.000000000 +0200 @@ -38,6 +38,8 @@ from typing import cast, Optional, Type, Awaitable, Callable, Union, Tuple +CR_OR_LF_RE = re.compile(b"\r|\n") + class _QuietException(Exception): def __init__(self) -> None: @@ -389,14 +391,11 @@ self._request_start_line = start_line lines.append(utf8("%s %s HTTP/1.1" % (start_line[0], start_line[1]))) # Client requests with a non-empty body must have either a - # Content-Length or a Transfer-Encoding. + # Content-Length or a Transfer-Encoding. If Content-Length is not + # present we'll add our Transfer-Encoding below. self._chunking_output = ( start_line.method in ("POST", "PUT", "PATCH") and "Content-Length" not in headers - and ( - "Transfer-Encoding" not in headers - or headers["Transfer-Encoding"] == "chunked" - ) ) else: assert isinstance(start_line, httputil.ResponseStartLine) @@ -418,9 +417,6 @@ and (start_line.code < 100 or start_line.code >= 200) # No need to chunk the output if a Content-Length is specified. and "Content-Length" not in headers - # Applications are discouraged from touching Transfer-Encoding, - # but if they do, leave it alone. - and "Transfer-Encoding" not in headers ) # If connection to a 1.1 client will be closed, inform client if ( @@ -453,8 +449,8 @@ ) lines.extend(line.encode("latin1") for line in header_lines) for line in lines: - if b"\n" in line: - raise ValueError("Newline in header: " + repr(line)) + if CR_OR_LF_RE.search(line): + raise ValueError("Illegal characters (CR or LF) in header: %r" % line) future = None if self.stream.closed(): future = self._write_future = Future() @@ -560,7 +556,7 @@ return connection_header != "close" elif ( "Content-Length" in headers - or headers.get("Transfer-Encoding", "").lower() == "chunked" + or is_transfer_encoding_chunked(headers) or getattr(start_line, "method", None) in ("HEAD", "GET") ): # start_line may be a request or response start line; only @@ -598,13 +594,6 @@ delegate: httputil.HTTPMessageDelegate, ) -> Optional[Awaitable[None]]: if "Content-Length" in headers: - if "Transfer-Encoding" in headers: - # Response cannot contain both Content-Length and - # Transfer-Encoding headers. - # http://tools.ietf.org/html/rfc7230#section-3.3.3 - raise httputil.HTTPInputError( - "Response with both Transfer-Encoding and Content-Length" - ) if "," in headers["Content-Length"]: # Proxies sometimes cause Content-Length headers to get # duplicated. If all the values are identical then we can @@ -631,20 +620,22 @@ else: content_length = None + is_chunked = is_transfer_encoding_chunked(headers) + if code == 204: # This response code is not allowed to have a non-empty body, # and has an implicit length of zero instead of read-until-close. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3 - if "Transfer-Encoding" in headers or content_length not in (None, 0): + if is_chunked or content_length not in (None, 0): raise httputil.HTTPInputError( "Response with code %d should not have body" % code ) content_length = 0 + if is_chunked: + return self._read_chunked_body(delegate) if content_length is not None: return self._read_fixed_body(content_length, delegate) - if headers.get("Transfer-Encoding", "").lower() == "chunked": - return self._read_chunked_body(delegate) if self.is_client: return self._read_body_until_close(delegate) return None @@ -863,3 +854,33 @@ if HEXDIGITS.fullmatch(s) is None: raise ValueError("not a hexadecimal integer: %r" % s) return int(s, 16) + + +def is_transfer_encoding_chunked(headers: httputil.HTTPHeaders) -> bool: + """Returns true if the headers specify Transfer-Encoding: chunked. + + Raise httputil.HTTPInputError if any other transfer encoding is used. + """ + # Note that transfer-encoding is an area in which postel's law can lead + # us astray. If a proxy and a backend server are liberal in what they accept, + # but accept slightly different things, this can lead to mismatched framing + # and request smuggling issues. Therefore we are as strict as possible here + # (even technically going beyond the requirements of the RFCs: a value of + # ",chunked" is legal but doesn't appear in practice for legitimate traffic) + if "Transfer-Encoding" not in headers: + return False + if "Content-Length" in headers: + # Message cannot contain both Content-Length and + # Transfer-Encoding headers. + # http://tools.ietf.org/html/rfc7230#section-3.3.3 + raise httputil.HTTPInputError( + "Message with both Transfer-Encoding and Content-Length" + ) + if headers["Transfer-Encoding"].lower() == "chunked": + return True + # We do not support any transfer-encodings other than chunked, and we do not + # expect to add any support because the concept of transfer-encoding has + # been removed in HTTP/2. + raise httputil.HTTPInputError( + "Unsupported Transfer-Encoding %s" % headers["Transfer-Encoding"] + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/httputil.py new/tornado-6.4.1/tornado/httputil.py --- old/tornado-6.4/tornado/httputil.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/httputil.py 2024-06-06 20:12:50.000000000 +0200 @@ -62,6 +62,9 @@ from asyncio import Future # noqa: F401 import unittest # noqa: F401 +# To be used with str.strip() and related methods. +HTTP_WHITESPACE = " \t" + @lru_cache(1000) def _normalize_header(name: str) -> str: @@ -171,7 +174,7 @@ # continuation of a multi-line header if self._last_key is None: raise HTTPInputError("first header line cannot start with whitespace") - new_part = " " + line.lstrip() + new_part = " " + line.lstrip(HTTP_WHITESPACE) self._as_list[self._last_key][-1] += new_part self._dict[self._last_key] += new_part else: @@ -179,7 +182,7 @@ name, value = line.split(":", 1) except ValueError: raise HTTPInputError("no colon in header line") - self.add(name, value.strip()) + self.add(name, value.strip(HTTP_WHITESPACE)) @classmethod def parse(cls, headers: str) -> "HTTPHeaders": diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/iostream.py new/tornado-6.4.1/tornado/iostream.py --- old/tornado-6.4/tornado/iostream.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/iostream.py 2024-06-06 20:12:50.000000000 +0200 @@ -1374,7 +1374,7 @@ return elif err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN): return self.close(exc_info=err) - elif err.args[0] == ssl.SSL_ERROR_SSL: + elif err.args[0] in (ssl.SSL_ERROR_SSL, ssl.SSL_ERROR_SYSCALL): try: peer = self.socket.getpeername() except Exception: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/simple_httpclient.py new/tornado-6.4.1/tornado/simple_httpclient.py --- old/tornado-6.4/tornado/simple_httpclient.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/simple_httpclient.py 2024-06-06 20:12:50.000000000 +0200 @@ -429,9 +429,9 @@ self.request.method == "POST" and "Content-Type" not in self.request.headers ): - self.request.headers[ - "Content-Type" - ] = "application/x-www-form-urlencoded" + self.request.headers["Content-Type"] = ( + "application/x-www-form-urlencoded" + ) if self.request.decompress_response: self.request.headers["Accept-Encoding"] = "gzip" req_path = (self.parsed.path or "/") + ( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/test/__main__.py new/tornado-6.4.1/tornado/test/__main__.py --- old/tornado-6.4/tornado/test/__main__.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/test/__main__.py 2024-06-06 20:12:50.000000000 +0200 @@ -2,6 +2,7 @@ This only works in python 2.7+. """ + from tornado.test.runtests import all, main # tornado.testing.main autodiscovery relies on 'all' being present in diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/test/escape_test.py new/tornado-6.4.1/tornado/test/escape_test.py --- old/tornado-6.4/tornado/test/escape_test.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/test/escape_test.py 2024-06-06 20:12:50.000000000 +0200 @@ -194,9 +194,11 @@ ( "www.external-link.com and www.internal-link.com/blogs extra", { - "extra_params": lambda href: 'class="internal"' - if href.startswith("http://www.internal-link.com") - else 'rel="nofollow" class="external"' + "extra_params": lambda href: ( + 'class="internal"' + if href.startswith("http://www.internal-link.com") + else 'rel="nofollow" class="external"' + ) }, '<a href="http://www.external-link.com" rel="nofollow" class="external">www.external-link.com</a>' # noqa: E501 ' and <a href="http://www.internal-link.com/blogs" class="internal">www.internal-link.com/blogs</a> extra', # noqa: E501 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/test/httpclient_test.py new/tornado-6.4.1/tornado/test/httpclient_test.py --- old/tornado-6.4/tornado/test/httpclient_test.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/test/httpclient_test.py 2024-06-06 20:12:50.000000000 +0200 @@ -725,6 +725,22 @@ if el.logged_stack: break + def test_header_crlf(self): + # Ensure that the client doesn't allow CRLF injection in headers. RFC 9112 section 2.2 + # prohibits a bare CR specifically and "a recipient MAY recognize a single LF as a line + # terminator" so we check each character separately as well as the (redundant) CRLF pair. + for header, name in [ + ("foo\rbar:", "cr"), + ("foo\nbar:", "lf"), + ("foo\r\nbar:", "crlf"), + ]: + with self.subTest(name=name, position="value"): + with self.assertRaises(ValueError): + self.fetch("/hello", headers={"foo": header}) + with self.subTest(name=name, position="key"): + with self.assertRaises(ValueError): + self.fetch("/hello", headers={header: "foo"}) + class RequestProxyTest(unittest.TestCase): def test_request_set(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/test/httpserver_test.py new/tornado-6.4.1/tornado/test/httpserver_test.py --- old/tornado-6.4/tornado/test/httpserver_test.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/test/httpserver_test.py 2024-06-06 20:12:50.000000000 +0200 @@ -581,6 +581,76 @@ ) self.assertEqual(400, start_line.code) + def test_chunked_request_body_duplicate_header(self): + # Repeated Transfer-Encoding headers should be an error (and not confuse + # the chunked-encoding detection to mess up framing). + self.stream.write( + b"""\ +POST /echo HTTP/1.1 +Transfer-Encoding: chunked +Transfer-encoding: chunked + +2 +ok +0 + +""" + ) + with ExpectLog( + gen_log, + ".*Unsupported Transfer-Encoding chunked,chunked", + level=logging.INFO, + ): + start_line, headers, response = self.io_loop.run_sync( + lambda: read_stream_body(self.stream) + ) + self.assertEqual(400, start_line.code) + + def test_chunked_request_body_unsupported_transfer_encoding(self): + # We don't support transfer-encodings other than chunked. + self.stream.write( + b"""\ +POST /echo HTTP/1.1 +Transfer-Encoding: gzip, chunked + +2 +ok +0 + +""" + ) + with ExpectLog( + gen_log, ".*Unsupported Transfer-Encoding gzip, chunked", level=logging.INFO + ): + start_line, headers, response = self.io_loop.run_sync( + lambda: read_stream_body(self.stream) + ) + self.assertEqual(400, start_line.code) + + def test_chunked_request_body_transfer_encoding_and_content_length(self): + # Transfer-encoding and content-length are mutually exclusive + self.stream.write( + b"""\ +POST /echo HTTP/1.1 +Transfer-Encoding: chunked +Content-Length: 2 + +2 +ok +0 + +""" + ) + with ExpectLog( + gen_log, + ".*Message with both Transfer-Encoding and Content-Length", + level=logging.INFO, + ): + start_line, headers, response = self.io_loop.run_sync( + lambda: read_stream_body(self.stream) + ) + self.assertEqual(400, start_line.code) + @gen_test def test_invalid_content_length(self): # HTTP only allows decimal digits in content-length. Make sure we don't diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/test/httputil_test.py new/tornado-6.4.1/tornado/test/httputil_test.py --- old/tornado-6.4/tornado/test/httputil_test.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/test/httputil_test.py 2024-06-06 20:12:50.000000000 +0200 @@ -334,6 +334,25 @@ gen_log.warning("failed while trying %r in %s", newline, encoding) raise + def test_unicode_whitespace(self): + # Only tabs and spaces are to be stripped according to the HTTP standard. + # Other unicode whitespace is to be left as-is. In the context of headers, + # this specifically means the whitespace characters falling within the + # latin1 charset. + whitespace = [ + (" ", True), # SPACE + ("\t", True), # TAB + ("\u00a0", False), # NON-BREAKING SPACE + ("\u0085", False), # NEXT LINE + ] + for c, stripped in whitespace: + headers = HTTPHeaders.parse("Transfer-Encoding: %schunked" % c) + if stripped: + expected = [("Transfer-Encoding", "chunked")] + else: + expected = [("Transfer-Encoding", "%schunked" % c)] + self.assertEqual(expected, list(headers.get_all())) + def test_optional_cr(self): # Both CRLF and LF should be accepted as separators. CR should not be # part of the data when followed by LF, but it is a normal char diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/test/ioloop_test.py new/tornado-6.4.1/tornado/test/ioloop_test.py --- old/tornado-6.4/tornado/test/ioloop_test.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/test/ioloop_test.py 2024-06-06 20:12:50.000000000 +0200 @@ -261,6 +261,7 @@ the object should be closed (by IOLoop.close(all_fds=True), not just the fd. """ + # Use a socket since they are supported by IOLoop on all platforms. # Unfortunately, sockets don't support the .closed attribute for # inspecting their close status, so we must use a wrapper. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/test/simple_httpclient_test.py new/tornado-6.4.1/tornado/test/simple_httpclient_test.py --- old/tornado-6.4/tornado/test/simple_httpclient_test.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/test/simple_httpclient_test.py 2024-06-06 20:12:50.000000000 +0200 @@ -828,7 +828,7 @@ with ExpectLog( gen_log, ( - "Malformed HTTP message from None: Response " + "Malformed HTTP message from None: Message " "with both Transfer-Encoding and Content-Length" ), level=logging.INFO, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/test/testing_test.py new/tornado-6.4.1/tornado/test/testing_test.py --- old/tornado-6.4/tornado/test/testing_test.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/test/testing_test.py 2024-06-06 20:12:50.000000000 +0200 @@ -5,7 +5,6 @@ from tornado.web import Application import asyncio import contextlib -import inspect import gc import os import platform @@ -118,7 +117,11 @@ super().tearDown() -class AsyncTestCaseWrapperTest(unittest.TestCase): +class AsyncTestCaseReturnAssertionsTest(unittest.TestCase): + # These tests verify that tests that return non-None values (without being decorated with + # @gen_test) raise errors instead of incorrectly succeeding. These tests should be removed or + # updated when the _callTestMethod method is removed from AsyncTestCase (the same checks will + # still happen, but they'll be performed in the stdlib as DeprecationWarnings) def test_undecorated_generator(self): class Test(AsyncTestCase): def test_gen(self): @@ -135,7 +138,10 @@ "pypy destructor warnings cannot be silenced", ) @unittest.skipIf( - sys.version_info >= (3, 12), "py312 has its own check for test case returns" + # This check actually exists in 3.11 but it changed in 3.12 in a way that breaks + # this test. + sys.version_info >= (3, 12), + "py312 has its own check for test case returns", ) def test_undecorated_coroutine(self): class Test(AsyncTestCase): @@ -176,17 +182,6 @@ self.assertEqual(len(result.errors), 1) self.assertIn("Return value from test method ignored", result.errors[0][1]) - def test_unwrap(self): - class Test(AsyncTestCase): - def test_foo(self): - pass - - test = Test("test_foo") - self.assertIs( - inspect.unwrap(test.test_foo), - test.test_foo.orig_method, # type: ignore[attr-defined] - ) - class SetUpTearDownTest(unittest.TestCase): def test_set_up_tear_down(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/testing.py new/tornado-6.4.1/tornado/testing.py --- old/tornado-6.4/tornado/testing.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/testing.py 2024-06-06 20:12:50.000000000 +0200 @@ -84,39 +84,6 @@ return 5 -class _TestMethodWrapper(object): - """Wraps a test method to raise an error if it returns a value. - - This is mainly used to detect undecorated generators (if a test - method yields it must use a decorator to consume the generator), - but will also detect other kinds of return values (these are not - necessarily errors, but we alert anyway since there is no good - reason to return a value from a test). - """ - - def __init__(self, orig_method: Callable) -> None: - self.orig_method = orig_method - self.__wrapped__ = orig_method - - def __call__(self, *args: Any, **kwargs: Any) -> None: - result = self.orig_method(*args, **kwargs) - if isinstance(result, Generator) or inspect.iscoroutine(result): - raise TypeError( - "Generator and coroutine test methods should be" - " decorated with tornado.testing.gen_test" - ) - elif result is not None: - raise ValueError("Return value from test method ignored: %r" % result) - - def __getattr__(self, name: str) -> Any: - """Proxy all unknown attributes to the original method. - - This is important for some of the decorators in the `unittest` - module, such as `unittest.skipIf`. - """ - return getattr(self.orig_method, name) - - class AsyncTestCase(unittest.TestCase): """`~unittest.TestCase` subclass for testing `.IOLoop`-based asynchronous code. @@ -173,12 +140,6 @@ self.__stop_args = None # type: Any self.__timeout = None # type: Optional[object] - # It's easy to forget the @gen_test decorator, but if you do - # the test will silently be ignored because nothing will consume - # the generator. Replace the test method with a wrapper that will - # make sure it's not an undecorated generator. - setattr(self, methodName, _TestMethodWrapper(getattr(self, methodName))) - # Not used in this class itself, but used by @gen_test self._test_generator = None # type: Optional[Union[Generator, Coroutine]] @@ -289,6 +250,30 @@ self.__rethrow() return ret + def _callTestMethod(self, method: Callable) -> None: + """Run the given test method, raising an error if it returns non-None. + + Failure to decorate asynchronous test methods with ``@gen_test`` can lead to tests + incorrectly passing. + + Remove this override when Python 3.10 support is dropped. This check (in the form of a + DeprecationWarning) became a part of the standard library in 3.11. + + Note that ``_callTestMethod`` is not documented as a public interface. However, it is + present in all supported versions of Python (3.8+), and if it goes away in the future that's + OK because we can just remove this override as noted above. + """ + # Calling super()._callTestMethod would hide the return value, even in python 3.8-3.10 + # where the check isn't being done for us. + result = method() + if isinstance(result, Generator) or inspect.iscoroutine(result): + raise TypeError( + "Generator and coroutine test methods should be" + " decorated with tornado.testing.gen_test" + ) + elif result is not None: + raise ValueError("Return value from test method ignored: %r" % result) + def stop(self, _arg: Any = None, **kwargs: Any) -> None: """Stops the `.IOLoop`, causing one pending (or future) call to `wait()` to return. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado/websocket.py new/tornado-6.4.1/tornado/websocket.py --- old/tornado-6.4/tornado/websocket.py 2023-11-29 04:20:18.000000000 +0100 +++ new/tornado-6.4.1/tornado/websocket.py 2024-06-06 20:12:50.000000000 +0200 @@ -1392,9 +1392,9 @@ # from the server). # TODO: set server parameters for deflate extension # if requested in self.compression_options. - request.headers[ - "Sec-WebSocket-Extensions" - ] = "permessage-deflate; client_max_window_bits" + request.headers["Sec-WebSocket-Extensions"] = ( + "permessage-deflate; client_max_window_bits" + ) # Websocket connection is currently unable to follow redirects request.follow_redirects = False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tornado-6.4/tornado.egg-info/PKG-INFO new/tornado-6.4.1/tornado.egg-info/PKG-INFO --- old/tornado-6.4/tornado.egg-info/PKG-INFO 2023-11-29 04:20:19.000000000 +0100 +++ new/tornado-6.4.1/tornado.egg-info/PKG-INFO 2024-06-06 20:12:53.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: tornado -Version: 6.4 +Version: 6.4.1 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.4/tornado.egg-info/SOURCES.txt new/tornado-6.4.1/tornado.egg-info/SOURCES.txt --- old/tornado-6.4/tornado.egg-info/SOURCES.txt 2023-11-29 04:20:19.000000000 +0100 +++ new/tornado-6.4.1/tornado.egg-info/SOURCES.txt 2024-06-06 20:12:53.000000000 +0200 @@ -114,6 +114,7 @@ docs/releases/v6.3.2.rst docs/releases/v6.3.3.rst docs/releases/v6.4.0.rst +docs/releases/v6.4.1.rst tornado/__init__.py tornado/_locale_data.py tornado/auth.py