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:}

Reply via email to