[Zope3-checkins] CVS: Zope3/src/zope/server/ftp - __init__.py:1.1.2.1 commonftpactivitylogger.py:1.1.2.1 ftpserver.py:1.1.2.1 ftpserverchannel.py:1.1.2.1 ftpstatusmessages.py:1.1.2.1 osemulators.py:1.1.2.1 passiveacceptor.py:1.1.2.1 publisherfilesystemaccess.py:1.1.2.1 publisherftpserver.py:1.1.2.1 publisherftpserverchannel.py:1.1.2.1 recvchannel.py:1.1.2.1 testfilesystemaccess.py:1.1.2.1 xmitchannel.py:1.1.2.1

Jim Fulton jim@zope.com
Mon, 23 Dec 2002 14:33:21 -0500


Update of /cvs-repository/Zope3/src/zope/server/ftp
In directory cvs.zope.org:/tmp/cvs-serv19908/zope/server/ftp

Added Files:
      Tag: NameGeddon-branch
	__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:
Initial renaming before debugging

=== Added File Zope3/src/zope/server/ftp/__init__.py ===
#
# This file is necessary to make this directory a package.


=== Added File Zope3/src/zope/server/ftp/commonftpactivitylogger.py ===
##############################################################################
#
# 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: commonftpactivitylogger.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""

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)


=== Added File Zope3/src/zope/server/ftp/ftpserver.py ===
##############################################################################
#
# 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: ftpserver.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""
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.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()


=== Added File Zope3/src/zope/server/ftp/ftpserverchannel.py === (446/546 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: ftpserverchannel.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""

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.
    # 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',

[-=- -=- -=- 446 lines omitted -=- -=- -=-]

            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()



=== Added File Zope3/src/zope/server/ftp/ftpstatusmessages.py ===
##############################################################################
#
# 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: ftpstatusmessages.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""


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.',
    }



=== Added File Zope3/src/zope/server/ftp/osemulators.py ===
##############################################################################
#
# 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: osemulators.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""

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')


=== Added File Zope3/src/zope/server/ftp/passiveacceptor.py ===
##############################################################################
#
# 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: passiveacceptor.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""

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()



=== Added File Zope3/src/zope/server/ftp/publisherfilesystemaccess.py ===
##############################################################################
#
# 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: publisherfilesystemaccess.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""

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)




=== Added File Zope3/src/zope/server/ftp/publisherftpserver.py ===
##############################################################################
#
# 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: publisherftpserver.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""
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)


=== Added File Zope3/src/zope/server/ftp/publisherftpserverchannel.py ===
##############################################################################
#
# 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: publisherftpserverchannel.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""

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.'








=== Added File Zope3/src/zope/server/ftp/recvchannel.py ===
##############################################################################
#
# 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: recvchannel.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""
from zope.server.serverchannelbase import ChannelBaseClass
from zope.server.buffers import OverflowableBuffer
from zope.server.interfaces.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


    ############################################################
    # 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

    #
    ############################################################



=== Added File Zope3/src/zope/server/ftp/testfilesystemaccess.py ===
##############################################################################
#
# 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: testfilesystemaccess.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""

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




=== Added File Zope3/src/zope/server/ftp/xmitchannel.py ===
##############################################################################
#
# 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: xmitchannel.py,v 1.1.2.1 2002/12/23 19:33:19 jim Exp $
"""

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