[Zope-Checkins] CVS: Zope3/lib/python/Zope/Server/FTP - CommonFTPActivityLogger.py:1.2 FTPServer.py:1.2 FTPServerChannel.py:1.2 FTPStatusMessages.py:1.2 IFTPCommandHandler.py:1.2 OSEmulators.py:1.2 PassiveAcceptor.py:1.2 PublisherFTPServer.py:1.2 PublisherFTPServerChannel.py:1.2 PublisherFTPTask.py:1.2 PublisherFilesystemAccess.py:1.2 RecvChannel.py:1.2 TestFilesystemAccess.py:1.2 XmitChannel.py:1.2 __init__.py:1.2

Jim Fulton jim@zope.com
Mon, 10 Jun 2002 19:30:07 -0400


Update of /cvs-repository/Zope3/lib/python/Zope/Server/FTP
In directory cvs.zope.org:/tmp/cvs-serv20468/lib/python/Zope/Server/FTP

Added Files:
	CommonFTPActivityLogger.py FTPServer.py FTPServerChannel.py 
	FTPStatusMessages.py IFTPCommandHandler.py OSEmulators.py 
	PassiveAcceptor.py PublisherFTPServer.py 
	PublisherFTPServerChannel.py PublisherFTPTask.py 
	PublisherFilesystemAccess.py RecvChannel.py 
	TestFilesystemAccess.py XmitChannel.py __init__.py 
Log Message:
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.

=== Zope3/lib/python/Zope/Server/FTP/CommonFTPActivityLogger.py 1.1 => 1.2 ===
+#
+# 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.log('127.0.0.1', message)


