Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-waitress for openSUSE:Factory
checked in at 2024-10-31 16:08:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-waitress (Old)
and /work/SRC/openSUSE:Factory/.python-waitress.new.2020 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-waitress"
Thu Oct 31 16:08:55 2024 rev:33 rq:1219322 version:3.0.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-waitress/python-waitress.changes
2024-07-03 20:29:28.463251390 +0200
+++
/work/SRC/openSUSE:Factory/.python-waitress.new.2020/python-waitress.changes
2024-10-31 16:09:02.266238438 +0100
@@ -1,0 +2,18 @@
+Wed Oct 30 06:49:46 UTC 2024 - Daniel Garcia <[email protected]>
+
+- Update to 3.0.1 (bsc#1232554, bsc#1232556, CVE-2024-49769, CVE-2024-49768):
+ * Fix a bug that would lead to Waitress busy looping on select()
+ on a half-open socket due to a race condition that existed when
+ creating a new HTTPChannel. See
+ https://github.com/Pylons/waitress/pull/435,
+ https://github.com/Pylons/waitress/issues/418 and
+
https://github.com/Pylons/waitress/security/advisories/GHSA-3f84-rpwh-47g6
+ * No longer strip the header values before passing them to the
+ WSGI environ. See https://github.com/Pylons/waitress/pull/434
+ and https://github.com/Pylons/waitress/issues/432
+ * Fix a race condition in Waitress when
+ `channel_request_lookahead` is enabled that could lead to HTTP
+ request smuggling.
+ * See
https://github.com/Pylons/waitress/security/advisories/GHSA-9298-4cf8-g4wj
+
+-------------------------------------------------------------------
Old:
----
waitress-3.0.0.tar.gz
New:
----
waitress-3.0.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-waitress.spec ++++++
--- /var/tmp/diff_new_pack.TI4UFQ/_old 2024-10-31 16:09:03.818303200 +0100
+++ /var/tmp/diff_new_pack.TI4UFQ/_new 2024-10-31 16:09:03.830303701 +0100
@@ -31,7 +31,7 @@
%endif
%{?sle15_python_module_pythons}
Name: python-waitress%{psuffix}
-Version: 3.0.0
+Version: 3.0.1
Release: 0
Summary: Waitress WSGI server
License: ZPL-2.1
++++++ waitress-3.0.0.tar.gz -> waitress-3.0.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/.github/workflows/ci-tests.yml
new/waitress-3.0.1/.github/workflows/ci-tests.yml
--- old/waitress-3.0.0/.github/workflows/ci-tests.yml 2024-02-04
23:39:05.000000000 +0100
+++ new/waitress-3.0.1/.github/workflows/ci-tests.yml 2024-10-27
02:15:47.000000000 +0100
@@ -7,6 +7,7 @@
- main
- "[0-9].[0-9]+-branch"
tags:
+ - "*"
# Build pull requests
pull_request:
@@ -15,44 +16,62 @@
strategy:
matrix:
py:
- - "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- - "pypy-3.8"
+ - "3.13"
- "pypy-3.9"
- "pypy-3.10"
# Pre-release
os:
- - "ubuntu-latest"
+ - "ubuntu-22.04"
- "windows-latest"
- - "macos-latest"
+ - "macos-14" # arm64
+ - "macos-13" # x64
architecture:
- x64
- x86
+ - arm64
include:
- - py: "pypy-3.8"
- toxenv: "pypy38"
- - py: "pypy-3.9"
- toxenv: "pypy39"
- - py: "pypy-3.10"
- toxenv: "pypy310"
+ - py: "pypy-3.9"
+ toxenv: "pypy39"
+ - py: "pypy-3.10"
+ toxenv: "pypy310"
exclude:
- # Linux and macOS don't have x86 python
- - os: "ubuntu-latest"
+ # Ubuntu does not have x86/arm64 Python
+ - os: "ubuntu-22.04"
architecture: x86
- - os: "macos-latest"
+ - os: "ubuntu-22.04"
+ architecture: arm64
+ # MacOS we need to make sure to remove x86 on all
+ # We need to run no arm64 on macos-13 (Intel), but some
+ # Python versions: 3.9/3.10
+ #
+ # From 3.11 onward, there is support for running x64 and
+ # arm64 on Apple Silicon based systems (macos-14)
+ - os: "macos-13"
architecture: x86
+ - os: "macos-13"
+ architecture: arm64
+ - os: "macos-14"
+ architecture: x86
+ - os: "macos-14"
+ architecture: x64
+ py: "3.9"
+ - os: "macos-14"
+ architecture: x64
+ py: "3.10"
+ # Windows does not have arm64 releases
+ - os: "windows-latest"
+ architecture: arm64
# Don't run all PyPy versions except latest on
# Windows/macOS. They are expensive to run.
- os: "windows-latest"
- py: "pypy-3.8"
- - os: "macos-latest"
- py: "pypy-3.8"
- - os: "windows-latest"
py: "pypy-3.9"
- - os: "macos-latest"
+ - os: "macos-13"
+ py: "pypy-3.9"
+ - os: "macos-14"
py: "pypy-3.9"
name: "Python: ${{ matrix.py }}-${{ matrix.architecture }} on ${{
matrix.os }}"
@@ -75,39 +94,39 @@
run: tox -e py
coverage:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
name: Validate coverage
steps:
- uses: actions/checkout@v4
- - name: Setup python 3.10
+ - name: Setup python
uses: actions/setup-python@v5
with:
- python-version: "3.10"
+ python-version: "3.13"
architecture: x64
- run: pip install tox
- - run: tox -e py310,coverage
+ - run: tox -e py313,coverage
docs:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
name: Build the documentation
steps:
- uses: actions/checkout@v4
- name: Setup python
uses: actions/setup-python@v5
with:
- python-version: "3.10"
+ python-version: "3.13"
architecture: x64
- run: pip install tox
- run: tox -e docs
lint:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
name: Lint the package
steps:
- uses: actions/checkout@v4
- name: Setup python
uses: actions/setup-python@v5
with:
- python-version: "3.10"
+ python-version: "3.13"
architecture: x64
- run: pip install tox
- run: tox -e lint
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/.readthedocs.yaml
new/waitress-3.0.1/.readthedocs.yaml
--- old/waitress-3.0.0/.readthedocs.yaml 1970-01-01 01:00:00.000000000
+0100
+++ new/waitress-3.0.1/.readthedocs.yaml 2024-06-08 23:51:37.000000000
+0200
@@ -0,0 +1,17 @@
+# https://docs.readthedocs.io/en/stable/config-file/v2.html
+version: 2
+build:
+ os: ubuntu-22.04
+ tools:
+ python: '3.12'
+sphinx:
+ configuration: docs/conf.py
+formats:
+ - pdf
+ - epub
+python:
+ install:
+ - method: pip
+ path: .
+ extra_requirements:
+ - docs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/CHANGES.txt
new/waitress-3.0.1/CHANGES.txt
--- old/waitress-3.0.0/CHANGES.txt 2024-02-05 00:30:04.000000000 +0100
+++ new/waitress-3.0.1/CHANGES.txt 2024-10-29 01:09:00.000000000 +0100
@@ -1,3 +1,27 @@
+3.0.1 (2024-11-28)
+------------------
+
+Security
+~~~~~~~~
+
+- Fix a bug that would lead to Waitress busy looping on select() on a half-open
+ socket due to a race condition that existed when creating a new HTTPChannel.
+ See https://github.com/Pylons/waitress/pull/435,
+ https://github.com/Pylons/waitress/issues/418 and
+ https://github.com/Pylons/waitress/security/advisories/GHSA-3f84-rpwh-47g6
+
+ With thanks to Dylan Jay and Dieter Maurer for their extensive debugging and
+ helping track this down.
+
+- No longer strip the header values before passing them to the WSGI environ.
+ See https://github.com/Pylons/waitress/pull/434 and
+ https://github.com/Pylons/waitress/issues/432
+
+- Fix a race condition in Waitress when `channel_request_lookahead` is enabled
+ that could lead to HTTP request smuggling.
+
+ See
https://github.com/Pylons/waitress/security/advisories/GHSA-9298-4cf8-g4wj
+
3.0.0 (2024-02-04)
------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/MANIFEST.in
new/waitress-3.0.1/MANIFEST.in
--- old/waitress-3.0.0/MANIFEST.in 2022-01-14 03:57:32.000000000 +0100
+++ new/waitress-3.0.1/MANIFEST.in 2024-06-09 00:10:10.000000000 +0200
@@ -14,7 +14,7 @@
include pyproject.toml setup.cfg
include .coveragerc .flake8
-include tox.ini rtd.txt
+include tox.ini .readthedocs.yaml
exclude TODO.txt
prune docs/_build
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/PKG-INFO new/waitress-3.0.1/PKG-INFO
--- old/waitress-3.0.0/PKG-INFO 2024-02-05 00:32:02.214200300 +0100
+++ new/waitress-3.0.1/PKG-INFO 2024-10-29 01:11:17.053294000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: waitress
-Version: 3.0.0
+Version: 3.0.1
Summary: Waitress WSGI server
Home-page: https://github.com/Pylons/waitress
Author: Zope Foundation and Contributors
@@ -18,17 +18,17 @@
Classifier: License :: OSI Approved :: Zope Public License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Operating System :: OS Independent
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
-Requires-Python: >=3.8.0
+Requires-Python: >=3.9.0
Description-Content-Type: text/x-rst
License-File: LICENSE.txt
Provides-Extra: testing
@@ -50,19 +50,43 @@
.. image::
https://github.com/Pylons/waitress/actions/workflows/ci-tests.yml/badge.svg
:target: https://github.com/Pylons/waitress/actions/workflows/ci-tests.yml
-.. image:: https://readthedocs.org/projects/waitress/badge/?version=main
- :target: https://docs.pylonsproject.org/projects/waitress/en/main
+.. image:: https://readthedocs.org/projects/waitress/badge/?version=stable
+ :target: https://docs.pylonsproject.org/projects/waitress/en/stable/
:alt: main Documentation Status
Waitress is a production-quality pure-Python WSGI server with very acceptable
performance. It has no dependencies except ones which live in the Python
-standard library. It runs on CPython on Unix and Windows under Python 3.8+. It
-is also known to run on PyPy 3 (version 3.8 compatible python and above) on
+standard library. It runs on CPython on Unix and Windows under Python 3.9+. It
+is also known to run on PyPy 3 (version 3.9 compatible python and above) on
UNIX. It supports HTTP/1.0 and HTTP/1.1.
For more information, see the "docs" directory of the Waitress package or visit
https://docs.pylonsproject.org/projects/waitress/en/latest/
+3.0.1 (2024-11-28)
+------------------
+
+Security
+~~~~~~~~
+
+- Fix a bug that would lead to Waitress busy looping on select() on a half-open
+ socket due to a race condition that existed when creating a new HTTPChannel.
+ See https://github.com/Pylons/waitress/pull/435,
+ https://github.com/Pylons/waitress/issues/418 and
+ https://github.com/Pylons/waitress/security/advisories/GHSA-3f84-rpwh-47g6
+
+ With thanks to Dylan Jay and Dieter Maurer for their extensive debugging and
+ helping track this down.
+
+- No longer strip the header values before passing them to the WSGI environ.
+ See https://github.com/Pylons/waitress/pull/434 and
+ https://github.com/Pylons/waitress/issues/432
+
+- Fix a race condition in Waitress when `channel_request_lookahead` is enabled
+ that could lead to HTTP request smuggling.
+
+ See
https://github.com/Pylons/waitress/security/advisories/GHSA-9298-4cf8-g4wj
+
3.0.0 (2024-02-04)
------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/README.rst
new/waitress-3.0.1/README.rst
--- old/waitress-3.0.0/README.rst 2024-02-04 23:39:05.000000000 +0100
+++ new/waitress-3.0.1/README.rst 2024-10-27 02:15:47.000000000 +0100
@@ -8,14 +8,14 @@
.. image::
https://github.com/Pylons/waitress/actions/workflows/ci-tests.yml/badge.svg
:target: https://github.com/Pylons/waitress/actions/workflows/ci-tests.yml
-.. image:: https://readthedocs.org/projects/waitress/badge/?version=main
- :target: https://docs.pylonsproject.org/projects/waitress/en/main
+.. image:: https://readthedocs.org/projects/waitress/badge/?version=stable
+ :target: https://docs.pylonsproject.org/projects/waitress/en/stable/
:alt: main Documentation Status
Waitress is a production-quality pure-Python WSGI server with very acceptable
performance. It has no dependencies except ones which live in the Python
-standard library. It runs on CPython on Unix and Windows under Python 3.8+. It
-is also known to run on PyPy 3 (version 3.8 compatible python and above) on
+standard library. It runs on CPython on Unix and Windows under Python 3.9+. It
+is also known to run on PyPy 3 (version 3.9 compatible python and above) on
UNIX. It supports HTTP/1.0 and HTTP/1.1.
For more information, see the "docs" directory of the Waitress package or visit
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/docs/arguments.rst
new/waitress-3.0.1/docs/arguments.rst
--- old/waitress-3.0.0/docs/arguments.rst 2024-02-05 00:09:10.000000000
+0100
+++ new/waitress-3.0.1/docs/arguments.rst 2024-10-29 01:06:11.000000000
+0100
@@ -314,3 +314,17 @@
be stripped of the prefix.
Default: ``''``
+
+channel_request_lookahead
+ Sets the amount of requests we can continue to read from the socket, while
+ we are processing current requests. The default value won't allow any
+ lookahead, increase it above ``0`` to enable.
+
+ When enabled this inserts a callable ``waitress.client_disconnected`` into
+ the environment that allows the task to check if the client disconnected
+ while waiting for the response at strategic points in the execution and to
+ cancel the operation.
+
+ Default: ``0``
+
+ .. versionadded:: 2.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/rtd.txt new/waitress-3.0.1/rtd.txt
--- old/waitress-3.0.0/rtd.txt 2017-09-15 22:45:36.000000000 +0200
+++ new/waitress-3.0.1/rtd.txt 1970-01-01 01:00:00.000000000 +0100
@@ -1,3 +0,0 @@
-Sphinx >= 1.3.1
-repoze.sphinx.autointerface
-pylons-sphinx-themes
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/setup.cfg new/waitress-3.0.1/setup.cfg
--- old/waitress-3.0.0/setup.cfg 2024-02-05 00:32:02.214689500 +0100
+++ new/waitress-3.0.1/setup.cfg 2024-10-29 01:11:17.053846600 +0100
@@ -1,6 +1,6 @@
[metadata]
name = waitress
-version = 3.0.0
+version = 3.0.1
description = Waitress WSGI server
long_description = file: README.rst, CHANGES.txt
long_description_content_type = text/x-rst
@@ -13,11 +13,11 @@
License :: OSI Approved :: Zope Public License
Programming Language :: Python
Programming Language :: Python :: 3
- Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
+ Programming Language :: Python :: 3.13
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Operating System :: OS Independent
@@ -37,7 +37,7 @@
package_dir =
=src
packages = find:
-python_requires = >=3.8.0
+python_requires = >=3.9.0
[options.entry_points]
paste.server_runner =
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/src/waitress/channel.py
new/waitress-3.0.1/src/waitress/channel.py
--- old/waitress-3.0.0/src/waitress/channel.py 2022-12-24 22:07:01.000000000
+0100
+++ new/waitress-3.0.1/src/waitress/channel.py 2024-10-29 01:06:11.000000000
+0100
@@ -67,8 +67,7 @@
self.outbuf_lock = threading.Condition()
wasyncore.dispatcher.__init__(self, sock, map=map)
-
- # Don't let wasyncore.dispatcher throttle self.addr on us.
+ self.connected = True
self.addr = addr
self.requests = []
@@ -92,13 +91,7 @@
# Precondition: there's data in the out buffer to be sent, or
# there's a pending will_close request
- if not self.connected:
- # we dont want to close the channel twice
-
- return
-
# try to flush any pending output
-
if not self.requests:
# 1. There are no running tasks, so we don't need to try to lock
# the outbuf before sending
@@ -147,7 +140,7 @@
# 1. We're not already about to close the connection.
# 2. We're not waiting to flush remaining data before closing the
# connection
- # 3. There are not too many tasks already queued
+ # 3. There are not too many tasks already queued (if lookahead is
enabled)
# 4. There's no data in the output buffer that needs to be sent
# before we potentially create a new task.
@@ -203,6 +196,15 @@
return False
with self.requests_lock:
+ # Don't bother processing anymore data if this connection is about
+ # to close. This may happen if readable() returned True, on the
+ # main thread before the service thread set the close_when_flushed
+ # flag, and we read data but our service thread is attempting to
+ # shut down the connection due to an error. We want to make sure we
+ # do this while holding the request_lock so that we can't race
+ if self.will_close or self.close_when_flushed:
+ return False
+
while data:
if self.request is None:
self.request = self.parser_class(self.adj)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/src/waitress/task.py
new/waitress-3.0.1/src/waitress/task.py
--- old/waitress-3.0.0/src/waitress/task.py 2024-02-04 23:52:18.000000000
+0100
+++ new/waitress-3.0.1/src/waitress/task.py 2024-03-03 22:56:33.000000000
+0100
@@ -557,7 +557,6 @@
}
for key, value in dict(request.headers).items():
- value = value.strip()
mykey = rename_headers.get(key, None)
if mykey is None:
mykey = "HTTP_" + key
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/src/waitress/wasyncore.py
new/waitress-3.0.1/src/waitress/wasyncore.py
--- old/waitress-3.0.0/src/waitress/wasyncore.py 2024-02-04
23:52:18.000000000 +0100
+++ new/waitress-3.0.1/src/waitress/wasyncore.py 2024-10-27
02:34:09.000000000 +0100
@@ -297,22 +297,6 @@
# get a socket from a blocking source.
sock.setblocking(0)
self.set_socket(sock, map)
- self.connected = True
- # The constructor no longer requires that the socket
- # passed be connected.
- try:
- self.addr = sock.getpeername()
- except OSError as err:
- if err.args[0] in (ENOTCONN, EINVAL):
- # To handle the case where we got an unconnected
- # socket.
- self.connected = False
- else:
- # The socket is broken in some unknown way, alert
- # the user and remove it from the map (to prevent
- # polling of broken sockets).
- self.del_channel(map)
- raise
else:
self.socket = None
@@ -394,23 +378,6 @@
self.addr = addr
return self.socket.bind(addr)
- def connect(self, address):
- self.connected = False
- self.connecting = True
- err = self.socket.connect_ex(address)
- if (
- err in (EINPROGRESS, EALREADY, EWOULDBLOCK)
- or err == EINVAL
- and os.name == "nt"
- ): # pragma: no cover
- self.addr = address
- return
- if err in (0, EISCONN):
- self.addr = address
- self.handle_connect_event()
- else:
- raise OSError(err, errorcode[err])
-
def accept(self):
# XXX can return either an address pair or None
try:
@@ -469,6 +436,8 @@
if why.args[0] not in (ENOTCONN, EBADF):
raise
+ self.socket = None
+
# log and log_info may be overridden to provide more sophisticated
# logging and warning methods. In general, log is for 'hit' logging
# and 'log_info' is for informational, warning and error logging.
@@ -519,7 +488,11 @@
# handle_expt_event() is called if there might be an error on the
# socket, or if there is OOB data
# check for the error condition first
- err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+ err = (
+ self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+ if self.socket is not None
+ else 1
+ )
if err != 0:
# we can get here when select.select() says that there is an
# exceptional condition on the socket
@@ -572,34 +545,6 @@
self.close()
-# ---------------------------------------------------------------------------
-# adds simple buffered output capability, useful for simple clients.
-# [for more sophisticated usage use asynchat.async_chat]
-# ---------------------------------------------------------------------------
-
-
-class dispatcher_with_send(dispatcher):
- def __init__(self, sock=None, map=None):
- dispatcher.__init__(self, sock, map)
- self.out_buffer = b""
-
- def initiate_send(self):
- num_sent = 0
- num_sent = dispatcher.send(self, self.out_buffer[:65536])
- self.out_buffer = self.out_buffer[num_sent:]
-
- handle_write = initiate_send
-
- def writable(self):
- return (not self.connected) or len(self.out_buffer)
-
- def send(self, data):
- if self.debug: # pragma: no cover
- self.log_info("sending %s" % repr(data))
- self.out_buffer = self.out_buffer + data
- self.initiate_send()
-
-
def close_all(map=None, ignore_all=False):
if map is None: # pragma: no cover
map = socket_map
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/src/waitress.egg-info/PKG-INFO
new/waitress-3.0.1/src/waitress.egg-info/PKG-INFO
--- old/waitress-3.0.0/src/waitress.egg-info/PKG-INFO 2024-02-05
00:32:02.000000000 +0100
+++ new/waitress-3.0.1/src/waitress.egg-info/PKG-INFO 2024-10-29
01:11:17.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: waitress
-Version: 3.0.0
+Version: 3.0.1
Summary: Waitress WSGI server
Home-page: https://github.com/Pylons/waitress
Author: Zope Foundation and Contributors
@@ -18,17 +18,17 @@
Classifier: License :: OSI Approved :: Zope Public License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Operating System :: OS Independent
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
-Requires-Python: >=3.8.0
+Requires-Python: >=3.9.0
Description-Content-Type: text/x-rst
License-File: LICENSE.txt
Provides-Extra: testing
@@ -50,19 +50,43 @@
.. image::
https://github.com/Pylons/waitress/actions/workflows/ci-tests.yml/badge.svg
:target: https://github.com/Pylons/waitress/actions/workflows/ci-tests.yml
-.. image:: https://readthedocs.org/projects/waitress/badge/?version=main
- :target: https://docs.pylonsproject.org/projects/waitress/en/main
+.. image:: https://readthedocs.org/projects/waitress/badge/?version=stable
+ :target: https://docs.pylonsproject.org/projects/waitress/en/stable/
:alt: main Documentation Status
Waitress is a production-quality pure-Python WSGI server with very acceptable
performance. It has no dependencies except ones which live in the Python
-standard library. It runs on CPython on Unix and Windows under Python 3.8+. It
-is also known to run on PyPy 3 (version 3.8 compatible python and above) on
+standard library. It runs on CPython on Unix and Windows under Python 3.9+. It
+is also known to run on PyPy 3 (version 3.9 compatible python and above) on
UNIX. It supports HTTP/1.0 and HTTP/1.1.
For more information, see the "docs" directory of the Waitress package or visit
https://docs.pylonsproject.org/projects/waitress/en/latest/
+3.0.1 (2024-11-28)
+------------------
+
+Security
+~~~~~~~~
+
+- Fix a bug that would lead to Waitress busy looping on select() on a half-open
+ socket due to a race condition that existed when creating a new HTTPChannel.
+ See https://github.com/Pylons/waitress/pull/435,
+ https://github.com/Pylons/waitress/issues/418 and
+ https://github.com/Pylons/waitress/security/advisories/GHSA-3f84-rpwh-47g6
+
+ With thanks to Dylan Jay and Dieter Maurer for their extensive debugging and
+ helping track this down.
+
+- No longer strip the header values before passing them to the WSGI environ.
+ See https://github.com/Pylons/waitress/pull/434 and
+ https://github.com/Pylons/waitress/issues/432
+
+- Fix a race condition in Waitress when `channel_request_lookahead` is enabled
+ that could lead to HTTP request smuggling.
+
+ See
https://github.com/Pylons/waitress/security/advisories/GHSA-9298-4cf8-g4wj
+
3.0.0 (2024-02-04)
------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/src/waitress.egg-info/SOURCES.txt
new/waitress-3.0.1/src/waitress.egg-info/SOURCES.txt
--- old/waitress-3.0.0/src/waitress.egg-info/SOURCES.txt 2024-02-05
00:32:02.000000000 +0100
+++ new/waitress-3.0.1/src/waitress.egg-info/SOURCES.txt 2024-10-29
01:11:17.000000000 +0100
@@ -1,5 +1,6 @@
.coveragerc
.flake8
+.readthedocs.yaml
CHANGES.txt
CONTRIBUTORS.txt
COPYRIGHT.txt
@@ -10,7 +11,6 @@
RELEASING.txt
contributing.md
pyproject.toml
-rtd.txt
setup.cfg
setup.py
tox.ini
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/tests/test_channel.py
new/waitress-3.0.1/tests/test_channel.py
--- old/waitress-3.0.0/tests/test_channel.py 2024-02-04 23:52:18.000000000
+0100
+++ new/waitress-3.0.1/tests/test_channel.py 2024-10-29 01:06:11.000000000
+0100
@@ -18,7 +18,7 @@
map = {}
inst = self._makeOne(sock, "127.0.0.1", adj, map=map)
inst.outbuf_lock = DummyLock()
- return inst, sock, map
+ return inst, sock.local(), map
def test_ctor(self):
inst, _, map = self._makeOneWithMap()
@@ -218,7 +218,7 @@
def send(_):
return 0
- sock.send = send
+ sock.remote.send = send
wrote = inst.write_soon(b"a")
self.assertEqual(wrote, 1)
@@ -236,7 +236,7 @@
def send(_):
return 0
- sock.send = send
+ sock.remote.send = send
outbufs = inst.outbufs
wrote = inst.write_soon(wrapper)
@@ -270,7 +270,7 @@
def send(_):
return 0
- sock.send = send
+ sock.remote.send = send
inst.adj.outbuf_high_watermark = 3
inst.current_outbuf_count = 4
@@ -286,7 +286,7 @@
def send(_):
return 0
- sock.send = send
+ sock.remote.send = send
inst.adj.outbuf_high_watermark = 3
inst.total_outbufs_len = 4
@@ -315,7 +315,7 @@
inst.connected = False
raise Exception()
- sock.send = send
+ sock.remote.send = send
inst.adj.outbuf_high_watermark = 3
inst.total_outbufs_len = 4
@@ -345,7 +345,7 @@
inst.connected = False
raise Exception()
- sock.send = send
+ sock.remote.send = send
wrote = inst.write_soon(b"xyz")
self.assertEqual(wrote, 3)
@@ -376,7 +376,7 @@
inst.total_outbufs_len = len(inst.outbufs[0])
inst.adj.send_bytes = 1
inst.adj.outbuf_high_watermark = 2
- sock.send = lambda x, do_close=True: False
+ sock.remote.send = lambda x, do_close=True: False
inst.will_close = False
inst.last_activity = 0
result = inst.handle_write()
@@ -400,7 +400,7 @@
def test__flush_some_full_outbuf_socket_returns_zero(self):
inst, sock, map = self._makeOneWithMap()
- sock.send = lambda x: False
+ sock.remote.send = lambda x: False
inst.outbufs[0].append(b"abc")
inst.total_outbufs_len = sum(len(x) for x in inst.outbufs)
result = inst._flush_some()
@@ -805,11 +805,12 @@
)
return [body]
- def _make_app_with_lookahead(self):
+ def _make_app_with_lookahead(self, recv_bytes=8192):
"""
Setup a channel with lookahead and store it and the socket in self
"""
adj = DummyAdjustments()
+ adj.recv_bytes = recv_bytes
adj.channel_request_lookahead = 5
channel, sock, map = self._makeOneWithMap(adj=adj)
channel.server.application = self.app_check_disconnect
@@ -901,13 +902,66 @@
self.assertEqual(data.split("\r\n")[-1], "finished")
self.assertEqual(self.request_body, b"x")
+ def test_lookahead_bad_request_drop_extra_data(self):
+ """
+ Send two requests, the first one being bad, split on the recv_bytes
+ limit, then emulate a race that could happen whereby we read data from
+ the socket while the service thread is cleaning up due to an error
+ processing the request.
+ """
+
+ invalid_request = [
+ "GET / HTTP/1.1",
+ "Host: localhost:8080",
+ "Content-length: -1",
+ "",
+ ]
+
+ invalid_request_len = len("".join([x + "\r\n" for x in
invalid_request]))
+
+ second_request = [
+ "POST / HTTP/1.1",
+ "Host: localhost:8080",
+ "Content-Length: 1",
+ "",
+ "x",
+ ]
+
+ full_request = invalid_request + second_request
+
+ self._make_app_with_lookahead(recv_bytes=invalid_request_len)
+ self._send(*full_request)
+ self.channel.handle_read()
+ self.assertEqual(len(self.channel.requests), 1)
+ self.channel.server.tasks[0].service()
+ self.assertTrue(self.channel.close_when_flushed)
+ # Read all of the next request
+ self.channel.handle_read()
+ self.channel.handle_read()
+ # Validate that there is no more data to be read
+ self.assertEqual(self.sock.remote.local_sent, b"")
+ # Validate that we dropped the data from the second read, and did not
+ # create a new request
+ self.assertEqual(len(self.channel.requests), 0)
+ data = self.sock.recv(256).decode("ascii")
+ self.assertFalse(self.channel.readable())
+ self.assertTrue(self.channel.writable())
+
+ # Handle the write, which will close the socket
+ self.channel.handle_write()
+ self.assertTrue(self.sock.closed)
+
+ data = self.sock.recv(256)
+ self.assertEqual(len(data), 0)
+
class DummySock:
blocking = False
closed = False
def __init__(self):
- self.sent = b""
+ self.local_sent = b""
+ self.remote_sent = b""
def setblocking(self, *arg):
self.blocking = True
@@ -925,14 +979,44 @@
self.closed = True
def send(self, data):
- self.sent += data
+ self.remote_sent += data
return len(data)
def recv(self, buffer_size):
- result = self.sent[:buffer_size]
- self.sent = self.sent[buffer_size:]
+ result = self.local_sent[:buffer_size]
+ self.local_sent = self.local_sent[buffer_size:]
return result
+ def local(self):
+ outer = self
+
+ class LocalDummySock:
+ def send(self, data):
+ outer.local_sent += data
+ return len(data)
+
+ def recv(self, buffer_size):
+ result = outer.remote_sent[:buffer_size]
+ outer.remote_sent = outer.remote_sent[buffer_size:]
+ return result
+
+ def close(self):
+ outer.closed = True
+
+ @property
+ def sent(self):
+ return outer.remote_sent
+
+ @property
+ def closed(self):
+ return outer.closed
+
+ @property
+ def remote(self):
+ return outer
+
+ return LocalDummySock()
+
class DummyLock:
notified = False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/tests/test_parser.py
new/waitress-3.0.1/tests/test_parser.py
--- old/waitress-3.0.0/tests/test_parser.py 2024-02-05 00:29:06.000000000
+0100
+++ new/waitress-3.0.1/tests/test_parser.py 2024-03-03 22:56:33.000000000
+0100
@@ -384,6 +384,11 @@
else: # pragma: nocover
self.assertTrue(False)
+ def test_parse_header_other_whitespace(self):
+ data = b"GET /foobar HTTP/1.1\r\nfoo: \xa0something\x85\r\n"
+ self.parser.parse_header(data)
+ self.assertEqual(self.parser.headers["FOO"], "\xa0something\x85")
+
def test_parse_header_empty(self):
data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nempty:\r\n"
self.parser.parse_header(data)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/tests/test_task.py
new/waitress-3.0.1/tests/test_task.py
--- old/waitress-3.0.0/tests/test_task.py 2024-02-04 23:39:05.000000000
+0100
+++ new/waitress-3.0.1/tests/test_task.py 2024-03-03 22:56:33.000000000
+0100
@@ -776,7 +776,7 @@
request.headers = {
"CONTENT_TYPE": "abc",
"CONTENT_LENGTH": "10",
- "X_FOO": "BAR",
+ "X_FOO": "\xa0BAR\x85",
"CONNECTION": "close",
}
request.query = "abc"
@@ -830,7 +830,8 @@
self.assertEqual(environ["REMOTE_PORT"], "39830")
self.assertEqual(environ["CONTENT_TYPE"], "abc")
self.assertEqual(environ["CONTENT_LENGTH"], "10")
- self.assertEqual(environ["HTTP_X_FOO"], "BAR")
+ # Make sure we don't strip non RFC compliant whitespace
+ self.assertEqual(environ["HTTP_X_FOO"], "\xa0BAR\x85")
self.assertEqual(environ["wsgi.version"], (1, 0))
self.assertEqual(environ["wsgi.url_scheme"], "http")
self.assertEqual(environ["wsgi.errors"], sys.stderr)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/tests/test_wasyncore.py
new/waitress-3.0.1/tests/test_wasyncore.py
--- old/waitress-3.0.0/tests/test_wasyncore.py 2024-02-04 20:29:14.000000000
+0100
+++ new/waitress-3.0.1/tests/test_wasyncore.py 2024-06-08 23:51:25.000000000
+0200
@@ -1,6 +1,7 @@
import _thread as thread
import contextlib
import errno
+from errno import EALREADY, EINPROGRESS, EINVAL, EISCONN, EWOULDBLOCK,
errorcode
import functools
import gc
from io import BytesIO
@@ -641,62 +642,6 @@
self.assertTrue(err != "")
-class dispatcherwithsend_noread(asyncore.dispatcher_with_send): # pragma: no
cover
- def readable(self):
- return False
-
- def handle_connect(self):
- pass
-
-
-class DispatcherWithSendTests(unittest.TestCase):
- def setUp(self):
- pass
-
- def tearDown(self):
- asyncore.close_all()
-
- @reap_threads
- def test_send(self):
- evt = threading.Event()
- sock = socket.socket()
- sock.settimeout(3)
- port = bind_port(sock)
-
- cap = BytesIO()
- args = (evt, cap, sock)
- t = threading.Thread(target=capture_server, args=args)
- t.start()
- try:
- # wait a little longer for the server to initialize (it sometimes
- # refuses connections on slow machines without this wait)
- time.sleep(0.2)
-
- data = b"Suppose there isn't a 16-ton weight?"
- d = dispatcherwithsend_noread()
- d.create_socket()
- d.connect((HOST, port))
-
- # give time for socket to connect
- time.sleep(0.1)
-
- d.send(data)
- d.send(data)
- d.send(b"\n")
-
- n = 1000
-
- while d.out_buffer and n > 0: # pragma: no cover
- asyncore.poll()
- n -= 1
-
- evt.wait()
-
- self.assertEqual(cap.getvalue(), data * 2)
- finally:
- join_thread(t, timeout=TIMEOUT)
-
-
@unittest.skipUnless(
hasattr(asyncore, "file_wrapper"), "asyncore.file_wrapper required"
)
@@ -839,6 +784,23 @@
self.create_socket(family)
self.connect(address)
+ def connect(self, address):
+ self.connected = False
+ self.connecting = True
+ err = self.socket.connect_ex(address)
+ if (
+ err in (EINPROGRESS, EALREADY, EWOULDBLOCK)
+ or err == EINVAL
+ and os.name == "nt"
+ ): # pragma: no cover
+ self.addr = address
+ return
+ if err in (0, EISCONN):
+ self.addr = address
+ self.handle_connect_event()
+ else:
+ raise OSError(err, errorcode[err])
+
def handle_connect(self):
pass
@@ -1454,17 +1416,6 @@
return dispatcher(sock=sock, map=map)
- def test_unexpected_getpeername_exc(self):
- sock = dummysocket()
-
- def getpeername():
- raise OSError(errno.EBADF)
-
- map = {}
- sock.getpeername = getpeername
- self.assertRaises(socket.error, self._makeOne, sock=sock, map=map)
- self.assertEqual(map, {})
-
def test___repr__accepting(self):
sock = dummysocket()
map = {}
@@ -1500,13 +1451,6 @@
inst.set_reuse_addr()
self.assertTrue(sock.errored)
- def test_connect_raise_socket_error(self):
- sock = dummysocket()
- map = {}
- sock.connect_ex = lambda *arg: 1
- inst = self._makeOne(sock=sock, map=map)
- self.assertRaises(socket.error, inst.connect, 0)
-
def test_accept_raise_TypeError(self):
sock = dummysocket()
map = {}
@@ -1675,21 +1619,6 @@
self.assertTrue(sock.closed)
-class Test_dispatcher_with_send(unittest.TestCase):
- def _makeOne(self, sock=None, map=None):
- from waitress.wasyncore import dispatcher_with_send
-
- return dispatcher_with_send(sock=sock, map=map)
-
- def test_writable(self):
- sock = dummysocket()
- map = {}
- inst = self._makeOne(sock=sock, map=map)
- inst.out_buffer = b"123"
- inst.connected = True
- self.assertTrue(inst.writable())
-
-
class Test_close_all(unittest.TestCase):
def _callFUT(self, map=None, ignore_all=False):
from waitress.wasyncore import close_all
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/waitress-3.0.0/tox.ini new/waitress-3.0.1/tox.ini
--- old/waitress-3.0.0/tox.ini 2024-02-04 23:39:05.000000000 +0100
+++ new/waitress-3.0.1/tox.ini 2024-10-27 02:15:47.000000000 +0100
@@ -1,7 +1,7 @@
[tox]
envlist =
lint,
- py38,py39,py310,py311,py312,pypy38,pypy39,pypy310
+ py39,py310,py311,py312,py313,pypy39,pypy310
coverage,
docs
isolated_build = True
@@ -10,7 +10,6 @@
commands =
python --version
python -mpytest \
- pypy38: --no-cov \
pypy39: --no-cov \
pypy310: --no-cov \
{posargs:}