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

Shane Hathaway shane@cvs.zope.org
Fri, 12 Apr 2002 17:30:57 -0400


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

Added Files:
      Tag: Zope-3x-branch
	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 Zope3-Server-Branch.


=== Added File Zope3/lib/python/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.4.1 2002/04/12 21:30:55 shane 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.log('127.0.0.1', message)


=== Added File Zope3/lib/python/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.4.1 2002/04/12 21:30:55 shane Exp $
"""
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()


=== Added File Zope3/lib/python/Zope/Server/FTP/FTPServerChannel.py === (432/532 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.4.1 2002/04/12 21:30:55 shane Exp $
"""

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 unix_longify as 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.

[-=- -=- -=- 432 lines omitted -=- -=- -=-]


    def getDirectoryList(self, args, long=0):
        # we need to scan the command line for arguments to '/bin/ls'...
        path_args = []
        for arg in args:
            if arg[0] != '-':
                path_args.append (arg)
            else:
                # ignore arguments
                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)



=== Added File Zope3/lib/python/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.4.1 2002/04/12 21:30:55 shane 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: %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.',
    }



=== Added File Zope3/lib/python/Zope/Server/FTP/IFTPCommandHandler.py ===


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)',                            #!
}


=== Added File Zope3/lib/python/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.
#
##############################################################################
"""

$Id: OSEmulators.py,v 1.1.4.1 2002/04/12 21:30:55 shane Exp $
"""

import stat
import time
import pwd, grp

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 unix_longify((file, stat_info)):
        # for now, only pay attention to the lower bits

    try: username = pwd.getpwuid(int(stat_info[stat.ST_UID]))[0]
    except: username = stat_info[stat.ST_UID]

    try: grpname = grp.getgrgid(int(stat_info[stat.ST_GID]))[0]
    except: grpname = stat_info[stat.ST_GID]


    mode = ('%o' % stat_info[stat.ST_MODE])[-3:]
    mode = ''.join(map (lambda x: mode_table[x], mode))
    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,
            file
            )


def ls_date (now, t):
    """Emulate the unix 'ls' command's date field.  it has two formats
       - if the date is more than 180 days in the past, then it's like
       this: Oct 19 1995 otherwise, it looks like this: Oct 19 17:33
    """
    try:
        info = time.gmtime(t)
    except:
        info = time.gmtime(0)

    # 15,600,000 == 86,400 * 180
    if (now - t) > 15600000:
        return '%s %2d  %d' % (
                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 )


=== Added File Zope3/lib/python/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.4.1 2002/04/12 21:30:55 shane 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/lib/python/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.4.1 2002/04/12 21:30:55 shane Exp $
"""
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)


=== Added File Zope3/lib/python/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.4.1 2002/04/12 21:30:55 shane Exp $
"""

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








=== Added File Zope3/lib/python/Zope/Server/FTP/PublisherFTPTask.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: PublisherFTPTask.py,v 1.1.4.1 2002/04/12 21:30:55 shane Exp $
"""

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


=== Added File Zope3/lib/python/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.4.1 2002/04/12 21:30:55 shane Exp $
"""

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)




=== Added File Zope3/lib/python/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.4.1 2002/04/12 21:30:55 shane Exp $
"""
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

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



=== Added File Zope3/lib/python/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.4.1 2002/04/12 21:30:55 shane Exp $
"""

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 self.passwords.has_key(name):
            raise Unauthorized
        if credentials.getPassword() != self.passwords[name]:
            raise Unauthorized

    def open(self, credentials):
        self.authenticate(credentials)
        return self.fs




=== Added File Zope3/lib/python/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.4.1 2002/04/12 21:30:55 shane 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



=== Added File Zope3/lib/python/Zope/Server/FTP/__init__.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: __init__.py,v 1.1.4.1 2002/04/12 21:30:55 shane Exp $
"""