=== Zope3/lib/python/Zope/Server/FTP/FTPServer.py 1.1 => 1.2 ===
+#
+# 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 FTPServerChannel import FTPServerChannel
+from Zope.Server.ServerBase import ServerBase
+from Zope.Server.VFS.IFilesystemAccess import IFilesystemAccess
+
+
+
+class FTPServer(ServerBase):
+    """Generic FTP Server"""
+
+    channel_class = FTPServerChannel
+    SERVER_IDENT = 'Zope.Server.FTPServer'
+
+
+    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/lib/python/Zope/Server/FTP/FTPServerChannel.py 1.1 => 1.2 === (440/540 lines abridged)
+#
+# 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 sys
+import socket
+import time
+
+from Zope.Server.LineReceiver.LineServerChannel import LineServerChannel
+from FTPStatusMessages import status_msgs
+from OSEmulators import ls_longify
+
+from IFTPCommandHandler import IFTPCommandHandler
+from PassiveAcceptor import PassiveAcceptor
+from RecvChannel import RecvChannel
+from 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.
+    # Since this could be also potentially a longer process, these commands
+    # are also the ones that are executed in a different thread.
+    thread_commands = ('cmd_appe', 'cmd_cdup', 'cmd_cwd', 'cmd_dele',

[-=- -=- -=- 440 lines omitted -=- -=- -=-]

+                pass
+        if len(path_args) < 1:
+            dir = '.'
+        else:
+            dir = path_args[0]
+
+        dir = self._generatePath(dir)
+        return self.listdir(dir, long)
+
+
+    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/lib/python/Zope/Server/FTP/FTPStatusMessages.py 1.1 => 1.2 ===
+#
+# 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: %s',
+    'ERR_NO_DIR'       : '550 "%s": No such directory.',
+    'ERR_NO_FILE'      : '550 "%s": No such file.',
+    '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/lib/python/Zope/Server/FTP/IFTPCommandHandler.py 1.1 => 1.2 ===
+
+from Interface import Interface
+
+class IFTPCommandHandler(Interface):
+    """This interface defines all the FTP commands that are supported by the
+       server.
+
+       Every command takes the command line as first arguments, since it is
+       responsible
+    """
+
+    def cmd_abor(args):
+        """Abort operation. No read access required.
+        """
+
+    def cmd_appe(args):
+        """Append to a file. Write access required.
+        """
+
+    def cmd_cdup(args):
+        """Change to parent of current working directory.
+        """
+
+    def cmd_cwd(args):
+        """Change working directory.
+        """
+
+    def cmd_dele(args):
+        """Delete a file. Write access required.
+        """
+
+    def cmd_help(args):
+        """Give help information. No read access required.
+        """
+
+    def cmd_list(args):
+        """Give list files in a directory.
+        """
+
+    def cmd_mdtm(args):
+        """Show last modification time of file.
+
+           Example output: 213 19960301204320
+
+           Geez, there seems to be a second syntax for this fiel, where one
+           can also set the modification time using:
+           MDTM datestring pathname
+
+        """
+
+    def cmd_mkd(args):
+        """Make a directory. Write access required.
+        """
+
+    def cmd_mode(args):
+        """Set file transfer mode.  No read access required. Obselete.
+        """
+
+    def cmd_nlst(args):
+        """Give name list of files in directory.
+        """
+
+    def cmd_noop(args):
+        """Do nothing. No read access required.
+        """
+
+    def cmd_pass(args):
+        """Specify password.
+        """
+
+    def cmd_pasv(args):
+        """Prepare for server-to-server transfer. No read access required.
+        """
+
+    def cmd_port(args):
+        """Specify data connection port. No read access required.
+        """
+
+    def cmd_pwd(args):
+        """Print the current working directory.
+        """
+
+    def cmd_quit(args):
+        """Terminate session. No read access required.
+        """
+
+    def cmd_rest(args):
+        """Restart incomplete transfer.
+        """
+
+    def cmd_retr(args):
+        """Retrieve a file.
+        """
+
+    def cmd_rmd(args):
+        """Remove a directory. Write access required.
+        """
+
+    def cmd_rnfr(args):
+        """Specify rename-from file name. Write access required.
+        """
+
+    def cmd_rnto(args):
+        """Specify rename-to file name. Write access required.
+        """
+
+    def cmd_size(args):
+        """Return size of file.
+        """
+
+    def cmd_stat(args):
+        """Return status of server. No read access required.
+        """
+
+    def cmd_stor(args):
+        """Store a file. Write access required.
+        """
+
+    def cmd_stru(args):
+        """Set file transfer structure. Obselete."""
+
+    def cmd_syst(args):
+        """Show operating system type of server system.
+
+           No read access required.
+
+           Replying to this command is of questionable utility,
+           because this server does not behave in a predictable way
+           w.r.t. the output of the LIST command.  We emulate Unix ls
+           output, but on win32 the pathname can contain drive
+           information at the front Currently, the combination of
+           ensuring that os.sep == '/' and removing the leading slash
+           when necessary seems to work.  [cd'ing to another drive
+           also works]
+
+           This is how wuftpd responds, and is probably the most
+           expected.  The main purpose of this reply is so that the
+           client knows to expect Unix ls-style LIST output.
+
+           one disadvantage to this is that some client programs
+           assume they can pass args to /bin/ls.  a few typical
+           responses:
+
+           215 UNIX Type: L8 (wuftpd)
+           215 Windows_NT version 3.51
+           215 VMS MultiNet V3.3
+           500 'SYST': command not understood. (SVR4)
+        """
+
+    def cmd_type(args):
+        """Specify data transfer type. No read access required.
+        """
+
+    def cmd_user(args):
+        """Specify user name. No read access required.
+        """
+
+
+
+# this is the command list from the wuftpd man page
+# '!' requires write access
+#
+not_implemented_commands = {
+        'acct':        'specify account (ignored)',
+        'allo':        'allocate storage (vacuously)',
+        'site':        'non-standard commands (see next section)',
+        'stou':        'store a file with a unique name',                            #!
+        'xcup':        'change to parent of current working directory (deprecated)',
+        'xcwd':        'change working directory (deprecated)',
+        'xmkd':        'make a directory (deprecated)',                            #!
+        'xpwd':        'print the current working directory (deprecated)',
+        'xrmd':        'remove a directory (deprecated)',                            #!
+}


=== Zope3/lib/python/Zope/Server/FTP/OSEmulators.py 1.1 => 1.2 ===
+#
+# 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 stat
+import time
+
+months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+          'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+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 (long(time.time()), 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
+    """
+    try:
+        info = time.localtime(t)
+    except:
+        info = time.localtime(0)
+
+    # 15,600,000 == 86,400 * 180
+    if abs((now - t) > 15600000):
+        return '%s %2d %5d' % (
+                months[info[1]-1],
+                info[2],
+                info[0]
+                )
+    else:
+        return '%s %2d %02d:%02d' % (
+                months[info[1]-1],
+                info[2],
+                info[3],
+                info[4]
+                )
+
+
+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):
+    try:
+        info = time.gmtime(t)
+    except:
+        info = time.gmtime(0)
+
+    # year, month, day, hour, minute, second, ...
+    if info[3] > 11:
+        merid = 'PM'
+        info[3] = info[3] - 12
+    else:
+        merid = 'AM'
+
+    return '%02d-%02d-%02d  %02d:%02d%s' % (
+            info[1], info[2], info[0]%100, info[3], info[4], merid )


=== Zope3/lib/python/Zope/Server/FTP/PassiveAcceptor.py 1.1 => 1.2 ===
+#
+# 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/lib/python/Zope/Server/FTP/PublisherFTPServer.py 1.1 => 1.2 ===
+#
+# 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 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/lib/python/Zope/Server/FTP/PublisherFTPServerChannel.py 1.1 => 1.2 ===
+#
+# 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 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/lib/python/Zope/Server/FTP/PublisherFTPTask.py 1.1 => 1.2 ===
+#
+# 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 FTPTask import FTPTask
+from Zope.Publisher.Publish import publish
+
+
+class PublisherFTPTask(FTPTask):
+    """ """
+
+    __implements__ = FTPTask.__implements__
+
+
+    def execute(self):
+        """ """
+        server = self.channel.server
+        env = self.create_environment()
+        instream = self.request_data.getBodyStream()
+
+        request = server.request_factory(instream, self, env)
+        publish(request)
+
+
+    def create_environment(self):
+        request_data = self.request_data
+        channel = self.channel
+        server = channel.server
+
+        # This should probably change to reflect calling the FileSystem
+        # methods
+        env = {'command': request_data.command
+               'args': request_data.args
+               }
+
+
+        return env


=== Zope3/lib/python/Zope/Server/FTP/PublisherFilesystemAccess.py 1.1 => 1.2 ===
+#
+# 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.PrincipalRegistry import principalRegistry
+
+from Zope.Server.VFS.PublisherFileSystem import PublisherFileSystem
+from Zope.Server.VFS.IFilesystemAccess import IFilesystemAccess
+from Zope.Server.VFS.IUsernamePassword 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/lib/python/Zope/Server/FTP/RecvChannel.py 1.1 => 1.2 ===
+#
+# 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.ITask 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
+
+
+    ############################################################
+    # Implementation methods for interface
+    # Zope.Server.ITask
+
+    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 Zope.Server.ITask.ITask'
+        self.control_channel.close_when_done()
+
+
+    def defer(self):
+        'See Zope.Server.ITask.ITask'
+        pass
+
+    #
+    ############################################################
+


=== Zope3/lib/python/Zope/Server/FTP/TestFilesystemAccess.py 1.1 => 1.2 ===
+#
+# 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.VFS.IFilesystemAccess import IFilesystemAccess
+from Zope.Server.VFS.IUsernamePassword 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/lib/python/Zope/Server/FTP/XmitChannel.py 1.1 => 1.2 ===
+#
+# 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
+


=== Zope3/lib/python/Zope/Server/FTP/__init__.py 1.1 => 1.2 ===
+#
+# 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$
+"""