Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-eventlet for openSUSE:Factory checked in at 2021-11-21 23:51:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-eventlet (Old) and /work/SRC/openSUSE:Factory/.python-eventlet.new.1895 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-eventlet" Sun Nov 21 23:51:36 2021 rev:38 rq:931441 version:0.32.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-eventlet/python-eventlet.changes 2021-04-01 14:15:58.947865603 +0200 +++ /work/SRC/openSUSE:Factory/.python-eventlet.new.1895/python-eventlet.changes 2021-11-21 23:51:45.814333891 +0100 @@ -1,0 +2,15 @@ +Sun Nov 7 21:12:37 UTC 2021 - Dirk M??ller <dmuel...@suse.com> + +- update to 0.32.0: + * greendns: compatibility with dnspython v2 + * green.ssl: wrap_socket now accepts argument `ciphers` + * websocket: control frames are now always uncompressed per RFC 7692 + * ssl: py3.6 using client certificates raised ValueError: check_hostname needs server_hostname argument + * IMPORTANT: websocket: Limit maximum uncompressed frame length to 8MiB + * wsgi: websocket ALREADY_HANDLED flag on corolocal + * green.ssl: Set suppress_ragged_eofs default based on SSLSocket defaults + * greenio: socket.connect_ex returned None instead of 0 on success + * Use _imp instead of deprecated imp +- drop pr_639.patch, merged upstream + +------------------------------------------------------------------- Old: ---- eventlet-0.30.2.tar.gz pr_639.patch New: ---- eventlet-0.32.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-eventlet.spec ++++++ --- /var/tmp/diff_new_pack.12tFtF/_old 2021-11-21 23:51:46.438331880 +0100 +++ /var/tmp/diff_new_pack.12tFtF/_new 2021-11-21 23:51:46.442331867 +0100 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-eventlet -Version: 0.30.2 +Version: 0.32.0 Release: 0 Summary: Concurrent networking library for Python License: MIT @@ -30,8 +30,6 @@ Patch0: remove_nose.patch # PATCH-FIX-UPSTREAM newdnspython.patch mc...@suse.com -- patch is from gh#rthalley/dnspython#519, discussion in gh#eventlet/eventlet#638 Patch1: newdnspython.patch -# PATCH-FEATURE-UPSTREAM pr_639.patch gh#eventlet/eventlet#639 jay...@gmail.com -Patch2: pr_639.patch # Really remove the dependency on nose Patch3: remove_nose_part_2.patch BuildRequires: %{python_module setuptools} @@ -46,7 +44,9 @@ BuildRequires: sysconfig-netconfig BuildRequires: %{python_module dnspython >= 1.15.0} BuildRequires: %{python_module greenlet >= 0.3} +%if 0%{?suse_version} >= 1550 BuildRequires: %{python_module pyOpenSSL} +%endif BuildRequires: %{python_module pytest} BuildRequires: %{python_module pyzmq} BuildRequires: %{python_module six >= 1.10.0} ++++++ eventlet-0.30.2.tar.gz -> eventlet-0.32.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/NEWS new/eventlet-0.32.0/NEWS --- old/eventlet-0.30.2/NEWS 2021-03-03 12:41:35.000000000 +0100 +++ new/eventlet-0.32.0/NEWS 2021-09-01 12:49:47.000000000 +0200 @@ -1,3 +1,24 @@ +0.32.0 +====== +* greendns: compatibility with dnspython v2 https://github.com/eventlet/eventlet/pull/722 +* green.ssl: wrap_socket now accepts argument `ciphers` https://github.com/eventlet/eventlet/pull/718 +* websocket: control frames are now always uncompressed per RFC 7692; Thanks to Onno Kortmann + +0.31.1 +====== +* ssl: py3.6 using client certificates raised ValueError: check_hostname needs server_hostname argument https://github.com/eventlet/eventlet/pull/705 + +0.31.0 +====== +* IMPORTANT: websocket: Limit maximum uncompressed frame length to 8MiB https://github.com/eventlet/eventlet/security/advisories/GHSA-9p9m-jm8w-94p2 + +0.30.3 +====== +* wsgi: websocket ALREADY_HANDLED flag on corolocal +* green.ssl: Set suppress_ragged_eofs default based on SSLSocket defaults +* greenio: socket.connect_ex returned None instead of 0 on success +* Use _imp instead of deprecated imp + 0.30.2 ====== * greendns: patch ssl to fix RecursionError on SSLContext.options.__set__ https://github.com/eventlet/eventlet/issues/677 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/PKG-INFO new/eventlet-0.32.0/PKG-INFO --- old/eventlet-0.30.2/PKG-INFO 2021-03-03 12:42:28.000000000 +0100 +++ new/eventlet-0.32.0/PKG-INFO 2021-09-01 12:54:37.135779000 +0200 @@ -1,6 +1,6 @@ -Metadata-Version: 1.1 +Metadata-Version: 2.1 Name: eventlet -Version: 0.30.2 +Version: 0.32.0 Summary: Highly concurrent networking library Home-page: http://eventlet.net Author: Linden Lab @@ -81,8 +81,8 @@ .. image:: https://img.shields.io/pypi/v/eventlet :target: https://pypi.org/project/eventlet/ - .. image:: https://travis-ci.org/eventlet/eventlet.svg?branch=master - :target: https://travis-ci.org/eventlet/eventlet + .. image:: https://img.shields.io/github/workflow/status/eventlet/eventlet/test/master + :target: https://github.com/eventlet/eventlet/actions?query=workflow%3Atest+branch%3Amaster .. image:: https://codecov.io/gh/eventlet/eventlet/branch/master/graph/badge.svg :target: https://codecov.io/gh/eventlet/eventlet @@ -100,6 +100,8 @@ Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python Classifier: Topic :: Internet Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/README.rst new/eventlet-0.32.0/README.rst --- old/eventlet-0.30.2/README.rst 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/README.rst 2021-09-01 12:49:47.000000000 +0200 @@ -73,8 +73,8 @@ .. image:: https://img.shields.io/pypi/v/eventlet :target: https://pypi.org/project/eventlet/ -.. image:: https://travis-ci.org/eventlet/eventlet.svg?branch=master - :target: https://travis-ci.org/eventlet/eventlet +.. image:: https://img.shields.io/github/workflow/status/eventlet/eventlet/test/master + :target: https://github.com/eventlet/eventlet/actions?query=workflow%3Atest+branch%3Amaster .. image:: https://codecov.io/gh/eventlet/eventlet/branch/master/graph/badge.svg :target: https://codecov.io/gh/eventlet/eventlet diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet/__init__.py new/eventlet-0.32.0/eventlet/__init__.py --- old/eventlet-0.30.2/eventlet/__init__.py 2021-03-03 12:39:52.000000000 +0100 +++ new/eventlet-0.32.0/eventlet/__init__.py 2021-09-01 12:49:47.000000000 +0200 @@ -8,7 +8,7 @@ DeprecationWarning, ) -version_info = (0, 30, 2) +version_info = (0, 32, 0) __version__ = '.'.join(map(str, version_info)) # This is to make Debian packaging easier, it ignores import # errors of greenlet so that the packager can still at least diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet/green/ssl.py new/eventlet-0.32.0/eventlet/green/ssl.py --- old/eventlet-0.30.2/eventlet/green/ssl.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/eventlet/green/ssl.py 2021-09-01 12:49:47.000000000 +0200 @@ -70,7 +70,7 @@ sock=sock.fd, server_side=server_side, do_handshake_on_connect=False, - suppress_ragged_eofs=kw.get('suppress_ragged_eofs'), + suppress_ragged_eofs=kw.get('suppress_ragged_eofs', True), server_hostname=kw.get('server_hostname'), context=context, session=kw.get('session'), @@ -85,6 +85,7 @@ ssl_version=ssl_version, ca_certs=ca_certs, do_handshake_on_connect=False, + ciphers=kw.get('ciphers'), ) ret.keyfile = keyfile ret.certfile = certfile @@ -364,7 +365,7 @@ sslobj = self._context._wrap_socket(self._sock, server_side, ssl_sock=self) else: context = self.context if PY33 else self._context - sslobj = context._wrap_socket(self, server_side) + sslobj = context._wrap_socket(self, server_side, server_hostname=self.server_hostname) else: sslobj = sslwrap(self._sock, server_side, self.keyfile, self.certfile, self.cert_reqs, self.ssl_version, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet/greenio/base.py new/eventlet-0.32.0/eventlet/greenio/base.py --- old/eventlet-0.30.2/eventlet/greenio/base.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/eventlet/greenio/base.py 2021-09-01 12:49:47.000000000 +0200 @@ -279,6 +279,7 @@ return get_errno(ex) except IOClosed: return errno.EBADFD + return 0 else: end = time.time() + self.gettimeout() timeout_exc = socket.timeout(errno.EAGAIN) @@ -295,6 +296,7 @@ return get_errno(ex) except IOClosed: return errno.EBADFD + return 0 def dup(self, *args, **kw): sock = self.fd.dup(*args, **kw) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet/patcher.py new/eventlet-0.32.0/eventlet/patcher.py --- old/eventlet-0.30.2/eventlet/patcher.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/eventlet/patcher.py 2021-09-01 12:49:47.000000000 +0200 @@ -1,4 +1,7 @@ -import imp +try: + import _imp as imp +except ImportError: + import imp import sys try: # Only for this purpose, it's irrelevant if `os` was already patched. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet/support/greendns.py new/eventlet-0.32.0/eventlet/support/greendns.py --- old/eventlet-0.30.2/eventlet/support/greendns.py 2021-03-03 12:39:36.000000000 +0100 +++ new/eventlet-0.32.0/eventlet/support/greendns.py 2021-09-01 12:49:47.000000000 +0200 @@ -120,6 +120,16 @@ return is_ipv4_addr(host) or is_ipv6_addr(host) +# NOTE(ralonsoh): in dnspython v2.0.0, "_compute_expiration" was replaced +# by "_compute_times". +if hasattr(dns.query, '_compute_expiration'): + def compute_expiration(query, timeout): + return query._compute_expiration(timeout) +else: + def compute_expiration(query, timeout): + return query._compute_times(timeout)[1] + + class HostsAnswer(dns.resolver.Answer): """Answer class for HostsResolver object""" @@ -660,8 +670,21 @@ raise dns.exception.Timeout +# Test if raise_on_truncation is an argument we should handle. +# It was newly added in dnspython 2.0 +try: + dns.message.from_wire("", raise_on_truncation=True) +except dns.message.ShortHeader: + _handle_raise_on_truncation = True +except TypeError: + # Argument error, there is no argument "raise_on_truncation" + _handle_raise_on_truncation = False + + def udp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, - af=None, source=None, source_port=0, ignore_unexpected=False): + af=None, source=None, source_port=0, ignore_unexpected=False, + one_rr_per_rrset=False, ignore_trailing=False, + raise_on_truncation=False, sock=None): """coro friendly replacement for dns.query.udp Return the response obtained after sending a query via UDP. @@ -686,7 +709,21 @@ @type source_port: int @param ignore_unexpected: If True, ignore responses from unexpected sources. The default is False. - @type ignore_unexpected: bool""" + @type ignore_unexpected: bool + @param one_rr_per_rrset: If True, put each RR into its own + RRset. + @type one_rr_per_rrset: bool + @param ignore_trailing: If True, ignore trailing + junk at end of the received message. + @type ignore_trailing: bool + @param raise_on_truncation: If True, raise an exception if + the TC bit is set. + @type raise_on_truncation: bool + @param sock: the socket to use for the + query. If None, the default, a socket is created. Note that + if a socket is provided, it must be a nonblocking datagram socket, + and the source and source_port are ignored. + @type sock: socket.socket | None""" wire = q.to_wire() if af is None: @@ -708,10 +745,13 @@ if source is not None: source = (source, source_port, 0, 0) - s = socket.socket(af, socket.SOCK_DGRAM) + if sock: + s = sock + else: + s = socket.socket(af, socket.SOCK_DGRAM) s.settimeout(timeout) try: - expiration = dns.query._compute_expiration(timeout) + expiration = compute_expiration(dns.query, timeout) if source is not None: s.bind(source) while True: @@ -756,14 +796,23 @@ finally: s.close() - r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac) + if _handle_raise_on_truncation: + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + raise_on_truncation=raise_on_truncation) + else: + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing) if not q.is_response(r): raise dns.query.BadResponse() return r def tcp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, - af=None, source=None, source_port=0): + af=None, source=None, source_port=0, + one_rr_per_rrset=False, ignore_trailing=False, sock=None): """coro friendly replacement for dns.query.tcp Return the response obtained after sending a query via TCP. @@ -785,7 +834,19 @@ @type source: string @param source_port: The port from which to send the message. The default is 0. - @type source_port: int""" + @type source_port: int + @type ignore_unexpected: bool + @param one_rr_per_rrset: If True, put each RR into its own + RRset. + @type one_rr_per_rrset: bool + @param ignore_trailing: If True, ignore trailing + junk at end of the received message. + @type ignore_trailing: bool + @param sock: the socket to use for the + query. If None, the default, a socket is created. Note that + if a socket is provided, it must be a nonblocking datagram socket, + and the source and source_port are ignored. + @type sock: socket.socket | None""" wire = q.to_wire() if af is None: @@ -801,10 +862,13 @@ destination = (where, port, 0, 0) if source is not None: source = (source, source_port, 0, 0) - s = socket.socket(af, socket.SOCK_STREAM) + if sock: + s = sock + else: + s = socket.socket(af, socket.SOCK_STREAM) s.settimeout(timeout) try: - expiration = dns.query._compute_expiration(timeout) + expiration = compute_expiration(dns.query, timeout) if source is not None: s.bind(source) while True: @@ -829,7 +893,9 @@ wire = bytes(_net_read(s, l, expiration)) finally: s.close() - r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac) + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing) if not q.is_response(r): raise dns.query.BadResponse() return r diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet/tpool.py new/eventlet-0.32.0/eventlet/tpool.py --- old/eventlet-0.30.2/eventlet/tpool.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/eventlet/tpool.py 2021-09-01 12:49:47.000000000 +0200 @@ -14,7 +14,10 @@ # limitations under the License. import atexit -import imp +try: + import _imp as imp +except ImportError: + import imp import os import sys import traceback diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet/websocket.py new/eventlet-0.32.0/eventlet/websocket.py --- old/eventlet-0.30.2/eventlet/websocket.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/eventlet/websocket.py 2021-09-01 12:49:47.000000000 +0200 @@ -38,6 +38,7 @@ break ACCEPTABLE_CLIENT_ERRORS = set((errno.ECONNRESET, errno.EPIPE)) +DEFAULT_MAX_FRAME_LENGTH = 8 << 20 __all__ = ["WebSocketWSGI", "WebSocket"] PROTOCOL_GUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' @@ -75,14 +76,20 @@ :class:`WebSocket`. To close the socket, simply return from the function. Note that the server will log the websocket request at the time of closure. + + An optional argument max_frame_length can be given, which will set the + maximum incoming *uncompressed* payload length of a frame. By default, this + is set to 8MiB. Note that excessive values here might create a DOS attack + vector. """ - def __init__(self, handler): + def __init__(self, handler, max_frame_length=DEFAULT_MAX_FRAME_LENGTH): self.handler = handler self.protocol_version = None self.support_legacy_versions = True self.supported_protocols = [] self.origin_checker = None + self.max_frame_length = max_frame_length @classmethod def configured(cls, @@ -135,7 +142,8 @@ ws._send_closing_frame(True) # use this undocumented feature of eventlet.wsgi to ensure that it # doesn't barf on the fact that we didn't call start_response - return wsgi.ALREADY_HANDLED + wsgi.WSGI_LOCAL.already_handled = True + return [] def _handle_legacy_request(self, environ): if 'eventlet.input' in environ: @@ -323,7 +331,8 @@ sock.sendall(b'\r\n'.join(handshake_reply) + b'\r\n\r\n') return RFC6455WebSocket(sock, environ, self.protocol_version, protocol=negotiated_protocol, - extensions=parsed_extensions) + extensions=parsed_extensions, + max_frame_length=self.max_frame_length) def _extract_number(self, value): """ @@ -502,7 +511,8 @@ class RFC6455WebSocket(WebSocket): - def __init__(self, sock, environ, version=13, protocol=None, client=False, extensions=None): + def __init__(self, sock, environ, version=13, protocol=None, client=False, extensions=None, + max_frame_length=DEFAULT_MAX_FRAME_LENGTH): super(RFC6455WebSocket, self).__init__(sock, environ, version) self.iterator = self._iter_frames() self.client = client @@ -511,6 +521,8 @@ self._deflate_enc = None self._deflate_dec = None + self.max_frame_length = max_frame_length + self._remote_close_data = None class UTF8Decoder(object): def __init__(self): @@ -582,12 +594,13 @@ return data class Message(object): - def __init__(self, opcode, decoder=None, decompressor=None): + def __init__(self, opcode, max_frame_length, decoder=None, decompressor=None): self.decoder = decoder self.data = [] self.finished = False self.opcode = opcode self.decompressor = decompressor + self.max_frame_length = max_frame_length def push(self, data, final=False): self.finished = final @@ -596,7 +609,12 @@ def getvalue(self): data = b"".join(self.data) if not self.opcode & 8 and self.decompressor: - data = self.decompressor.decompress(data + b'\x00\x00\xff\xff') + data = self.decompressor.decompress(data + b"\x00\x00\xff\xff", self.max_frame_length) + if self.decompressor.unconsumed_tail: + raise FailedConnectionError( + 1009, + "Incoming compressed frame exceeds length limit of {} bytes.".format(self.max_frame_length)) + if self.decoder: data = self.decoder.decode(data, self.finished) return data @@ -610,6 +628,7 @@ def _handle_control_frame(self, opcode, data): if opcode == 8: # connection close + self._remote_close_data = data if not data: status = 1000 elif len(data) > 1: @@ -709,13 +728,17 @@ length = struct.unpack('!H', recv(2))[0] elif length == 127: length = struct.unpack('!Q', recv(8))[0] + + if length > self.max_frame_length: + raise FailedConnectionError(1009, "Incoming frame of {} bytes is above length limit of {} bytes.".format( + length, self.max_frame_length)) if masked: mask = struct.unpack('!BBBB', recv(4)) received = 0 if not message or opcode & 8: decoder = self.UTF8Decoder() if opcode == 1 else None decompressor = self._get_permessage_deflate_dec(rsv1) - message = self.Message(opcode, decoder=decoder, decompressor=decompressor) + message = self.Message(opcode, self.max_frame_length, decoder=decoder, decompressor=decompressor) if not length: message.push(b'', final=finished) else: @@ -743,7 +766,17 @@ compress_bit = 0 compressor = self._get_permessage_deflate_enc() - if message and compressor: + # Control frames are identified by opcodes where the most significant + # bit of the opcode is 1. Currently defined opcodes for control frames + # include 0x8 (Close), 0x9 (Ping), and 0xA (Pong). Opcodes 0xB-0xF are + # reserved for further control frames yet to be defined. + # https://datatracker.ietf.org/doc/html/rfc6455#section-5.5 + is_control_frame = (control_code or 0) & 8 + # An endpoint MUST NOT set the "Per-Message Compressed" bit of control + # frames and non-first fragments of a data message. An endpoint + # receiving such a frame MUST _Fail the WebSocket Connection_. + # https://datatracker.ietf.org/doc/html/rfc7692#section-6.1 + if message and compressor and not is_control_frame: message = compressor.compress(message) message += compressor.flush(zlib.Z_SYNC_FLUSH) assert message[-4:] == b"\x00\x00\xff\xff" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet/wsgi.py new/eventlet-0.32.0/eventlet/wsgi.py --- old/eventlet-0.30.2/eventlet/wsgi.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/eventlet/wsgi.py 2021-09-01 12:49:47.000000000 +0200 @@ -9,6 +9,7 @@ import eventlet from eventlet import greenio from eventlet import support +from eventlet.corolocal import local from eventlet.green import BaseHTTPServer from eventlet.green import socket import six @@ -69,19 +70,7 @@ pass -# special flag return value for apps -class _AlreadyHandled(object): - - def __iter__(self): - return self - - def next(self): - raise StopIteration - - __next__ = next - - -ALREADY_HANDLED = _AlreadyHandled() +WSGI_LOCAL = local() class Input(object): @@ -570,14 +559,12 @@ try: try: + WSGI_LOCAL.already_handled = False result = self.application(self.environ, start_response) - if (isinstance(result, _AlreadyHandled) - or isinstance(getattr(result, '_obj', None), _AlreadyHandled)): - self.close_connection = 1 - return # Set content-length if possible - if not headers_sent and hasattr(result, '__len__') and \ + if headers_set and \ + not headers_sent and hasattr(result, '__len__') and \ 'Content-Length' not in [h for h, _v in headers_set[1]]: headers_set[1].append(('Content-Length', str(sum(map(len, result))))) @@ -599,6 +586,9 @@ towrite = [] just_written_size = towrite_size towrite_size = 0 + if WSGI_LOCAL.already_handled: + self.close_connection = 1 + return if towrite: just_written_size = towrite_size write(b''.join(towrite)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet/zipkin/client.py new/eventlet-0.32.0/eventlet/zipkin/client.py --- old/eventlet-0.30.2/eventlet/zipkin/client.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/eventlet/zipkin/client.py 2021-09-01 12:49:47.000000000 +0200 @@ -15,7 +15,7 @@ def __init__(self, host='127.0.0.1', port=9410): """ - :param host: zipkin collector IP addoress (default '127.0.0.1') + :param host: zipkin collector IP address (default '127.0.0.1') :param port: zipkin collector port (default 9410) """ self.host = host diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet.egg-info/PKG-INFO new/eventlet-0.32.0/eventlet.egg-info/PKG-INFO --- old/eventlet-0.30.2/eventlet.egg-info/PKG-INFO 2021-03-03 12:42:27.000000000 +0100 +++ new/eventlet-0.32.0/eventlet.egg-info/PKG-INFO 2021-09-01 12:54:36.000000000 +0200 @@ -1,6 +1,6 @@ -Metadata-Version: 1.1 +Metadata-Version: 2.1 Name: eventlet -Version: 0.30.2 +Version: 0.32.0 Summary: Highly concurrent networking library Home-page: http://eventlet.net Author: Linden Lab @@ -81,8 +81,8 @@ .. image:: https://img.shields.io/pypi/v/eventlet :target: https://pypi.org/project/eventlet/ - .. image:: https://travis-ci.org/eventlet/eventlet.svg?branch=master - :target: https://travis-ci.org/eventlet/eventlet + .. image:: https://img.shields.io/github/workflow/status/eventlet/eventlet/test/master + :target: https://github.com/eventlet/eventlet/actions?query=workflow%3Atest+branch%3Amaster .. image:: https://codecov.io/gh/eventlet/eventlet/branch/master/graph/badge.svg :target: https://codecov.io/gh/eventlet/eventlet @@ -100,6 +100,8 @@ Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python Classifier: Topic :: Internet Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/eventlet.egg-info/requires.txt new/eventlet-0.32.0/eventlet.egg-info/requires.txt --- old/eventlet-0.30.2/eventlet.egg-info/requires.txt 2021-03-03 12:42:27.000000000 +0100 +++ new/eventlet-0.32.0/eventlet.egg-info/requires.txt 2021-09-01 12:54:36.000000000 +0200 @@ -1,4 +1,4 @@ -dnspython<2.0.0,>=1.15.0 +dnspython>=1.15.0 greenlet>=0.3 six>=1.10.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/setup.py new/eventlet-0.32.0/setup.py --- old/eventlet-0.30.2/setup.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/setup.py 2021-09-01 12:49:47.000000000 +0200 @@ -15,7 +15,7 @@ url='http://eventlet.net', packages=setuptools.find_packages(exclude=['benchmarks', 'tests', 'tests.*']), install_requires=( - 'dnspython >= 1.15.0, < 2.0.0', + 'dnspython >= 1.15.0', 'greenlet >= 0.3', 'monotonic >= 1.4;python_version<"3.5"', 'six >= 1.10.0', @@ -41,6 +41,8 @@ "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python", "Topic :: Internet", "Topic :: Software Development :: Libraries :: Python Modules", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/tests/__init__.py new/eventlet-0.32.0/tests/__init__.py --- old/eventlet-0.30.2/tests/__init__.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/tests/__init__.py 2021-09-01 12:49:47.000000000 +0200 @@ -232,10 +232,10 @@ utime = r2.ru_utime - r1.ru_utime stime = r2.ru_stime - r1.ru_stime - # This check is reliably unreliable on Travis, presumably because of CPU + # This check is reliably unreliable on Travis/Github Actions, presumably because of CPU # resources being quite restricted by the build environment. The workaround # is to apply an arbitrary factor that should be enough to make it work nicely. - if os.environ.get('TRAVIS') == 'true': + if os.environ.get('CI') == 'true': allowed_part *= 5 assert utime + stime < duration * allowed_part, \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/tests/greendns_test.py new/eventlet-0.32.0/tests/greendns_test.py --- old/eventlet-0.30.2/tests/greendns_test.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/tests/greendns_test.py 2021-09-01 12:49:47.000000000 +0200 @@ -901,7 +901,7 @@ resolver.nameserver_ports[dnsaddr[0]] = dnsaddr[1] response = resolver.query('host.example.com', 'a', tcp=True) self.assertIsInstance(response, Answer) - self.assertEqual(response.rrset.items[0].address, expected_ip) + self.assertEqual(list(response.rrset.items)[0].address, expected_ip) def test_reverse_name(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/tests/hub_test.py new/eventlet-0.32.0/tests/hub_test.py --- old/eventlet-0.30.2/tests/hub_test.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/tests/hub_test.py 2021-09-01 12:49:47.000000000 +0200 @@ -362,7 +362,7 @@ # kill dummyproc, this schedules a timer to return execution to # this greenlet before throwing an exception in dummyproc. # it is from this timer that execution should be returned to this - # greenlet, and not by propogating of the terminating greenlet. + # greenlet, and not by propagating of the terminating greenlet. g.kill() with eventlet.Timeout(0.5, self.CustomException()): # we now switch to the hub, there should be no existing timers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/tests/isolated/socket_resolve_green.py new/eventlet-0.32.0/tests/isolated/socket_resolve_green.py --- old/eventlet-0.30.2/tests/isolated/socket_resolve_green.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/tests/isolated/socket_resolve_green.py 2021-09-01 12:49:47.000000000 +0200 @@ -7,6 +7,7 @@ import time import dns.message import dns.query + import dns.flags n = 10 delay = 0.01 @@ -17,7 +18,7 @@ addr = addr_map[qname.to_text()] r = dns.message.make_response(q) r.index = None - r.flags = 256 + r.flags = dns.flags.QR | dns.flags.RD r.answer.append(dns.rrset.from_text(str(qname), 60, 'IN', 'A', addr)) r.time = 0.001 eventlet.sleep(delay) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/tests/mysqldb_test.py new/eventlet-0.32.0/tests/mysqldb_test.py --- old/eventlet-0.30.2/tests/mysqldb_test.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/tests/mysqldb_test.py 2021-09-01 12:49:47.000000000 +0200 @@ -38,6 +38,8 @@ class TestMySQLdb(tests.LimitedTestCase): + TEST_TIMEOUT = 5 + def setUp(self): self._auth = get_database_auth()['MySQLdb'] self.create_db() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/tests/socket_test.py new/eventlet-0.32.0/tests/socket_test.py --- old/eventlet-0.30.2/tests/socket_test.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/tests/socket_test.py 2021-09-01 12:49:47.000000000 +0200 @@ -99,3 +99,11 @@ tests.check_is_timeout(e) else: assert False, 'No timeout, socket.error was not raised' + + +def test_connect_ex_success(): + # https://github.com/eventlet/eventlet/issues/696 + server = eventlet.listen(("127.0.0.1", 0)) + client = socket.socket() + result = client.connect_ex(server.getsockname()) + assert result == 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/tests/ssl_test.py new/eventlet-0.32.0/tests/ssl_test.py --- old/eventlet-0.30.2/tests/ssl_test.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/tests/ssl_test.py 2021-09-01 12:49:47.000000000 +0200 @@ -1,6 +1,7 @@ import contextlib import random import socket +import sys import warnings import eventlet @@ -371,3 +372,26 @@ peer, _ = server_tls.accept() assert peer.recv(64) == expected peer.close() + + def test_client_check_hostname(self): + # stdlib API compatibility + # https://github.com/eventlet/eventlet/issues/567 + def serve(listener): + sock, addr = listener.accept() + sock.recv(64) + sock.sendall(b"response") + sock.close() + + listener = listen_ssl_socket() + server_coro = eventlet.spawn(serve, listener) + ctx = ssl.create_default_context() + ctx.verify_mode = ssl.CERT_REQUIRED + ctx.check_hostname = True + ctx.load_verify_locations(tests.certificate_file) + ctx.load_cert_chain(tests.certificate_file, tests.private_key_file) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client = ctx.wrap_socket(sock, server_hostname="Test") + client.connect(listener.getsockname()) + client.send(b"check_hostname works") + client.recv(64) + server_coro.wait() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/tests/websocket_new_test.py new/eventlet-0.32.0/tests/websocket_new_test.py --- old/eventlet-0.30.2/tests/websocket_new_test.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/tests/websocket_new_test.py 2021-09-01 12:49:47.000000000 +0200 @@ -30,7 +30,12 @@ else: ws.close() -wsapp = websocket.WebSocketWSGI(handle) + +# Set a lower limit of DEFAULT_MAX_FRAME_LENGTH for testing, as +# sending an 8MiB frame over the loopback interface can trigger a +# timeout. +TEST_MAX_FRAME_LENGTH = 50000 +wsapp = websocket.WebSocketWSGI(handle, max_frame_length=TEST_MAX_FRAME_LENGTH) class TestWebSocket(tests.wsgi_test._TestBase): @@ -534,3 +539,55 @@ ws.close() eventlet.sleep(0.01) + + def test_large_frame_size_compressed_13(self): + # Test fix for GHSA-9p9m-jm8w-94p2 + extensions_string = 'permessage-deflate' + extensions = {'permessage-deflate': { + 'client_no_context_takeover': False, + 'server_no_context_takeover': False}} + + sock = eventlet.connect(self.server_addr) + sock.sendall(six.b(self.connect % extensions_string)) + sock.recv(1024) + ws = websocket.RFC6455WebSocket(sock, {}, client=True, extensions=extensions) + + should_still_fit = b"x" * TEST_MAX_FRAME_LENGTH + one_too_much = should_still_fit + b"x" + + # send just fitting frame twice to make sure they are fine independently + ws.send(should_still_fit) + assert ws.wait() == should_still_fit + ws.send(should_still_fit) + assert ws.wait() == should_still_fit + ws.send(one_too_much) + + res = ws.wait() + assert res is None # socket closed + # TODO: The websocket currently sents compressed control frames, which contradicts RFC7692. + # Renable the following assert after that has been fixed. + # assert ws._remote_close_data == b"\x03\xf1Incoming compressed frame is above length limit." + eventlet.sleep(0.01) + + def test_large_frame_size_uncompressed_13(self): + # Test fix for GHSA-9p9m-jm8w-94p2 + sock = eventlet.connect(self.server_addr) + sock.sendall(six.b(self.connect)) + sock.recv(1024) + ws = websocket.RFC6455WebSocket(sock, {}, client=True) + + should_still_fit = b"x" * TEST_MAX_FRAME_LENGTH + one_too_much = should_still_fit + b"x" + + # send just fitting frame twice to make sure they are fine independently + ws.send(should_still_fit) + assert ws.wait() == should_still_fit + ws.send(should_still_fit) + assert ws.wait() == should_still_fit + ws.send(one_too_much) + + res = ws.wait() + assert res is None # socket closed + # close code should be available now + assert ws._remote_close_data == b"\x03\xf1Incoming frame of 50001 bytes is above length limit of 50000 bytes." + eventlet.sleep(0.01) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/tests/websocket_test.py new/eventlet-0.32.0/tests/websocket_test.py --- old/eventlet-0.30.2/tests/websocket_test.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/tests/websocket_test.py 2021-09-01 12:49:47.000000000 +0200 @@ -528,6 +528,38 @@ with eventlet.Timeout(1): pool.waitall() + def test_wrapped_wsgi(self): + site = self.site + + def wrapper(environ, start_response): + for chunk in site(environ, start_response): + yield chunk + + self.site = wrapper + self.spawn_server() + connect = [ + "GET /range HTTP/1.1", + "Upgrade: WebSocket", + "Connection: Upgrade", + "Host: {}:{}".format(*self.server_addr), + "Origin: http://{}:{}".format(*self.server_addr), + "WebSocket-Protocol: ws", + ] + sock = eventlet.connect(self.server_addr) + + sock.sendall(six.b("\r\n".join(connect) + "\r\n\r\n")) + resp = sock.recv(1024) + headers, result = resp.split(b"\r\n\r\n") + msgs = [result.strip(b"\x00\xff")] + msgs.extend(sock.recv(20).strip(b"\x00\xff") for _ in range(10)) + expect = [six.b("msg {}".format(i)) for i in range(10)] + [b""] + assert msgs == expect + # In case of server error, server will write HTTP 500 response to the socket + msg = sock.recv(20) + assert not msg + sock.close() + eventlet.sleep(0.01) + class TestWebSocketSSL(tests.wsgi_test._TestBase): def set_site(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.30.2/tests/wsgi_test.py new/eventlet-0.32.0/tests/wsgi_test.py --- old/eventlet-0.30.2/tests/wsgi_test.py 2021-02-02 10:04:54.000000000 +0100 +++ new/eventlet-0.32.0/tests/wsgi_test.py 2021-09-01 12:49:47.000000000 +0200 @@ -103,7 +103,8 @@ def already_handled(env, start_response): start_response('200 OK', [('Content-type', 'text/plain')]) - return wsgi.ALREADY_HANDLED + wsgi.WSGI_LOCAL.already_handled = True + return [] class Site(object): @@ -115,8 +116,7 @@ class IterableApp(object): - - def __init__(self, send_start_response=False, return_val=wsgi.ALREADY_HANDLED): + def __init__(self, send_start_response=False, return_val=()): self.send_start_response = send_start_response self.return_val = return_val self.env = {} @@ -125,6 +125,8 @@ self.env = env if self.send_start_response: start_response('200 OK', [('Content-type', 'text/plain')]) + else: + wsgi.WSGI_LOCAL.already_handled = True return self.return_val @@ -395,7 +397,7 @@ # Eventlet issue: "Python 3: wsgi doesn't handle correctly partial # write of socket send() when using writelines()". # - # The bug was caused by the default writelines() implementaiton + # The bug was caused by the default writelines() implementation # (used by the wsgi module) which doesn't check if write() # successfully completed sending *all* data therefore data could be # lost and the client could be left hanging forever. ++++++ newdnspython.patch ++++++ --- /var/tmp/diff_new_pack.12tFtF/_old 2021-11-21 23:51:46.554331506 +0100 +++ /var/tmp/diff_new_pack.12tFtF/_new 2021-11-21 23:51:46.558331493 +0100 @@ -1,8 +1,8 @@ -Index: eventlet-0.29.1/eventlet/support/greendns.py +Index: eventlet-0.32.0/eventlet/support/greendns.py =================================================================== ---- eventlet-0.29.1.orig/eventlet/support/greendns.py -+++ eventlet-0.29.1/eventlet/support/greendns.py -@@ -313,7 +313,7 @@ class ResolverProxy(object): +--- eventlet-0.32.0.orig/eventlet/support/greendns.py ++++ eventlet-0.32.0/eventlet/support/greendns.py +@@ -325,7 +325,7 @@ class ResolverProxy(object): self.clear() def clear(self): @@ -11,10 +11,10 @@ self._resolver.cache = dns.resolver.LRUCache() def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, -Index: eventlet-0.29.1/tests/greendns_test.py +Index: eventlet-0.32.0/tests/greendns_test.py =================================================================== ---- eventlet-0.29.1.orig/tests/greendns_test.py -+++ eventlet-0.29.1/tests/greendns_test.py +--- eventlet-0.32.0.orig/tests/greendns_test.py ++++ eventlet-0.32.0/tests/greendns_test.py @@ -885,7 +885,7 @@ class TinyDNSTests(tests.LimitedTestCase # https://github.com/eventlet/eventlet/issues/499 # None means we don't want the server to find the IP @@ -33,16 +33,3 @@ resolver.nameservers = [dnsaddr[0]] resolver.nameserver_ports[dnsaddr[0]] = dnsaddr[1] response = resolver.query('host.example.com', 'a', tcp=True) -Index: eventlet-0.29.1/setup.py -=================================================================== ---- eventlet-0.29.1.orig/setup.py -+++ eventlet-0.29.1/setup.py -@@ -15,7 +15,7 @@ setuptools.setup( - url='http://eventlet.net', - packages=setuptools.find_packages(exclude=['benchmarks', 'tests', 'tests.*']), - install_requires=( -- 'dnspython >= 1.15.0, < 2.0.0', -+ 'dnspython >= 1.15.0', - 'greenlet >= 0.3', - 'monotonic >= 1.4;python_version<"3.5"', - 'six >= 1.10.0', ++++++ remove_nose.patch ++++++ --- /var/tmp/diff_new_pack.12tFtF/_old 2021-11-21 23:51:46.566331467 +0100 +++ /var/tmp/diff_new_pack.12tFtF/_new 2021-11-21 23:51:46.570331454 +0100 @@ -1,5 +1,7 @@ ---- a/setup.py -+++ b/setup.py +Index: eventlet-0.32.0/setup.py +=================================================================== +--- eventlet-0.32.0.orig/setup.py ++++ eventlet-0.32.0/setup.py @@ -27,7 +27,7 @@ setuptools.setup( 'README.rst' ) @@ -9,8 +11,10 @@ classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", ---- a/eventlet.egg-info/SOURCES.txt -+++ b/eventlet.egg-info/SOURCES.txt +Index: eventlet-0.32.0/eventlet.egg-info/SOURCES.txt +=================================================================== +--- eventlet-0.32.0.orig/eventlet.egg-info/SOURCES.txt ++++ eventlet-0.32.0/eventlet.egg-info/SOURCES.txt @@ -174,7 +174,6 @@ tests/greenthread_test.py tests/hub_test.py tests/mock.py @@ -19,15 +23,17 @@ tests/openssl_test.py tests/os_test.py tests/parse_results.py -@@ -270,4 +269,4 @@ tests/stdlib/test_threading_local.py +@@ -273,4 +272,4 @@ tests/stdlib/test_threading_local.py tests/stdlib/test_timeout.py tests/stdlib/test_urllib.py tests/stdlib/test_urllib2.py -tests/stdlib/test_urllib2_localnet.py \ No newline at end of file +tests/stdlib/test_urllib2_localnet.py ---- a/tests/greenio_test.py -+++ b/tests/greenio_test.py +Index: eventlet-0.32.0/tests/greenio_test.py +=================================================================== +--- eventlet-0.32.0.orig/tests/greenio_test.py ++++ eventlet-0.32.0/tests/greenio_test.py @@ -9,8 +9,6 @@ import socket as _orig_sock import sys import tempfile @@ -57,8 +63,10 @@ def test_get_fileno_of_a_socket_works(): ---- a/tests/nosewrapper.py -+++ b/tests/nosewrapper.py +Index: eventlet-0.32.0/tests/nosewrapper.py +=================================================================== +--- eventlet-0.32.0.orig/tests/nosewrapper.py ++++ eventlet-0.32.0/tests/nosewrapper.py @@ -1,20 +1,13 @@ """ This script simply gets the paths correct for testing eventlet with the hub extension for Nose.""" @@ -83,8 +91,10 @@ -launch(argv=sys.argv) +if __name__ == '__main__': + unittest.main() ---- a/tests/__init__.py -+++ b/tests/__init__.py +Index: eventlet-0.32.0/tests/__init__.py +=================================================================== +--- eventlet-0.32.0.orig/tests/__init__.py ++++ eventlet-0.32.0/tests/__init__.py @@ -20,7 +20,7 @@ import sys import unittest import warnings @@ -102,8 +112,10 @@ raise SkipTest('CPU usage testing not supported (`import resource` failed)') r1 = resource.getrusage(resource.RUSAGE_SELF) ---- a/tests/dagpool_test.py -+++ b/tests/dagpool_test.py +Index: eventlet-0.32.0/tests/dagpool_test.py +=================================================================== +--- eventlet-0.32.0.orig/tests/dagpool_test.py ++++ eventlet-0.32.0/tests/dagpool_test.py @@ -5,7 +5,6 @@ @brief Test DAGPool class """