[Zope3-checkins] CVS: Zope3/src/zope/server/ftp - __init__.py:1.2 commonftpactivitylogger.py:1.2 ftpserver.py:1.2 ftpserverchannel.py:1.2 ftpstatusmessages.py:1.2 osemulators.py:1.2 passiveacceptor.py:1.2 publisherfilesystemaccess.py:1.2 publisherftpserver.py:1.2 publisherftpserverchannel.py:1.2 recvchannel.py:1.2 testfilesystemaccess.py:1.2 xmitchannel.py:1.2
Jim Fulton
jim@zope.com
Wed, 25 Dec 2002 09:15:54 -0500
Update of /cvs-repository/Zope3/src/zope/server/ftp
In directory cvs.zope.org:/tmp/cvs-serv20790/src/zope/server/ftp
Added Files:
__init__.py commonftpactivitylogger.py ftpserver.py
ftpserverchannel.py ftpstatusmessages.py osemulators.py
passiveacceptor.py publisherfilesystemaccess.py
publisherftpserver.py publisherftpserverchannel.py
recvchannel.py testfilesystemaccess.py xmitchannel.py
Log Message:
Grand renaming:
- Renamed most files (especially python modules) to lower case.
- Moved views and interfaces into separate hierarchies within each
project, where each top-level directory under the zope package
is a separate project.
- Moved everything to src from lib/python.
lib/python will eventually go away. I need access to the cvs
repository to make this happen, however.
There are probably some bits that are broken. All tests pass
and zope runs, but I haven't tried everything. There are a number
of cleanups I'll work on tomorrow.
=== Zope3/src/zope/server/ftp/__init__.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/__init__.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
=== Zope3/src/zope/server/ftp/commonftpactivitylogger.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/commonftpactivitylogger.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,54 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import time
+import sys
+
+from zope.server.logger.filelogger import FileLogger
+from zope.server.logger.resolvinglogger import ResolvingLogger
+from zope.server.logger.unresolvinglogger import UnresolvingLogger
+
+class CommonFTPActivityLogger:
+ """Outputs hits in common HTTP log format.
+ """
+
+ def __init__(self, logger_object=None, resolver=None):
+ if logger_object is None:
+ logger_object = FileLogger(sys.stdout)
+
+ if resolver is not None:
+ self.output = ResolvingLogger(resolver, logger_object)
+ else:
+ self.output = UnresolvingLogger(logger_object)
+
+
+ def log(self, task):
+ """
+ Receives a completed task and logs it in the
+ common log format.
+ """
+
+ now = time.localtime(time.time())
+
+ message = '%s [%s] "%s %s"' % (task.channel.username,
+ time.strftime('%Y/%m/%d %H:%M', now),
+ task.m_name[4:].upper(),
+ task.channel.cwd,
+ )
+
+ self.output.logRequest('127.0.0.1', message)
=== Zope3/src/zope/server/ftp/ftpserver.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/ftpserver.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+import asyncore
+from zope.server.ftp.ftpserverchannel import FTPServerChannel
+from zope.server.serverbase import ServerBase
+from zope.server.interfaces.vfs import IFilesystemAccess
+
+
+
+class FTPServer(ServerBase):
+ """Generic FTP Server"""
+
+ channel_class = FTPServerChannel
+ SERVER_IDENT = 'zope.server.ftp'
+
+
+ def __init__(self, ip, port, fs_access, *args, **kw):
+
+ assert IFilesystemAccess.isImplementedBy(fs_access)
+ self.fs_access = fs_access
+
+ super(FTPServer, self).__init__(ip, port, *args, **kw)
+
+
+if __name__ == '__main__':
+ from zope.server.taskthreads import ThreadedTaskDispatcher
+ from zope.server.vfs.osfilesystem import OSFileSystem
+ from zope.server.vfs.testfilesystemaccess import TestFilesystemAccess
+ td = ThreadedTaskDispatcher()
+ td.setThreadCount(4)
+ fs = OSFileSystem('/')
+ fs_access = TestFilesystemAccess(fs)
+ FTPServer('', 8021, fs_access, task_dispatcher=td)
+ try:
+ while 1:
+ asyncore.poll(5)
+ print 'active channels:', FTPServerChannel.active_channels
+ except KeyboardInterrupt:
+ print 'shutting down...'
+ td.shutdown()
=== Zope3/src/zope/server/ftp/ftpserverchannel.py 1.1 => 1.2 === (443/543 lines abridged)
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/ftpserverchannel.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,540 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import posixpath
+import stat
+import socket
+import time
+
+from zope.server.linereceiver.lineserverchannel import LineServerChannel
+from zope.server.ftp.ftpstatusmessages import status_msgs
+from zope.server.ftp.osemulators import ls_longify
+
+from zope.server.interfaces.ftp import IFTPCommandHandler
+from zope.server.ftp.passiveacceptor import PassiveAcceptor
+from zope.server.ftp.recvchannel import RecvChannel
+from zope.server.ftp.xmitchannel import XmitChannel, ApplicationXmitStream
+from zope.server.vfs.usernamepassword import UsernamePassword
+from zope.exceptions import Unauthorized
+
+
+class FTPServerChannel(LineServerChannel):
+ """The FTP Server Channel represents a connection to a particular
+ client. We can therefore store information here."""
+
+ __implements__ = LineServerChannel.__implements__, IFTPCommandHandler
+
+
+ # List of commands that are always available
+ special_commands = ('cmd_quit', 'cmd_type', 'cmd_noop', 'cmd_user',
+ 'cmd_pass')
+
+ # These are the commands that are accessing the filesystem.
[-=- -=- -=- 443 lines omitted -=- -=- -=-]
+ if fs.isdir(path):
+ file_list = fs.listdir(path, long)
+ else:
+ file_list = [ (posixpath.split(path)[1], fs.stat(path)) ]
+ # Make a pretty unix-like FTP output
+ if long:
+ file_list = map(ls_longify, file_list)
+ return ''.join(map(lambda line: line + '\r\n', file_list))
+
+
+
+ def connectDataChannel(self, cdc):
+ pa = self.passive_acceptor
+ if pa:
+ # PASV mode.
+ if pa.ready:
+ # a connection has already been made.
+ conn, addr = pa.ready
+ cdc.set_socket (conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ # else we're still waiting for a connect to the PASV port.
+ # FTP Explorer is known to do this.
+ else:
+ # not in PASV mode.
+ ip, port = self.client_addr
+ cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ if self.bind_local_minus_one:
+ cdc.bind(('', self.server.port - 1))
+ try:
+ cdc.connect((ip, port))
+ except socket.error, err:
+ cdc.close('NO_DATA_CONN')
+
+
+ def notifyClientDCClosing(self, *reply_args):
+ if self.client_dc is not None:
+ self.client_dc = None
+ if reply_args:
+ self.reply(*reply_args)
+
+
+ def close(self):
+ LineServerChannel.close(self)
+ # Make sure the client DC gets closed too.
+ cdc = self.client_dc
+ if cdc is not None:
+ self.client_dc = None
+ cdc.close()
=== Zope3/src/zope/server/ftp/ftpstatusmessages.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/ftpstatusmessages.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,71 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+
+status_msgs = {
+ 'OPEN_DATA_CONN' : '150 Opening %s mode data connection for file list',
+ 'OPEN_CONN' : '150 Opening %s connection for %s',
+ 'SUCCESS_200' : '200 %s command successful.',
+ 'TYPE_SET_OK' : '200 Type set to %s.',
+ 'STRU_OK' : '200 STRU F Ok.',
+ 'MODE_OK' : '200 MODE S Ok.',
+ 'FILE_DATE' : '213 %4d%02d%02d%02d%02d%02d',
+ 'FILE_SIZE' : '213 %d Bytes',
+ 'HELP_START' : '214-The following commands are recognized',
+ 'HELP_END' : '214 Help done.',
+ 'SERVER_TYPE' : '215 %s Type: %s',
+ 'SERVER_READY' : '220 %s FTP server (Zope Async/Thread V0.1) ready.',
+ 'GOODBYE' : '221 Goodbye.',
+ 'SUCCESS_226' : '226 %s command successful.',
+ 'TRANS_SUCCESS' : '226 Transfer successful.',
+ 'PASV_MODE_MSG' : '227 Entering Passive Mode (%s,%d,%d)',
+ 'LOGIN_SUCCESS' : '230 Login Successful.',
+ 'SUCCESS_250' : '250 %s command successful.',
+ 'SUCCESS_257' : '257 %s command successful.',
+ 'ALREADY_CURRENT' : '257 "%s" is the current directory.',
+ 'PASS_REQUIRED' : '331 Password required',
+ 'RESTART_TRANSFER' : '350 Restarting at %d. Send STORE or '
+ 'RETRIEVE to initiate transfer.',
+ 'READY_FOR_DEST' : '350 File exists, ready for destination.',
+ 'NO_DATA_CONN' : "425 Can't build data connection",
+ 'TRANSFER_ABORTED' : '426 Connection closed; transfer aborted.',
+ 'CMD_UNKNOWN' : "500 '%s': command not understood.",
+ 'INTERNAL_ERROR' : "500 Internal error: %s",
+ 'ERR_ARGS' : '500 Bad command arguments',
+ 'MODE_UNKOWN' : '502 Unimplemented MODE type',
+ 'WRONG_BYTE_SIZE' : '504 Byte size must be 8',
+ 'STRU_UNKNOWN' : '504 Unimplemented STRU type',
+ 'NOT_AUTH' : "530 You are not authorized to perform the "
+ "'%s' command",
+ 'LOGIN_REQUIRED' : '530 Please log in with USER and PASS',
+ 'LOGIN_MISMATCH' : '530 The username and password do not match.',
+ 'ERR_NO_LIST' : '550 Could not list directory or file: %s',
+ 'ERR_NO_DIR' : '550 "%s": No such directory.',
+ 'ERR_NO_FILE' : '550 "%s": No such file.',
+ 'ERR_NO_DIR_FILE' : '550 "%s": No such file or directory.',
+ 'ERR_IS_NOT_FILE' : '550 "%s": Is not a file',
+ 'ERR_CREATE_FILE' : '550 Error creating file.',
+ 'ERR_CREATE_DIR' : '550 Error creating directory: %s',
+ 'ERR_DELETE_FILE' : '550 Error deleting file: %s',
+ 'ERR_DELETE_DIR' : '550 Error removing directory: %s',
+ 'ERR_OPEN_READ' : '553 Could not open file for reading: %s',
+ 'ERR_OPEN_WRITE' : '553 Could not open file for writing: %s',
+ 'ERR_IO' : '553 I/O Error: %s',
+ 'ERR_RENAME' : '560 Could not rename "%s" to "%s": %s',
+ 'ERR_RNFR_SOURCE' : '560 No source filename specify. Call RNFR first.',
+ }
=== Zope3/src/zope/server/ftp/osemulators.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/osemulators.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,96 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""OS-Emulator Package
+
+Simulates OS-level directory listing output for *nix and MS-DOS (including
+Windows NT).
+
+$Id$
+"""
+
+import stat
+import datetime
+
+mode_table = {
+ '0':'---',
+ '1':'--x',
+ '2':'-w-',
+ '3':'-wx',
+ '4':'r--',
+ '5':'r-x',
+ '6':'rw-',
+ '7':'rwx'
+ }
+
+
+def ls_longify((filename, stat_info)):
+ """Formats a directory entry similarly to the 'ls' command.
+ """
+
+ # Note that we expect a little deviance from the result of os.stat():
+ # we expect the ST_UID and ST_GID fields to contain user IDs.
+ username = str(stat_info[stat.ST_UID])[:8]
+ grpname = str(stat_info[stat.ST_GID])[:8]
+
+ mode_octal = ('%o' % stat_info[stat.ST_MODE])[-3:]
+ mode = ''.join(map(mode_table.get, mode_octal))
+ if stat.S_ISDIR (stat_info[stat.ST_MODE]):
+ dirchar = 'd'
+ else:
+ dirchar = '-'
+ date = ls_date(datetime.datetime.now(), stat_info[stat.ST_MTIME])
+
+ return '%s%s %3d %-8s %-8s %8d %s %s' % (
+ dirchar,
+ mode,
+ stat_info[stat.ST_NLINK],
+ username,
+ grpname,
+ stat_info[stat.ST_SIZE],
+ date,
+ filename
+ )
+
+
+def ls_date(now, t):
+ """Emulate the 'ls' command's date field. It has two formats.
+ If the date is more than 180 days in the past or future, then
+ it's like this:
+ Oct 19 1995
+ otherwise, it looks like this:
+ Oct 19 17:33
+ """
+ if abs((now - t).days) > 180:
+ return t.strftime('%b %d, %Y')
+ else:
+ return t.strftime('%b %d %H:%M')
+
+
+def msdos_longify((file, stat_info)):
+ """This matches the output of NT's ftp server (when in MSDOS mode)
+ exactly.
+ """
+ if stat.S_ISDIR(stat_info[stat.ST_MODE]):
+ dir = '<DIR>'
+ else:
+ dir = ' '
+ date = msdos_date(stat_info[stat.ST_MTIME])
+ return '%s %s %8d %s' % (date, dir, stat_info[stat.ST_SIZE], file)
+
+
+def msdos_date(t):
+ """Emulate MS-DOS 'dir' command. Example:
+ 09-19-95 05:33PM
+ """
+ return t.strftime('%m-%d-%y %H:%M%p')
=== Zope3/src/zope/server/ftp/passiveacceptor.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/passiveacceptor.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import asyncore
+import socket
+
+
+class PassiveAcceptor(asyncore.dispatcher):
+ """This socket accepts a data connection, used when the server has
+ been placed in passive mode. Although the RFC implies that we
+ ought to be able to use the same acceptor over and over again,
+ this presents a problem: how do we shut it off, so that we are
+ accepting connections only when we expect them? [we can't]
+
+ wuftpd, and probably all the other servers, solve this by
+ allowing only one connection to hit this acceptor. They then
+ close it. Any subsequent data-connection command will then try
+ for the default port on the client side [which is of course
+ never there]. So the 'always-send-PORT/PASV' behavior seems
+ required.
+
+ Another note: wuftpd will also be listening on the channel as
+ soon as the PASV command is sent. It does not wait for a data
+ command first.
+
+ --- we need to queue up a particular behavior:
+ 1) xmit : queue up producer[s]
+ 2) recv : the file object
+
+ It would be nice if we could make both channels the same.
+ Hmmm.."""
+
+ __implements__ = asyncore.dispatcher.__implements__
+
+ ready = None
+
+ def __init__ (self, control_channel):
+ asyncore.dispatcher.__init__ (self)
+ self.control_channel = control_channel
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ # bind to an address on the interface that the
+ # control connection is coming from.
+ self.bind ( (self.control_channel.getsockname()[0], 0) )
+ self.addr = self.getsockname()
+ self.listen(1)
+
+
+ def log (self, *ignore):
+ pass
+
+
+ def handle_accept (self):
+ conn, addr = self.accept()
+ conn.setblocking(0)
+ dc = self.control_channel.client_dc
+ if dc is not None:
+ dc.set_socket(conn)
+ dc.addr = addr
+ dc.connected = 1
+ self.control_channel.passive_acceptor = None
+ else:
+ self.ready = conn, addr
+ self.close()
=== Zope3/src/zope/server/ftp/publisherfilesystemaccess.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/publisherfilesystemaccess.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Implementation of IFilesystemAccess intended only for testing.
+
+$Id$
+"""
+
+from cStringIO import StringIO
+from zope.exceptions import Unauthorized
+from zope.app.security.registries.principalregistry import principalRegistry
+
+from zope.server.vfs.publisherfilesystem import PublisherFileSystem
+from zope.server.interfaces.vfs import IFilesystemAccess
+from zope.server.interfaces.vfs import IUsernamePassword
+
+
+class PublisherFilesystemAccess:
+
+ __implements__ = IFilesystemAccess
+
+ def __init__(self, request_factory):
+ self.request_factory = request_factory
+
+
+ def authenticate(self, credentials):
+ assert IUsernamePassword.isImplementedBy(credentials)
+ env = {'credentials' : credentials}
+ request = self.request_factory(StringIO(''), StringIO(), env)
+ id = principalRegistry.authenticate(request)
+ if id is None:
+ raise Unauthorized
+
+
+ def open(self, credentials):
+ return PublisherFileSystem(credentials, self.request_factory)
=== Zope3/src/zope/server/ftp/publisherftpserver.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/publisherftpserver.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from zope.server.ftp.ftpserver import FTPServer
+
+from zope.server.ftp.publisherfilesystemaccess import PublisherFilesystemAccess
+
+class PublisherFTPServer(FTPServer):
+ """Generic FTP Server"""
+
+
+ def __init__(self, request_factory, name, ip, port, *args, **kw):
+ self.request_factory = request_factory
+ fs_access = PublisherFilesystemAccess(request_factory)
+ super(PublisherFTPServer, self).__init__(ip, port, fs_access,
+ *args, **kw)
=== Zope3/src/zope/server/ftp/publisherftpserverchannel.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/publisherftpserverchannel.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.server.ftp.ftpserverchannel import FTPServerChannel
+
+class PublisherFTPServerChannel(FTPServerChannel):
+ """The FTP Server Channel represents a connection to a particular
+ client. We can therefore store information here."""
+
+ __implements__ = FTPServerChannel.__implements__
+
+
+ def authenticate(self):
+ if self._getFilesystem()._authenticate():
+ return 1, 'User successfully authenticated.'
+ else:
+ return 0, 'User could not be authenticated.'
=== Zope3/src/zope/server/ftp/recvchannel.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/recvchannel.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,102 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from zope.server.serverchannelbase import ChannelBaseClass
+from zope.server.buffers import OverflowableBuffer
+from zope.server.interfaces import ITask
+
+
+class RecvChannel(ChannelBaseClass):
+ """ """
+
+ complete_transfer = 0
+ _fileno = None # provide a default for asyncore.dispatcher._fileno
+
+ def __init__ (self, control_channel, finish_args):
+ self.control_channel = control_channel
+ self.finish_args = finish_args
+ self.inbuf = OverflowableBuffer(control_channel.adj.inbuf_overflow)
+ ChannelBaseClass.__init__(self, None, None, control_channel.adj)
+ # Note that this channel starts out in async mode.
+
+ def writable (self):
+ return 0
+
+ def handle_connect (self):
+ pass
+
+ def received (self, data):
+ if data:
+ self.inbuf.append(data)
+
+ def handle_close (self):
+ """Client closed, indicating EOF."""
+ c = self.control_channel
+ task = FinishedRecvTask(c, self.inbuf, self.finish_args)
+ self.complete_transfer = 1
+ self.close()
+ c.start_task(task)
+
+ def close(self, *reply_args):
+ try:
+ c = self.control_channel
+ if c is not None:
+ self.control_channel = None
+ if not self.complete_transfer and not reply_args:
+ # Not all data transferred
+ reply_args = ('TRANSFER_ABORTED',)
+ c.notifyClientDCClosing(*reply_args)
+ finally:
+ if self.socket is not None:
+ # XXX asyncore.dispatcher.close() doesn't like socket == None
+ ChannelBaseClass.close(self)
+
+
+
+class FinishedRecvTask:
+
+ __implements__ = ITask
+
+ def __init__(self, control_channel, inbuf, finish_args):
+ self.control_channel = control_channel
+ self.inbuf = inbuf
+ self.finish_args = finish_args
+
+ def service(self):
+ """Called to execute the task.
+ """
+ close_on_finish = 0
+ c = self.control_channel
+ try:
+ try:
+ c.finishedRecv(self.inbuf, self.finish_args)
+ except socket.error:
+ close_on_finish = 1
+ if c.adj.log_socket_errors:
+ raise
+ finally:
+ c.end_task(close_on_finish)
+
+
+ def cancel(self):
+ 'See ITask'
+ self.control_channel.close_when_done()
+
+
+ def defer(self):
+ 'See ITask'
+ pass
=== Zope3/src/zope/server/ftp/testfilesystemaccess.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/testfilesystemaccess.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,44 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Implementation of IFilesystemAccess intended only for testing.
+
+$Id$
+"""
+
+from zope.server.interfaces.vfs import IFilesystemAccess
+from zope.server.interfaces.vfs import IUsernamePassword
+from zope.exceptions import Unauthorized
+
+
+class TestFilesystemAccess:
+
+ __implements__ = IFilesystemAccess
+
+ passwords = {'foo': 'bar'}
+
+ def __init__(self, fs):
+ self.fs = fs
+
+ def authenticate(self, credentials):
+ if not IUsernamePassword.isImplementedBy(credentials):
+ raise Unauthorized
+ name = credentials.getUserName()
+ if not (name in self.passwords):
+ raise Unauthorized
+ if credentials.getPassword() != self.passwords[name]:
+ raise Unauthorized
+
+ def open(self, credentials):
+ self.authenticate(credentials)
+ return self.fs
=== Zope3/src/zope/server/ftp/xmitchannel.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:54 2002
+++ Zope3/src/zope/server/ftp/xmitchannel.py Wed Dec 25 09:15:23 2002
@@ -0,0 +1,92 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.server.serverchannelbase import ChannelBaseClass
+
+
+class XmitChannel(ChannelBaseClass):
+
+ opened = 0
+ _fileno = None # provide a default for asyncore.dispatcher._fileno
+
+ def __init__ (self, control_channel, ok_reply_args):
+ self.control_channel = control_channel
+ self.ok_reply_args = ok_reply_args
+ self.set_sync()
+ ChannelBaseClass.__init__(self, None, None, control_channel.adj)
+
+ def _open(self):
+ """Signal the client to open the connection."""
+ self.opened = 1
+ self.control_channel.reply(*self.ok_reply_args)
+ self.control_channel.connectDataChannel(self)
+
+ def write(self, data):
+ if self.control_channel is None:
+ raise IOError, 'Client FTP connection closed'
+ if not self.opened:
+ self._open()
+ ChannelBaseClass.write(self, data)
+
+ def readable(self):
+ return not self.connected
+
+ def handle_read(self):
+ # This is only called when making the connection.
+ try:
+ self.recv(1)
+ except:
+ # The connection failed.
+ self.close('NO_DATA_CONN')
+
+ def handle_connect(self):
+ pass
+
+ def handle_comm_error(self):
+ self.close('TRANSFER_ABORTED')
+
+ def close(self, *reply_args):
+ try:
+ c = self.control_channel
+ if c is not None:
+ self.control_channel = None
+ if not reply_args:
+ if not len(self.outbuf):
+ # All data transferred
+ if not self.opened:
+ # Zero-length file
+ self._open()
+ reply_args = ('TRANS_SUCCESS',)
+ else:
+ # Not all data transferred
+ reply_args = ('TRANSFER_ABORTED',)
+ c.notifyClientDCClosing(*reply_args)
+ finally:
+ if self.socket is not None:
+ # XXX asyncore.dispatcher.close() doesn't like socket == None
+ ChannelBaseClass.close(self)
+
+
+class ApplicationXmitStream:
+ """Provide stream output, remapping close() to close_when_done().
+ """
+
+ def __init__(self, xmit_channel):
+ self.write = xmit_channel.write
+ self.flush = xmit_channel.flush
+ self.close = xmit_channel.close_when_done