forked from pool/python-pyftpdlib
Accepting request 1161433 from devel:languages:python
- update to 1.5.9: * Enhancements * #611: use ruff code style checker instead of flake8 + isort (much faster + makes many more code quality checks). * Bug fixes * #604: client connection may be reset in PASV/EPSV mode during TLS handshake. (patch by Benedikt McMullin) * #607: possible infinite wait in Epoll (patch by @stat1c-void) * #607: possible infinite traceback printing in DTPHandler (patch by @stat1c-void) * #613: (CRITICAL) bugfix for TLS disconnect causing 100% CPU usage. (patch by @hakai) * #614: close connection on SSL EOF error, instead of erroneously replying with "226 Transfer completed." * Enhancements * #586: removed Python 2.6 support. * #591: speedup logging by 28% by using logging._srcfile = None trick. This avoids calling calling sys._getframe() for each log record. * #605: added support for Python 3.12. * Enhancements * #544: replace Travis with Github Actions for CI testing. * Bug fixes * #481: fix [WinError 10038] an operation was attempted on something that is not a socket. (patch by Tailing Yuan) * #578, [critical]: FTPS broke with PyOpenSSL version 22.1.0. * Enhancements * #467: added pre-fork concurrency model, spawn()ing worker processes to split load. OBS-URL: https://build.opensuse.org/request/show/1161433 OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-pyftpdlib?expand=0&rev=21
This commit is contained in:
BIN
pyftpdlib-1.5.7.tar.gz
(Stored with Git LFS)
BIN
pyftpdlib-1.5.7.tar.gz
(Stored with Git LFS)
Binary file not shown.
3
pyftpdlib-1.5.9.tar.gz
Normal file
3
pyftpdlib-1.5.9.tar.gz
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:323d4c42f1406aedb4df18faf680f64f32c080ff66f6c26090ba592f5bfc4a0f
|
||||||
|
size 204755
|
@@ -1,3 +1,64 @@
|
|||||||
|
-------------------------------------------------------------------
|
||||||
|
Mon Mar 25 14:21:26 UTC 2024 - Dirk Müller <dmueller@suse.com>
|
||||||
|
|
||||||
|
- update to 1.5.9:
|
||||||
|
* Enhancements
|
||||||
|
* #611: use ruff code style checker instead of flake8 + isort
|
||||||
|
(much faster + makes many more code quality checks).
|
||||||
|
* Bug fixes
|
||||||
|
* #604: client connection may be reset in PASV/EPSV mode during
|
||||||
|
TLS handshake. (patch by Benedikt McMullin)
|
||||||
|
* #607: possible infinite wait in Epoll (patch by
|
||||||
|
@stat1c-void)
|
||||||
|
* #607: possible infinite traceback printing in DTPHandler
|
||||||
|
(patch by @stat1c-void)
|
||||||
|
* #613: (CRITICAL) bugfix for TLS disconnect causing 100% CPU
|
||||||
|
usage. (patch by @hakai)
|
||||||
|
* #614: close connection on SSL EOF error, instead of
|
||||||
|
erroneously replying with "226 Transfer completed."
|
||||||
|
* Enhancements
|
||||||
|
* #586: removed Python 2.6 support.
|
||||||
|
* #591: speedup logging by 28% by using logging._srcfile = None
|
||||||
|
trick. This avoids calling calling sys._getframe() for each
|
||||||
|
log record.
|
||||||
|
* #605: added support for Python 3.12.
|
||||||
|
* Enhancements
|
||||||
|
* #544: replace Travis with Github Actions for CI testing.
|
||||||
|
* Bug fixes
|
||||||
|
* #481: fix [WinError 10038] an operation was attempted on
|
||||||
|
something that is not a socket. (patch by Tailing Yuan)
|
||||||
|
* #578, [critical]: FTPS broke with PyOpenSSL version 22.1.0.
|
||||||
|
* Enhancements
|
||||||
|
* #467: added pre-fork concurrency model, spawn()ing worker
|
||||||
|
processes to split load.
|
||||||
|
* #520: directory LISTing is now 3.7x times faster.
|
||||||
|
* Enhancements
|
||||||
|
* #495: colored test output.
|
||||||
|
* Bug fixes
|
||||||
|
* #492: CRLF line endings are replaced with CRCRLF in ASCII
|
||||||
|
mode downloads.
|
||||||
|
* #496: import error due to multiprocessing.Lock() bug.
|
||||||
|
* Enhancements
|
||||||
|
* #463: FTPServer class can now be used as a context manager.
|
||||||
|
* Bug fixes
|
||||||
|
* #431: Ctrl-C doesn't exit python -m pyftpdlib on Windows.
|
||||||
|
* #436: ThreadedFTPServer.max_cons is evaluated
|
||||||
|
threading.activeCount(). If the user uses threads of its own
|
||||||
|
it will consume the number of max_cons.
|
||||||
|
* #447: ThreadedFTPServer and MultiprocessFTPServer do not
|
||||||
|
join() tasks which are no longer consuming resources.
|
||||||
|
* Enhancements
|
||||||
|
* #201: implemented SITE MFMT command which changes file
|
||||||
|
modification time. (patch by Tahir Ijaz)
|
||||||
|
* #327: add username and password command line options
|
||||||
|
* #433: documentation moved to readthedocs:
|
||||||
|
http://pyftpdlib.readthedocs.io
|
||||||
|
* Bug fixes
|
||||||
|
* #403: fix duplicated output log. (path by PonyPC)
|
||||||
|
* #414: Respond successfully to STOR only after closing file
|
||||||
|
handle.
|
||||||
|
- drop support-python-312.patch (upstream)
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
Tue Sep 19 05:14:22 UTC 2023 - Steve Kowalik <steven.kowalik@suse.com>
|
Tue Sep 19 05:14:22 UTC 2023 - Steve Kowalik <steven.kowalik@suse.com>
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#
|
#
|
||||||
# spec file for package python-pyftpdlib
|
# spec file for package python-pyftpdlib
|
||||||
#
|
#
|
||||||
# Copyright (c) 2023 SUSE LLC
|
# Copyright (c) 2024 SUSE LLC
|
||||||
# Copyright (c) 2016 LISA GmbH, Bingen, Germany.
|
# Copyright (c) 2016 LISA GmbH, Bingen, Germany.
|
||||||
#
|
#
|
||||||
# All modifications and additions to the file contributed by third parties
|
# All modifications and additions to the file contributed by third parties
|
||||||
@@ -19,15 +19,13 @@
|
|||||||
|
|
||||||
%{?sle15_python_module_pythons}
|
%{?sle15_python_module_pythons}
|
||||||
Name: python-pyftpdlib
|
Name: python-pyftpdlib
|
||||||
Version: 1.5.7
|
Version: 1.5.9
|
||||||
Release: 0
|
Release: 0
|
||||||
Summary: Asynchronous FTP server library for Python
|
Summary: Asynchronous FTP server library for Python
|
||||||
License: MIT
|
License: MIT
|
||||||
URL: https://github.com/giampaolo/pyftpdlib/
|
URL: https://github.com/giampaolo/pyftpdlib/
|
||||||
Source: https://files.pythonhosted.org/packages/source/p/pyftpdlib/pyftpdlib-%{version}.tar.gz
|
Source: https://files.pythonhosted.org/packages/source/p/pyftpdlib/pyftpdlib-%{version}.tar.gz
|
||||||
Source1: keycert.pem
|
Source1: keycert.pem
|
||||||
# PATCH-FIX-UPSTREAM gh#giampaolo/pyftpdlib#605
|
|
||||||
Patch0: support-python-312.patch
|
|
||||||
BuildRequires: %{python_module pip}
|
BuildRequires: %{python_module pip}
|
||||||
BuildRequires: %{python_module psutil}
|
BuildRequires: %{python_module psutil}
|
||||||
BuildRequires: %{python_module pyOpenSSL}
|
BuildRequires: %{python_module pyOpenSSL}
|
||||||
@@ -39,7 +37,7 @@ BuildRequires: fdupes
|
|||||||
BuildRequires: python-rpm-macros
|
BuildRequires: python-rpm-macros
|
||||||
Requires: python-pyOpenSSL
|
Requires: python-pyOpenSSL
|
||||||
Requires(post): update-alternatives
|
Requires(post): update-alternatives
|
||||||
Requires(postun):update-alternatives
|
Requires(postun): update-alternatives
|
||||||
Recommends: python-pysendfile
|
Recommends: python-pysendfile
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
%python_subpackages
|
%python_subpackages
|
||||||
|
@@ -1,896 +0,0 @@
|
|||||||
From a0784dc320042f7f3d8aa0cf2bde5a06a1d771a3 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Giampaolo Rodola <g.rodola@gmail.com>
|
|
||||||
Date: Thu, 3 Aug 2023 12:04:08 +0800
|
|
||||||
Subject: [PATCH 1/4] copy asyncore.py and asynchat.py from python 3.11
|
|
||||||
|
|
||||||
---
|
|
||||||
MANIFEST.in | 2 +
|
|
||||||
Makefile | 2 +-
|
|
||||||
pyftpdlib/_asynchat.py | 217 +++++++++++++++
|
|
||||||
pyftpdlib/_asyncore.py | 587 +++++++++++++++++++++++++++++++++++++++++
|
|
||||||
pyftpdlib/handlers.py | 7 +-
|
|
||||||
pyftpdlib/ioloop.py | 10 +-
|
|
||||||
6 files changed, 821 insertions(+), 4 deletions(-)
|
|
||||||
create mode 100644 pyftpdlib/_asynchat.py
|
|
||||||
create mode 100644 pyftpdlib/_asyncore.py
|
|
||||||
|
|
||||||
Index: pyftpdlib-1.5.7/MANIFEST.in
|
|
||||||
===================================================================
|
|
||||||
--- pyftpdlib-1.5.7.orig/MANIFEST.in
|
|
||||||
+++ pyftpdlib-1.5.7/MANIFEST.in
|
|
||||||
@@ -35,6 +35,8 @@ include docs/tutorial.rst
|
|
||||||
include make.bat
|
|
||||||
include pyftpdlib/__init__.py
|
|
||||||
include pyftpdlib/__main__.py
|
|
||||||
+include pyftpdlib/_asynchat.py
|
|
||||||
+include pyftpdlib/_asyncore.py
|
|
||||||
include pyftpdlib/_compat.py
|
|
||||||
include pyftpdlib/authorizers.py
|
|
||||||
include pyftpdlib/filesystems.py
|
|
||||||
Index: pyftpdlib-1.5.7/pyftpdlib/_asynchat.py
|
|
||||||
===================================================================
|
|
||||||
--- /dev/null
|
|
||||||
+++ pyftpdlib-1.5.7/pyftpdlib/_asynchat.py
|
|
||||||
@@ -0,0 +1,217 @@
|
|
||||||
+# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
|
|
||||||
+# Author: Sam Rushing <rushing@nightmare.com>
|
|
||||||
+
|
|
||||||
+# ======================================================================
|
|
||||||
+# Copyright 1996 by Sam Rushing
|
|
||||||
+#
|
|
||||||
+# All Rights Reserved
|
|
||||||
+#
|
|
||||||
+# Permission to use, copy, modify, and distribute this software and
|
|
||||||
+# its documentation for any purpose and without fee is hereby
|
|
||||||
+# granted, provided that the above copyright notice appear in all
|
|
||||||
+# copies and that both that copyright notice and this permission
|
|
||||||
+# notice appear in supporting documentation, and that the name of Sam
|
|
||||||
+# Rushing not be used in advertising or publicity pertaining to
|
|
||||||
+# distribution of the software without specific, written prior
|
|
||||||
+# permission.
|
|
||||||
+#
|
|
||||||
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
||||||
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
|
||||||
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
|
||||||
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
||||||
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
||||||
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
+# ======================================================================
|
|
||||||
+
|
|
||||||
+from collections import deque
|
|
||||||
+
|
|
||||||
+from . import _asyncore as asyncore
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class async_chat(asyncore.dispatcher):
|
|
||||||
+ ac_in_buffer_size = 65536
|
|
||||||
+ ac_out_buffer_size = 65536
|
|
||||||
+ use_encoding = 0
|
|
||||||
+ encoding = 'latin-1'
|
|
||||||
+
|
|
||||||
+ def __init__(self, sock=None, map=None):
|
|
||||||
+ self.ac_in_buffer = b''
|
|
||||||
+ self.incoming = []
|
|
||||||
+ self.producer_fifo = deque()
|
|
||||||
+ asyncore.dispatcher.__init__(self, sock, map)
|
|
||||||
+
|
|
||||||
+ def collect_incoming_data(self, data):
|
|
||||||
+ raise NotImplementedError("must be implemented in subclass")
|
|
||||||
+
|
|
||||||
+ def _collect_incoming_data(self, data):
|
|
||||||
+ self.incoming.append(data)
|
|
||||||
+
|
|
||||||
+ def _get_data(self):
|
|
||||||
+ d = b''.join(self.incoming)
|
|
||||||
+ del self.incoming[:]
|
|
||||||
+ return d
|
|
||||||
+
|
|
||||||
+ def found_terminator(self):
|
|
||||||
+ raise NotImplementedError("must be implemented in subclass")
|
|
||||||
+
|
|
||||||
+ def set_terminator(self, term):
|
|
||||||
+ if isinstance(term, str) and self.use_encoding:
|
|
||||||
+ term = bytes(term, self.encoding)
|
|
||||||
+ elif isinstance(term, int) and term < 0:
|
|
||||||
+ raise ValueError('the number of received bytes must be positive')
|
|
||||||
+ self.terminator = term
|
|
||||||
+
|
|
||||||
+ def get_terminator(self):
|
|
||||||
+ return self.terminator
|
|
||||||
+
|
|
||||||
+ def handle_read(self):
|
|
||||||
+
|
|
||||||
+ try:
|
|
||||||
+ data = self.recv(self.ac_in_buffer_size)
|
|
||||||
+ except BlockingIOError:
|
|
||||||
+ return
|
|
||||||
+ except OSError:
|
|
||||||
+ self.handle_error()
|
|
||||||
+ return
|
|
||||||
+
|
|
||||||
+ if isinstance(data, str) and self.use_encoding:
|
|
||||||
+ data = bytes(str, self.encoding)
|
|
||||||
+ self.ac_in_buffer = self.ac_in_buffer + data
|
|
||||||
+
|
|
||||||
+ while self.ac_in_buffer:
|
|
||||||
+ lb = len(self.ac_in_buffer)
|
|
||||||
+ terminator = self.get_terminator()
|
|
||||||
+ if not terminator:
|
|
||||||
+ # no terminator, collect it all
|
|
||||||
+ self.collect_incoming_data(self.ac_in_buffer)
|
|
||||||
+ self.ac_in_buffer = b''
|
|
||||||
+ elif isinstance(terminator, int):
|
|
||||||
+ # numeric terminator
|
|
||||||
+ n = terminator
|
|
||||||
+ if lb < n:
|
|
||||||
+ self.collect_incoming_data(self.ac_in_buffer)
|
|
||||||
+ self.ac_in_buffer = b''
|
|
||||||
+ self.terminator = self.terminator - lb
|
|
||||||
+ else:
|
|
||||||
+ self.collect_incoming_data(self.ac_in_buffer[:n])
|
|
||||||
+ self.ac_in_buffer = self.ac_in_buffer[n:]
|
|
||||||
+ self.terminator = 0
|
|
||||||
+ self.found_terminator()
|
|
||||||
+ else:
|
|
||||||
+ terminator_len = len(terminator)
|
|
||||||
+ index = self.ac_in_buffer.find(terminator)
|
|
||||||
+ if index != -1:
|
|
||||||
+ if index > 0:
|
|
||||||
+ self.collect_incoming_data(self.ac_in_buffer[:index])
|
|
||||||
+ self.ac_in_buffer = self.ac_in_buffer[index +
|
|
||||||
+ terminator_len:]
|
|
||||||
+ self.found_terminator()
|
|
||||||
+ else:
|
|
||||||
+ index = find_prefix_at_end(self.ac_in_buffer, terminator)
|
|
||||||
+ if index:
|
|
||||||
+ if index != lb:
|
|
||||||
+ self.collect_incoming_data(
|
|
||||||
+ self.ac_in_buffer[:-index])
|
|
||||||
+ self.ac_in_buffer = self.ac_in_buffer[-index:]
|
|
||||||
+ break
|
|
||||||
+ else:
|
|
||||||
+ self.collect_incoming_data(self.ac_in_buffer)
|
|
||||||
+ self.ac_in_buffer = b''
|
|
||||||
+
|
|
||||||
+ def handle_write(self):
|
|
||||||
+ self.initiate_send()
|
|
||||||
+
|
|
||||||
+ def handle_close(self):
|
|
||||||
+ self.close()
|
|
||||||
+
|
|
||||||
+ def push(self, data):
|
|
||||||
+ if not isinstance(data, (bytes, bytearray, memoryview)):
|
|
||||||
+ raise TypeError('data argument must be byte-ish (%r)',
|
|
||||||
+ type(data))
|
|
||||||
+ sabs = self.ac_out_buffer_size
|
|
||||||
+ if len(data) > sabs:
|
|
||||||
+ for i in range(0, len(data), sabs):
|
|
||||||
+ self.producer_fifo.append(data[i:i + sabs])
|
|
||||||
+ else:
|
|
||||||
+ self.producer_fifo.append(data)
|
|
||||||
+ self.initiate_send()
|
|
||||||
+
|
|
||||||
+ def push_with_producer(self, producer):
|
|
||||||
+ self.producer_fifo.append(producer)
|
|
||||||
+ self.initiate_send()
|
|
||||||
+
|
|
||||||
+ def readable(self):
|
|
||||||
+ return 1
|
|
||||||
+
|
|
||||||
+ def writable(self):
|
|
||||||
+ return self.producer_fifo or (not self.connected)
|
|
||||||
+
|
|
||||||
+ def close_when_done(self):
|
|
||||||
+ self.producer_fifo.append(None)
|
|
||||||
+
|
|
||||||
+ def initiate_send(self):
|
|
||||||
+ while self.producer_fifo and self.connected:
|
|
||||||
+ first = self.producer_fifo[0]
|
|
||||||
+ if not first:
|
|
||||||
+ del self.producer_fifo[0]
|
|
||||||
+ if first is None:
|
|
||||||
+ self.handle_close()
|
|
||||||
+ return
|
|
||||||
+
|
|
||||||
+ obs = self.ac_out_buffer_size
|
|
||||||
+ try:
|
|
||||||
+ data = first[:obs]
|
|
||||||
+ except TypeError:
|
|
||||||
+ data = first.more()
|
|
||||||
+ if data:
|
|
||||||
+ self.producer_fifo.appendleft(data)
|
|
||||||
+ else:
|
|
||||||
+ del self.producer_fifo[0]
|
|
||||||
+ continue
|
|
||||||
+
|
|
||||||
+ if isinstance(data, str) and self.use_encoding:
|
|
||||||
+ data = bytes(data, self.encoding)
|
|
||||||
+
|
|
||||||
+ try:
|
|
||||||
+ num_sent = self.send(data)
|
|
||||||
+ except OSError:
|
|
||||||
+ self.handle_error()
|
|
||||||
+ return
|
|
||||||
+
|
|
||||||
+ if num_sent:
|
|
||||||
+ if num_sent < len(data) or obs < len(first):
|
|
||||||
+ self.producer_fifo[0] = first[num_sent:]
|
|
||||||
+ else:
|
|
||||||
+ del self.producer_fifo[0]
|
|
||||||
+ return
|
|
||||||
+
|
|
||||||
+ def discard_buffers(self):
|
|
||||||
+ # Emergencies only!
|
|
||||||
+ self.ac_in_buffer = b''
|
|
||||||
+ del self.incoming[:]
|
|
||||||
+ self.producer_fifo.clear()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class simple_producer:
|
|
||||||
+
|
|
||||||
+ def __init__(self, data, buffer_size=512):
|
|
||||||
+ self.data = data
|
|
||||||
+ self.buffer_size = buffer_size
|
|
||||||
+
|
|
||||||
+ def more(self):
|
|
||||||
+ if len(self.data) > self.buffer_size:
|
|
||||||
+ result = self.data[:self.buffer_size]
|
|
||||||
+ self.data = self.data[self.buffer_size:]
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result = self.data
|
|
||||||
+ self.data = b''
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def find_prefix_at_end(haystack, needle):
|
|
||||||
+ ll = len(needle) - 1
|
|
||||||
+ while ll and not haystack.endswith(needle[:ll]):
|
|
||||||
+ ll -= 1
|
|
||||||
+ return ll
|
|
||||||
Index: pyftpdlib-1.5.7/pyftpdlib/_asyncore.py
|
|
||||||
===================================================================
|
|
||||||
--- /dev/null
|
|
||||||
+++ pyftpdlib-1.5.7/pyftpdlib/_asyncore.py
|
|
||||||
@@ -0,0 +1,587 @@
|
|
||||||
+# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
|
|
||||||
+# Author: Sam Rushing <rushing@nightmare.com>
|
|
||||||
+
|
|
||||||
+# ======================================================================
|
|
||||||
+# Copyright 1996 by Sam Rushing
|
|
||||||
+#
|
|
||||||
+# All Rights Reserved
|
|
||||||
+#
|
|
||||||
+# Permission to use, copy, modify, and distribute this software and
|
|
||||||
+# its documentation for any purpose and without fee is hereby
|
|
||||||
+# granted, provided that the above copyright notice appear in all
|
|
||||||
+# copies and that both that copyright notice and this permission
|
|
||||||
+# notice appear in supporting documentation, and that the name of Sam
|
|
||||||
+# Rushing not be used in advertising or publicity pertaining to
|
|
||||||
+# distribution of the software without specific, written prior
|
|
||||||
+# permission.
|
|
||||||
+#
|
|
||||||
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
||||||
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
|
||||||
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
|
||||||
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
||||||
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
||||||
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
+# ======================================================================
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+import os
|
|
||||||
+import select
|
|
||||||
+import socket
|
|
||||||
+import sys
|
|
||||||
+import time
|
|
||||||
+import warnings
|
|
||||||
+from errno import EAGAIN
|
|
||||||
+from errno import EALREADY
|
|
||||||
+from errno import EBADF
|
|
||||||
+from errno import ECONNABORTED
|
|
||||||
+from errno import ECONNRESET
|
|
||||||
+from errno import EINPROGRESS
|
|
||||||
+from errno import EINVAL
|
|
||||||
+from errno import EISCONN
|
|
||||||
+from errno import ENOTCONN
|
|
||||||
+from errno import EPIPE
|
|
||||||
+from errno import ESHUTDOWN
|
|
||||||
+from errno import EWOULDBLOCK
|
|
||||||
+from errno import errorcode
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+_DISCONNECTED = frozenset(
|
|
||||||
+ {ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE, EBADF})
|
|
||||||
+
|
|
||||||
+try:
|
|
||||||
+ socket_map
|
|
||||||
+except NameError:
|
|
||||||
+ socket_map = {}
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def _strerror(err):
|
|
||||||
+ try:
|
|
||||||
+ return os.strerror(err)
|
|
||||||
+ except (ValueError, OverflowError, NameError):
|
|
||||||
+ if err in errorcode:
|
|
||||||
+ return errorcode[err]
|
|
||||||
+ return "Unknown error %s" % err
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class ExitNow(Exception):
|
|
||||||
+ pass
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+_reraised_exceptions = (ExitNow, KeyboardInterrupt, SystemExit)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def read(obj):
|
|
||||||
+ try:
|
|
||||||
+ obj.handle_read_event()
|
|
||||||
+ except _reraised_exceptions:
|
|
||||||
+ raise
|
|
||||||
+ except Exception:
|
|
||||||
+ obj.handle_error()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def write(obj):
|
|
||||||
+ try:
|
|
||||||
+ obj.handle_write_event()
|
|
||||||
+ except _reraised_exceptions:
|
|
||||||
+ raise
|
|
||||||
+ except Exception:
|
|
||||||
+ obj.handle_error()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def _exception(obj):
|
|
||||||
+ try:
|
|
||||||
+ obj.handle_expt_event()
|
|
||||||
+ except _reraised_exceptions:
|
|
||||||
+ raise
|
|
||||||
+ except Exception:
|
|
||||||
+ obj.handle_error()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def readwrite(obj, flags):
|
|
||||||
+ try:
|
|
||||||
+ if flags & select.POLLIN:
|
|
||||||
+ obj.handle_read_event()
|
|
||||||
+ if flags & select.POLLOUT:
|
|
||||||
+ obj.handle_write_event()
|
|
||||||
+ if flags & select.POLLPRI:
|
|
||||||
+ obj.handle_expt_event()
|
|
||||||
+ if flags & (select.POLLHUP | select.POLLERR | select.POLLNVAL):
|
|
||||||
+ obj.handle_close()
|
|
||||||
+ except OSError as e:
|
|
||||||
+ if e.errno not in _DISCONNECTED:
|
|
||||||
+ obj.handle_error()
|
|
||||||
+ else:
|
|
||||||
+ obj.handle_close()
|
|
||||||
+ except _reraised_exceptions:
|
|
||||||
+ raise
|
|
||||||
+ except Exception:
|
|
||||||
+ obj.handle_error()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def poll(timeout=0.0, map=None):
|
|
||||||
+ if map is None:
|
|
||||||
+ map = socket_map
|
|
||||||
+ if map:
|
|
||||||
+ r = []
|
|
||||||
+ w = []
|
|
||||||
+ e = []
|
|
||||||
+ for fd, obj in list(map.items()):
|
|
||||||
+ is_r = obj.readable()
|
|
||||||
+ is_w = obj.writable()
|
|
||||||
+ if is_r:
|
|
||||||
+ r.append(fd)
|
|
||||||
+ if is_w and not obj.accepting:
|
|
||||||
+ w.append(fd)
|
|
||||||
+ if is_r or is_w:
|
|
||||||
+ e.append(fd)
|
|
||||||
+ if [] == r == w == e:
|
|
||||||
+ time.sleep(timeout)
|
|
||||||
+ return
|
|
||||||
+
|
|
||||||
+ r, w, e = select.select(r, w, e, timeout)
|
|
||||||
+
|
|
||||||
+ for fd in r:
|
|
||||||
+ obj = map.get(fd)
|
|
||||||
+ if obj is None:
|
|
||||||
+ continue
|
|
||||||
+ read(obj)
|
|
||||||
+
|
|
||||||
+ for fd in w:
|
|
||||||
+ obj = map.get(fd)
|
|
||||||
+ if obj is None:
|
|
||||||
+ continue
|
|
||||||
+ write(obj)
|
|
||||||
+
|
|
||||||
+ for fd in e:
|
|
||||||
+ obj = map.get(fd)
|
|
||||||
+ if obj is None:
|
|
||||||
+ continue
|
|
||||||
+ _exception(obj)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def poll2(timeout=0.0, map=None):
|
|
||||||
+ if map is None:
|
|
||||||
+ map = socket_map
|
|
||||||
+ if timeout is not None:
|
|
||||||
+ timeout = int(timeout * 1000)
|
|
||||||
+ pollster = select.poll()
|
|
||||||
+ if map:
|
|
||||||
+ for fd, obj in list(map.items()):
|
|
||||||
+ flags = 0
|
|
||||||
+ if obj.readable():
|
|
||||||
+ flags |= select.POLLIN | select.POLLPRI
|
|
||||||
+ if obj.writable() and not obj.accepting:
|
|
||||||
+ flags |= select.POLLOUT
|
|
||||||
+ if flags:
|
|
||||||
+ pollster.register(fd, flags)
|
|
||||||
+
|
|
||||||
+ r = pollster.poll(timeout)
|
|
||||||
+ for fd, flags in r:
|
|
||||||
+ obj = map.get(fd)
|
|
||||||
+ if obj is None:
|
|
||||||
+ continue
|
|
||||||
+ readwrite(obj, flags)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+poll3 = poll2 # Alias for backward compatibility
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def loop(timeout=30.0, use_poll=False, map=None, count=None):
|
|
||||||
+ if map is None:
|
|
||||||
+ map = socket_map
|
|
||||||
+
|
|
||||||
+ if use_poll and hasattr(select, 'poll'):
|
|
||||||
+ poll_fun = poll2
|
|
||||||
+ else:
|
|
||||||
+ poll_fun = poll
|
|
||||||
+
|
|
||||||
+ if count is None:
|
|
||||||
+ while map:
|
|
||||||
+ poll_fun(timeout, map)
|
|
||||||
+
|
|
||||||
+ else:
|
|
||||||
+ while map and count > 0:
|
|
||||||
+ poll_fun(timeout, map)
|
|
||||||
+ count = count - 1
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class dispatcher:
|
|
||||||
+
|
|
||||||
+ debug = False
|
|
||||||
+ connected = False
|
|
||||||
+ accepting = False
|
|
||||||
+ connecting = False
|
|
||||||
+ closing = False
|
|
||||||
+ addr = None
|
|
||||||
+ ignore_log_types = frozenset({'warning'})
|
|
||||||
+
|
|
||||||
+ def __init__(self, sock=None, map=None):
|
|
||||||
+ if map is None:
|
|
||||||
+ self._map = socket_map
|
|
||||||
+ else:
|
|
||||||
+ self._map = map
|
|
||||||
+
|
|
||||||
+ self._fileno = None
|
|
||||||
+
|
|
||||||
+ if sock:
|
|
||||||
+ sock.setblocking(False)
|
|
||||||
+ self.set_socket(sock, map)
|
|
||||||
+ self.connected = True
|
|
||||||
+ try:
|
|
||||||
+ self.addr = sock.getpeername()
|
|
||||||
+ except OSError as err:
|
|
||||||
+ if err.errno in (ENOTCONN, EINVAL):
|
|
||||||
+ self.connected = False
|
|
||||||
+ else:
|
|
||||||
+ self.del_channel(map)
|
|
||||||
+ raise
|
|
||||||
+ else:
|
|
||||||
+ self.socket = None
|
|
||||||
+
|
|
||||||
+ def __repr__(self):
|
|
||||||
+ status = [self.__class__.__module__ +
|
|
||||||
+ "." + self.__class__.__qualname__]
|
|
||||||
+ if self.accepting and self.addr:
|
|
||||||
+ status.append('listening')
|
|
||||||
+ elif self.connected:
|
|
||||||
+ status.append('connected')
|
|
||||||
+ if self.addr is not None:
|
|
||||||
+ try:
|
|
||||||
+ status.append('%s:%d' % self.addr)
|
|
||||||
+ except TypeError:
|
|
||||||
+ status.append(repr(self.addr))
|
|
||||||
+ return '<%s at %#x>' % (' '.join(status), id(self))
|
|
||||||
+
|
|
||||||
+ def add_channel(self, map=None):
|
|
||||||
+ # self.log_info('adding channel %s' % self)
|
|
||||||
+ if map is None:
|
|
||||||
+ map = self._map
|
|
||||||
+ map[self._fileno] = self
|
|
||||||
+
|
|
||||||
+ def del_channel(self, map=None):
|
|
||||||
+ fd = self._fileno
|
|
||||||
+ if map is None:
|
|
||||||
+ map = self._map
|
|
||||||
+ if fd in map:
|
|
||||||
+ del map[fd]
|
|
||||||
+ self._fileno = None
|
|
||||||
+
|
|
||||||
+ def create_socket(self, family=socket.AF_INET, type=socket.SOCK_STREAM):
|
|
||||||
+ self.family_and_type = family, type
|
|
||||||
+ sock = socket.socket(family, type)
|
|
||||||
+ sock.setblocking(False)
|
|
||||||
+ self.set_socket(sock)
|
|
||||||
+
|
|
||||||
+ def set_socket(self, sock, map=None):
|
|
||||||
+ self.socket = sock
|
|
||||||
+ self._fileno = sock.fileno()
|
|
||||||
+ self.add_channel(map)
|
|
||||||
+
|
|
||||||
+ def set_reuse_addr(self):
|
|
||||||
+ try:
|
|
||||||
+ self.socket.setsockopt(
|
|
||||||
+ socket.SOL_SOCKET, socket.SO_REUSEADDR,
|
|
||||||
+ self.socket.getsockopt(socket.SOL_SOCKET,
|
|
||||||
+ socket.SO_REUSEADDR) | 1
|
|
||||||
+ )
|
|
||||||
+ except OSError:
|
|
||||||
+ pass
|
|
||||||
+
|
|
||||||
+ def readable(self):
|
|
||||||
+ return True
|
|
||||||
+
|
|
||||||
+ def writable(self):
|
|
||||||
+ return True
|
|
||||||
+
|
|
||||||
+ def listen(self, num):
|
|
||||||
+ self.accepting = True
|
|
||||||
+ if os.name == 'nt' and num > 5:
|
|
||||||
+ num = 5
|
|
||||||
+ return self.socket.listen(num)
|
|
||||||
+
|
|
||||||
+ def bind(self, addr):
|
|
||||||
+ 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':
|
|
||||||
+ 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):
|
|
||||||
+ try:
|
|
||||||
+ conn, addr = self.socket.accept()
|
|
||||||
+ except TypeError:
|
|
||||||
+ return None
|
|
||||||
+ except OSError as why:
|
|
||||||
+ if why.errno in (EWOULDBLOCK, ECONNABORTED, EAGAIN):
|
|
||||||
+ return None
|
|
||||||
+ else:
|
|
||||||
+ raise
|
|
||||||
+ else:
|
|
||||||
+ return conn, addr
|
|
||||||
+
|
|
||||||
+ def send(self, data):
|
|
||||||
+ try:
|
|
||||||
+ result = self.socket.send(data)
|
|
||||||
+ return result
|
|
||||||
+ except OSError as why:
|
|
||||||
+ if why.errno == EWOULDBLOCK:
|
|
||||||
+ return 0
|
|
||||||
+ elif why.errno in _DISCONNECTED:
|
|
||||||
+ self.handle_close()
|
|
||||||
+ return 0
|
|
||||||
+ else:
|
|
||||||
+ raise
|
|
||||||
+
|
|
||||||
+ def recv(self, buffer_size):
|
|
||||||
+ try:
|
|
||||||
+ data = self.socket.recv(buffer_size)
|
|
||||||
+ if not data:
|
|
||||||
+ self.handle_close()
|
|
||||||
+ return b''
|
|
||||||
+ else:
|
|
||||||
+ return data
|
|
||||||
+ except OSError as why:
|
|
||||||
+ if why.errno in _DISCONNECTED:
|
|
||||||
+ self.handle_close()
|
|
||||||
+ return b''
|
|
||||||
+ else:
|
|
||||||
+ raise
|
|
||||||
+
|
|
||||||
+ def close(self):
|
|
||||||
+ self.connected = False
|
|
||||||
+ self.accepting = False
|
|
||||||
+ self.connecting = False
|
|
||||||
+ self.del_channel()
|
|
||||||
+ if self.socket is not None:
|
|
||||||
+ try:
|
|
||||||
+ self.socket.close()
|
|
||||||
+ except OSError as why:
|
|
||||||
+ if why.errno not in (ENOTCONN, EBADF):
|
|
||||||
+ raise
|
|
||||||
+
|
|
||||||
+ def log(self, message):
|
|
||||||
+ sys.stderr.write('log: %s\n' % str(message))
|
|
||||||
+
|
|
||||||
+ def log_info(self, message, type='info'):
|
|
||||||
+ if type not in self.ignore_log_types:
|
|
||||||
+ print('%s: %s' % (type, message)) # noqa
|
|
||||||
+
|
|
||||||
+ def handle_read_event(self):
|
|
||||||
+ if self.accepting:
|
|
||||||
+ # accepting sockets are never connected, they "spawn" new
|
|
||||||
+ # sockets that are connected
|
|
||||||
+ self.handle_accept()
|
|
||||||
+ elif not self.connected:
|
|
||||||
+ if self.connecting:
|
|
||||||
+ self.handle_connect_event()
|
|
||||||
+ self.handle_read()
|
|
||||||
+ else:
|
|
||||||
+ self.handle_read()
|
|
||||||
+
|
|
||||||
+ def handle_connect_event(self):
|
|
||||||
+ err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
|
|
||||||
+ if err != 0:
|
|
||||||
+ raise OSError(err, _strerror(err))
|
|
||||||
+ self.handle_connect()
|
|
||||||
+ self.connected = True
|
|
||||||
+ self.connecting = False
|
|
||||||
+
|
|
||||||
+ def handle_write_event(self):
|
|
||||||
+ if self.accepting:
|
|
||||||
+ return
|
|
||||||
+
|
|
||||||
+ if not self.connected:
|
|
||||||
+ if self.connecting:
|
|
||||||
+ self.handle_connect_event()
|
|
||||||
+ self.handle_write()
|
|
||||||
+
|
|
||||||
+ def handle_expt_event(self):
|
|
||||||
+ err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
|
|
||||||
+ if err != 0:
|
|
||||||
+ self.handle_close()
|
|
||||||
+ else:
|
|
||||||
+ self.handle_expt()
|
|
||||||
+
|
|
||||||
+ def handle_error(self):
|
|
||||||
+ nil, t, v, tbinfo = compact_traceback()
|
|
||||||
+ try:
|
|
||||||
+ self_repr = repr(self)
|
|
||||||
+ except Exception:
|
|
||||||
+ self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
|
|
||||||
+
|
|
||||||
+ self.log_info(
|
|
||||||
+ 'uncaptured python exception, closing channel %s (%s:%s %s)' % (
|
|
||||||
+ self_repr,
|
|
||||||
+ t,
|
|
||||||
+ v,
|
|
||||||
+ tbinfo
|
|
||||||
+ ),
|
|
||||||
+ 'error'
|
|
||||||
+ )
|
|
||||||
+ self.handle_close()
|
|
||||||
+
|
|
||||||
+ def handle_expt(self):
|
|
||||||
+ self.log_info('unhandled incoming priority event', 'warning')
|
|
||||||
+
|
|
||||||
+ def handle_read(self):
|
|
||||||
+ self.log_info('unhandled read event', 'warning')
|
|
||||||
+
|
|
||||||
+ def handle_write(self):
|
|
||||||
+ self.log_info('unhandled write event', 'warning')
|
|
||||||
+
|
|
||||||
+ def handle_connect(self):
|
|
||||||
+ self.log_info('unhandled connect event', 'warning')
|
|
||||||
+
|
|
||||||
+ def handle_accept(self):
|
|
||||||
+ pair = self.accept()
|
|
||||||
+ if pair is not None:
|
|
||||||
+ self.handle_accepted(*pair)
|
|
||||||
+
|
|
||||||
+ def handle_accepted(self, sock, addr):
|
|
||||||
+ sock.close()
|
|
||||||
+ self.log_info('unhandled accepted event', 'warning')
|
|
||||||
+
|
|
||||||
+ def handle_close(self):
|
|
||||||
+ self.log_info('unhandled close event', 'warning')
|
|
||||||
+ self.close()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+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:]
|
|
||||||
+
|
|
||||||
+ def handle_write(self):
|
|
||||||
+ self.initiate_send()
|
|
||||||
+
|
|
||||||
+ def writable(self):
|
|
||||||
+ return (not self.connected) or len(self.out_buffer)
|
|
||||||
+
|
|
||||||
+ def send(self, data):
|
|
||||||
+ if self.debug:
|
|
||||||
+ self.log_info('sending %s' % repr(data))
|
|
||||||
+ self.out_buffer = self.out_buffer + data
|
|
||||||
+ self.initiate_send()
|
|
||||||
+
|
|
||||||
+# ---------------------------------------------------------------------------
|
|
||||||
+# used for debugging.
|
|
||||||
+# ---------------------------------------------------------------------------
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def compact_traceback():
|
|
||||||
+ t, v, tb = sys.exc_info()
|
|
||||||
+ tbinfo = []
|
|
||||||
+ if not tb: # Must have a traceback
|
|
||||||
+ raise AssertionError("traceback does not exist")
|
|
||||||
+ while tb:
|
|
||||||
+ tbinfo.append((
|
|
||||||
+ tb.tb_frame.f_code.co_filename,
|
|
||||||
+ tb.tb_frame.f_code.co_name,
|
|
||||||
+ str(tb.tb_lineno)
|
|
||||||
+ ))
|
|
||||||
+ tb = tb.tb_next
|
|
||||||
+
|
|
||||||
+ # just to be safe
|
|
||||||
+ del tb
|
|
||||||
+
|
|
||||||
+ file, function, line = tbinfo[-1]
|
|
||||||
+ info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
|
|
||||||
+ return (file, function, line), t, v, info
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def close_all(map=None, ignore_all=False):
|
|
||||||
+ if map is None:
|
|
||||||
+ map = socket_map
|
|
||||||
+ for x in list(map.values()):
|
|
||||||
+ try:
|
|
||||||
+ x.close()
|
|
||||||
+ except OSError as x:
|
|
||||||
+ if x.errno == EBADF:
|
|
||||||
+ pass
|
|
||||||
+ elif not ignore_all:
|
|
||||||
+ raise
|
|
||||||
+ except _reraised_exceptions:
|
|
||||||
+ raise
|
|
||||||
+ except Exception:
|
|
||||||
+ if not ignore_all:
|
|
||||||
+ raise
|
|
||||||
+ map.clear()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+if os.name == 'posix':
|
|
||||||
+ class file_wrapper:
|
|
||||||
+ # Here we override just enough to make a file
|
|
||||||
+ # look like a socket for the purposes of asyncore.
|
|
||||||
+ # The passed fd is automatically os.dup()'d
|
|
||||||
+
|
|
||||||
+ def __init__(self, fd):
|
|
||||||
+ self.fd = os.dup(fd)
|
|
||||||
+
|
|
||||||
+ def __del__(self):
|
|
||||||
+ if self.fd >= 0:
|
|
||||||
+ warnings.warn("unclosed file %r" % self, ResourceWarning,
|
|
||||||
+ source=self, stacklevel=2)
|
|
||||||
+ self.close()
|
|
||||||
+
|
|
||||||
+ def recv(self, *args):
|
|
||||||
+ return os.read(self.fd, *args)
|
|
||||||
+
|
|
||||||
+ def send(self, *args):
|
|
||||||
+ return os.write(self.fd, *args)
|
|
||||||
+
|
|
||||||
+ def getsockopt(self, level, optname, buflen=None):
|
|
||||||
+ if (level == socket.SOL_SOCKET and
|
|
||||||
+ optname == socket.SO_ERROR and
|
|
||||||
+ not buflen):
|
|
||||||
+ return 0
|
|
||||||
+ raise NotImplementedError("Only asyncore specific behaviour "
|
|
||||||
+ "implemented.")
|
|
||||||
+
|
|
||||||
+ read = recv
|
|
||||||
+ write = send
|
|
||||||
+
|
|
||||||
+ def close(self):
|
|
||||||
+ if self.fd < 0:
|
|
||||||
+ return
|
|
||||||
+ fd = self.fd
|
|
||||||
+ self.fd = -1
|
|
||||||
+ os.close(fd)
|
|
||||||
+
|
|
||||||
+ def fileno(self):
|
|
||||||
+ return self.fd
|
|
||||||
+
|
|
||||||
+ class file_dispatcher(dispatcher):
|
|
||||||
+
|
|
||||||
+ def __init__(self, fd, map=None):
|
|
||||||
+ dispatcher.__init__(self, None, map)
|
|
||||||
+ self.connected = True
|
|
||||||
+ try:
|
|
||||||
+ fd = fd.fileno()
|
|
||||||
+ except AttributeError:
|
|
||||||
+ pass
|
|
||||||
+ self.set_file(fd)
|
|
||||||
+ # set it to non-blocking mode
|
|
||||||
+ os.set_blocking(fd, False)
|
|
||||||
+
|
|
||||||
+ def set_file(self, fd):
|
|
||||||
+ self.socket = file_wrapper(fd)
|
|
||||||
+ self._fileno = self.socket.fileno()
|
|
||||||
+ self.add_channel()
|
|
||||||
Index: pyftpdlib-1.5.7/pyftpdlib/handlers.py
|
|
||||||
===================================================================
|
|
||||||
--- pyftpdlib-1.5.7.orig/pyftpdlib/handlers.py
|
|
||||||
+++ pyftpdlib-1.5.7/pyftpdlib/handlers.py
|
|
||||||
@@ -2,7 +2,6 @@
|
|
||||||
# Use of this source code is governed by MIT license that can be
|
|
||||||
# found in the LICENSE file.
|
|
||||||
|
|
||||||
-import asynchat
|
|
||||||
import contextlib
|
|
||||||
import errno
|
|
||||||
import glob
|
|
||||||
@@ -57,6 +56,12 @@ from .log import debug
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
+if sys.version_info[:2] >= (3, 12):
|
|
||||||
+ from . import _asynchat as asynchat
|
|
||||||
+else:
|
|
||||||
+ import asynchat
|
|
||||||
+
|
|
||||||
+
|
|
||||||
CR_BYTE = ord('\r')
|
|
||||||
|
|
||||||
|
|
||||||
Index: pyftpdlib-1.5.7/pyftpdlib/ioloop.py
|
|
||||||
===================================================================
|
|
||||||
--- pyftpdlib-1.5.7.orig/pyftpdlib/ioloop.py
|
|
||||||
+++ pyftpdlib-1.5.7/pyftpdlib/ioloop.py
|
|
||||||
@@ -56,8 +56,6 @@ server = Server('localhost', 8021)
|
|
||||||
IOLoop.instance().loop()
|
|
||||||
"""
|
|
||||||
|
|
||||||
-import asynchat
|
|
||||||
-import asyncore
|
|
||||||
import errno
|
|
||||||
import heapq
|
|
||||||
import os
|
|
||||||
@@ -80,6 +78,14 @@ from .log import is_logging_configured
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
+if sys.version_info[:2] >= (3, 12):
|
|
||||||
+ from . import _asynchat as asynchat
|
|
||||||
+ from . import _asyncore as asyncore
|
|
||||||
+else:
|
|
||||||
+ import asynchat
|
|
||||||
+ import asyncore
|
|
||||||
+
|
|
||||||
+
|
|
||||||
timer = getattr(time, 'monotonic', time.time)
|
|
||||||
_read = asyncore.read
|
|
||||||
_write = asyncore.write
|
|
Reference in New Issue
Block a user