[Zope3-checkins] SVN: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp This is the initial integration between Twisted FTP server and Zope.

Michael Kerrin michael.kerrin at openapp.biz
Mon Apr 25 09:40:50 EDT 2005


Log message for revision 30159:
  This is the initial integration between Twisted FTP server and Zope.
  

Changed:
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/ftp.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/interfaces.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/publisher.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/server.py
  D   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp.py

-=-
Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py	2005-04-25 13:30:42 UTC (rev 30158)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py	2005-04-25 13:40:50 UTC (rev 30159)
@@ -0,0 +1,25 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+
+from zope.app.server.server import ServerType
+from zope.app.server.ftp.server import FTPRequestFactory, FTPFactory
+
+def createFTPFactory(db):
+    request_factory = FTPRequestFactory(db)
+
+    factory = FTPFactory(request_factory)
+
+    return factory
+
+server = ServerType(createFTPFactory, 8021)


Property changes on: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/ftp.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/ftp.py	2005-04-25 13:30:42 UTC (rev 30158)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/ftp.py	2005-04-25 13:40:50 UTC (rev 30159)
@@ -0,0 +1,308 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+
+"""This module defines all the FTP shell classes
+"""
+
+## cStringIO is causing me problems with unicode charactors.
+from cStringIO import StringIO
+import posixpath
+from datetime import date, timedelta
+
+from zope.interface import implements
+
+from twisted.protocols import ftp
+
+from zope.app.server.ftp.publisher import PublisherFileSystem
+
+def ls(ls_info):
+    """Formats a directory entry similarly to the 'ls' command.
+    """
+
+    info = {
+        'owner_name': 'na',
+        'owner_readable': True,
+        'owner_writable': True,
+        'group_name': "na",
+        'group_readable': True,
+        'group_writable': True,
+        'other_readable': False,
+        'other_writable': False,
+        'nlinks': 1,
+        'size': 0,
+        }
+
+    if ls_info['type'] == 'd':
+        info['owner_executable'] = True
+        info['group_executable'] = True
+        info['other_executable'] = True
+    else:
+        info['owner_executable'] = False
+        info['group_executable'] = False
+        info['other_executable'] = False
+
+    info.update(ls_info)
+
+    mtime = info.get('mtime')
+    if mtime is not None:
+        if date.today() - mtime.date() > timedelta(days=180):
+            mtime = mtime.strftime('%b %d  %Y')
+        else:
+            mtime = mtime.strftime('%b %d %H:%M')
+    else:
+        mtime = "Jan 02  0000"
+
+    return "%s%s%s%s%s%s%s%s%s%s %3d %-8s %-8s %8d %s %s" % (
+        info['type'] == 'd' and 'd' or '-',
+        info['owner_readable'] and 'r' or '-',
+        info['owner_writable'] and 'w' or '-',
+        info['owner_executable'] and 'x' or '-',
+        info['group_readable'] and 'r' or '-',
+        info['group_writable'] and 'w' or '-',
+        info['group_executable'] and 'x' or '-',
+        info['other_readable'] and 'r' or '-',
+        info['other_writable'] and 'w' or '-',
+        info['other_executable'] and 'x' or '-',
+        info['nlinks'],
+        info['owner_name'],
+        info['group_name'],
+        info['size'],
+        mtime,
+        info['name'],
+        )
+
+## this should correspond to zope.server.ftp.server.FTPServerChannel
+class ZopeFTPShell(object):
+    """An abstraction of the shell commands used by the FTP protocol
+    for a given user account
+    """
+    implements(ftp.IFTPShell)
+
+    def __init__(self, username, password, request_factory):
+        self.fs_access = PublisherFileSystem((username, password), request_factory)
+        self._dir = '/'
+
+    def mapCPathToSPath(self, path):
+        if not path or path[0] != '/':
+            path = posixpath.join(self._dir, path)
+
+        path = posixpath.normpath(path)
+        if path.startswith('..'):
+            path = '/'
+
+        return path, path
+
+    def pwd(self):
+        return self._dir
+
+    def cwd(self, path):
+        dummy, path = self.mapCPathToSPath(path)
+
+        if self.fs_access.type(path) is None:
+            raise ftp.FileNotFoundError(path)
+
+        if self.fs_access.type(path) == 'd':
+            self._dir = path
+        else:
+            raise ftp.FileNotFoundError(path)
+
+    def cdup(self):
+        self.cwd('..')
+
+    def size(self, path):
+        dummy, path = self.mapCPathToSPath(path)
+
+        fs = self.fs_access
+        if fs.type(path) != 'f':
+            raise ftp.FileNotFoundError(path)
+        return fs.size(path)
+
+    def _generatePath(self, args):
+        """Convert relative paths to absolute paths."""
+        # We use posixpath even on non-Posix platforms because we don't want
+        # slashes converted to backslashes.
+        path = posixpath.join(self._dir, args)
+        return posixpath.normpath(path)
+
+    def mkd(self, path):
+        if not path:
+            raise ftp.CmdSyntaxError('Bad command arguments.')
+        path = self._generatePath(path)
+        try:
+            self.fs_access.mkdir(path)
+        except OSError, err:
+            raise ftp.CmdActionNotTaken('creating directory %s' % path)
+
+    def rmd(self, path):
+        if not path:
+            raise ftp.CmdSyntaxError('Bad command arguments.')
+        path = self._generatePath(path)
+        try:
+            self.fs_access.rmdir(path)
+        except OSError, err:
+            raise ftp.CmdActionNotTaken('deleting directory %s' % path)
+
+    def dele(self, path):
+        if not path:
+            raise ftp.CmdSyntaxError('Bad command arguments.')
+        path = self._generatePath(path)
+
+        try:
+            self.fs_access.remove(path)
+        except OSError, err:
+            raise ftp.CmdOpenReadError(path)
+
+    def getList(self, args, long=0, directory=0):
+        # we need to scan the command line for arguments to '/bin/ls'...
+        ## fs = self._getFileSystem()
+        fs = self.fs_access
+        path_args = []
+        for arg in args:
+            if arg[0] != '-':
+                path_args.append (arg)
+            else:
+                # ignore arguments
+                pass
+        if len(path_args) < 1:
+            path = '.'
+        else:
+            path = path_args[0]
+
+        path = self._generatePath(path)
+
+        if fs.type(path) == 'd' and not directory:
+            if long:
+                file_list = map(ls, fs.ls(path))
+            else:
+                file_list = fs.names(path)
+        else:
+            if long:
+                file_list = [ls(fs.lsinfo(path))]
+            else:
+                file_list = [posixpath.split(path)[1]]
+
+        return '\r\n'.join(file_list) + '\r\n'
+
+    def _list(self, path, long = 1, directory = False, *args):
+        path = self._generatePath(path)
+
+        dummy, path = self.mapCPathToSPath(path)
+
+        if not self.fs_access.type(path):
+            raise ftp.FileNotFoundError(path)
+
+        s = self.getList(args, long, directory)
+
+        return StringIO(str(s)), len(s)
+
+    def list(self, path):
+        return self._list(path)
+
+    def nlst(self, path):
+        return self._list(path, long = 0)
+
+    def retr(self, path):
+        fs = self.fs_access
+        if not path:
+            raise ftp.CmdSyntaxError('Bad command arguments.')
+        path = self._generatePath(path)
+
+        if not (fs.type(path) == 'f'):
+            raise ftp.FileNotFoundError(path)
+
+##         start = 0
+##         if self.restart_position:
+##             start = self.restart_position
+##             self.restart_position = 0
+
+##         ok_reply = 'OPEN_CONN', (self.type_map[self.transfer_mode], path)
+##         cdc = RETRChannel(self, ok_reply)
+##         outstream = ApplicationOutputStream(cdc)
+
+        start = 0
+        outstream = StringIO()
+
+        try:
+            fs.readfile(path, outstream, start)
+        except OSError, err:
+            raise ftp.CmdOpenReadError(path)
+
+        l = len(outstream.getvalue())
+        outstream.seek(0)
+
+        return outstream, l
+
+##         try:
+##             fs.readfile(path, outstream, start)
+##             cdc.close_when_done()
+##         except OSError, err:
+##             self.reply('ERR_OPEN_READ', str(err))
+##             cdc.reported = True
+##             cdc.close_when_done()
+##         except IOError, err:
+##             self.reply('ERR_IO', str(err))
+##             cdc.reported = True
+##             cdc.close_when_done()
+
+
+    def stor(self, params):
+        """This command causes the server-DTP to accept the data
+        transferred via the data connection and to store the data as
+        a file at the server site.  If the file specified in the
+        pathname exists at the server site, then its contents shall
+        be replaced by the data being transferred.  A new file is
+        created at the server site if the file specified in the
+        pathname does not already exist.
+        """
+        if not params:
+            raise ftp.CmdSyntaxError('Bad command arguments.')
+        path = self._generatePath(params)
+
+##         start = 0
+##         if self.restart_position:
+##             self.start = self.restart_position
+##         mode = write_mode + self.type_mode_map[self.transfer_mode]
+
+        if not self.fs_access.writable(path):
+##             self.reply('ERR_OPEN_WRITE', "Can't write file")
+##             return
+            raise ftp.CmdOpenWriteError(path)
+
+##         cdc = STORChannel(self, (path, mode, start))
+##         self.syncConnectData(cdc)
+##         self.reply('OPEN_CONN', (self.type_map[self.transfer_mode], path))
+
+    def writefile(self, path, data):
+        """
+        this is not in IFTPShell but needed to upload the data into Zope.
+        """
+        path = self._generatePath(path)
+
+        try:
+            self.fs_access.writefile(path, data)
+        except OSError, err:
+            raise ftp.CmdFileActionError()
+
+    def mdtm(self, args):
+        fs = self.fs_access
+        # We simply do not understand this non-standard extension to MDTM
+        if len(args.split()) > 1:
+            raise ftp.CmdSyntaxError('Bad command arguments.')
+        path = self._generatePath(args)
+        
+        if fs.type(path) != 'f':
+            raise ftp.FileNotFoundError(path)
+        else:
+            mtime = fs.mtime(path)
+            return mtime.strftime('%Y%m%d%H%M%S')


Property changes on: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/ftp.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/interfaces.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/interfaces.py	2005-04-25 13:30:42 UTC (rev 30158)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/interfaces.py	2005-04-25 13:40:50 UTC (rev 30159)
@@ -0,0 +1,217 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+
+from zope.interface import Interface
+
+class IFileSystem(Interface):
+    """An abstract filesystem.
+
+       Opening files for reading, and listing directories, should
+       return a producer.
+
+       All paths are POSIX paths, even when run on Windows,
+       which mainly means that FS implementations always expect forward
+       slashes, and filenames are case-sensitive.
+
+       `IFileSystem`, in generel, could be created many times per
+       request. Thus it is not advisable to store state in them. However, if
+       you have a special kind of `IFileSystemAccess` object that somhow
+       manages an `IFileSystem` for each set of credentials, then it would be
+       possible to store some state on this obejct. 
+    """
+
+    def type(path):
+        """Return the file type at `path`.
+
+        The return valie is 'd', for a directory, 'f', for a file, and
+        None if there is no file at `path`.
+
+        This method doesn't raise exceptions.
+        """
+
+    def names(path, filter=None):
+        """Return a sequence of the names in a directory.
+
+        If `filter` is not None, include only those names for which
+        `filter` returns a true value.
+        """
+
+    def ls(path, filter=None):
+        """Return a sequence of information objects.
+
+        Returm item info objects (see the ls_info operation) for the files
+        in a directory.
+
+        If `filter` is not None, include only those names for which
+        `filter` returns a true value.
+        """
+
+    def readfile(path, outstream, start=0, end=None):
+        """Outputs the file at `path` to a stream.
+
+        Data are copied starting from `start`.  If `end` is not None,
+        data are copied up to `end`.
+
+        """
+
+    def lsinfo(path):
+        """Return information for a unix-style ls listing for `path`.
+
+        Information is returned as a dictionary containing the following keys:
+
+        type
+
+           The path type, either 'd' or 'f'.
+
+        owner_name
+
+           Defaults to "na".  Must not include spaces.
+
+        owner_readable
+
+           Defaults to True.
+
+        owner_writable
+
+           Defaults to True.
+
+        owner_executable
+
+           Defaults to True for directories and False otherwise.
+
+        group_name
+
+           Defaults to "na".  Must not include spaces.
+
+        group_readable
+
+           Defaults to True.
+
+        group_writable
+
+           Defaults to True.
+
+        group_executable
+
+           Defaults to True for directories and False otherwise.
+
+        other_readable
+
+           Defaults to False.
+
+        other_writable
+
+           Defaults to False.
+
+        other_executable
+
+           Defaults to True for directories and false otherwise.
+
+        mtime
+
+           Optional time, as a datetime.datetime object.
+
+        nlinks
+
+           The number of links. Defaults to 1.
+
+        size
+
+           The file size.  Defaults to 0.
+
+        name
+
+           The file name.
+        """
+
+    def mtime(path):
+        """Return the modification time for the file at `path`.
+
+        This method returns the modification time. It is assumed that the path
+        exists. You can use the `type(path)` method to determine whether
+        `path` points to a valid file.
+
+        If the modification time is unknown, then return `None`.
+        """
+
+    def size(path):
+        """Return the size of the file at path.
+
+        This method returns the modification time. It is assumed that the path
+        exists. You can use the `type(path)` method to determine whether
+        `path` points to a valid file.
+        """
+
+    def mkdir(path):
+        """Create a directory.
+
+        If it is not possible or allowed to create the directory, an `OSError`
+        should be raised describing the reason of failure. 
+        """
+
+    def remove(path):
+        """Remove a file.  Same as unlink.
+
+        If it is not possible or allowed to remove the file, an `OSError`
+        should be raised describing the reason of failure. 
+        """
+
+    def rmdir(path):
+        """Remove a directory.
+
+        If it is not possible or allowed to remove the directory, an `OSError`
+        should be raised describing the reason of failure. 
+        """
+
+    def rename(old, new):
+        """Rename a file or directory."""
+
+    def writefile(path, instream, start=None, end=None, append=False):
+        """Write data to a file.
+
+        Both `start` and `end` must be either None or a non-negative
+        integer.
+
+        If `append` is true, `start` and `end` are ignored.
+
+        If `start` or `end` is not None, they specify the part of the
+        file that is to be written.
+
+        If `end` is None, the file is truncated after the data are
+        written.  If `end` is not None, any parts of the file after
+        `end` are left unchanged.
+
+        Note that if `end` is not `None`, and there is not enough data
+        in the `instream` it will fill the file up to `end`, then the missing
+        data are undefined.
+
+        If both `start` is `None` and `end` is `None`, then the file contents
+        are overwritten.
+
+        If `start` is specified and the file doesn't exist or is shorter
+        than `start`, the data in the file before `start` file will be
+        undefined.
+
+        If you do not want to handle incorrect starting and ending indices,
+        you can also raise an `IOError`, which will be properly handled by the
+        server.
+        """
+
+    def writable(path):
+        """Return boolean indicating whether a file at path is writable.
+
+        Note that a true value should be returned if the file doesn't
+        exist but its directory is writable.
+
+        """


Property changes on: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/interfaces.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/publisher.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/publisher.py	2005-04-25 13:30:42 UTC (rev 30158)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/publisher.py	2005-04-25 13:40:50 UTC (rev 30159)
@@ -0,0 +1,127 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+
+import posixpath
+from cStringIO import StringIO
+
+from zope.interface import implements
+from zope.publisher.publish import publish
+
+from interfaces import IFileSystem
+
+class NoOutput(object):
+    """An output stream lookalike that warns you if you try to
+    dump anything into it."""
+
+    def write(self, data):
+        raise RuntimeError, "Not a writable stream"
+
+    def flush(self):
+        pass
+
+    close = flush
+
+## this is the old zope.server.ftp.publisher.PublisherFileSystem class
+class PublisherFileSystem(object):
+    """Generic Publisher FileSystem implementation."""
+
+    implements(IFileSystem)
+
+    def __init__ (self, credentials, request_factory):
+        self.credentials = credentials
+        self.request_factory = request_factory
+
+    def type(self, path):
+        if path == '/':
+            return 'd'
+
+        return self._execute(path, 'type')
+
+    def names(self, path, filter=None):
+        return self._execute(path, 'names', split=False, filter=filter)
+
+    def ls(self, path, filter=None):
+        return self._execute(path, 'ls', split=False, filter=filter)
+
+    def readfile(self, path, outstream, start=0, end=None):
+        return self._execute(path, 'readfile', 
+                             outstream=outstream, start=start, end=end)
+
+    def lsinfo(self, path):
+        return self._execute(path, 'lsinfo')
+
+    def mtime(self, path):
+        return self._execute(path, 'mtime')
+
+    def size(self, path):
+        return self._execute(path, 'size')
+
+    def mkdir(self, path):
+        return self._execute(path, 'mkdir')
+
+    def remove(self, path):
+        return self._execute(path, 'remove')
+
+    def rmdir(self, path):
+        return self._execute(path, 'rmdir')
+
+    def rename(self, old, new):
+        'See IWriteFileSystem'
+        old = self._translate(old)
+        new = self._translate(new)
+        path0, old = posixpath.split(old)
+        path1, new = posixpath.split(new)
+        assert path0 == path1
+        return self._execute(path0, 'rename', split=False, old=old, new=new)
+
+    def writefile(self, path, instream, start=None, end=None, append=False):
+        'See IWriteFileSystem'
+        return self._execute(
+            path, 'writefile',
+            instream=instream, start=start, end=end, append=append)
+
+    def writable(self, path):
+        'See IWriteFileSystem'
+        return self._execute(path, 'writable')
+
+    def _execute(self, path, command, split=True, **kw):
+        env = {}
+        env.update(kw)
+        env['command'] = command
+
+        path = self._translate(path)
+
+        if split:
+            env['path'], env['name'] = posixpath.split(path)
+        else:
+            env['path'] = path
+            
+        env['credentials'] = self.credentials
+        # NoOutput avoids creating a black hole.
+        request = self.request_factory(StringIO(''), NoOutput(), env)
+
+        # Note that publish() calls close() on request, which deletes the
+        # response from the request, so that we need to keep track of it.
+        response = request.response
+        publish(request)
+        return response.getResult()
+
+    def _translate (self, path):
+        # Normalize
+        path = posixpath.normpath(path)
+        if path.startswith('..'):
+            # Someone is trying to get lower than the permitted root.
+            # We just ignore it.
+            path = '/'
+        return path


Property changes on: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/publisher.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/server.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/server.py	2005-04-25 13:30:42 UTC (rev 30158)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/server.py	2005-04-25 13:40:50 UTC (rev 30159)
@@ -0,0 +1,92 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+
+from zope.interface import implements
+
+from twisted.cred import portal, checkers, credentials
+from twisted.protocols import ftp
+from twisted.internet import reactor, defer
+
+from zope.publisher.ftp import FTPRequest
+
+from zope.app.server.server import ServerType
+from zope.app.publication.ftp import FTPPublication
+from zope.app.publication.interfaces import IPublicationRequestFactory
+
+from ftp import ZopeFTPShell
+
+class ZopeSimpleAuthenticatation(object):
+    implements(checkers.ICredentialsChecker)
+
+    credentialInterfaces = credentials.IUsernamePassword
+
+    def requestAvatarId(self, credentials):
+        """
+        see zope.server.ftp.publisher.PublisherFileSystemAccess
+
+        We can't actually do any authentication initially, as the
+        user may not be defined at the root.
+        """
+        # -> the user = username, password so we can authenticate later on.
+        return defer.succeed(credentials)
+
+class FTPRealm(object):
+
+    def __init__(self, request_factory, logout = None):
+        self.request_factory = request_factory
+        self.logout = logout
+
+    def requestAvatar(self, avatarId, mind, *interfaces):
+        if ftp.IFTPShell in interfaces:
+            avatar = ZopeFTPShell(avatarId.username, avatarId.password, self.request_factory)
+            avatar.logout = self.logout
+            return ftp.IFTPShell, avatar, avatar.logout
+        raise NotImplementedError("Only IFTPShell interface is supported by this realm")
+
+class FTPFactory(ftp.FTPFactory):
+    allowAnonymous = False
+
+    def __init__(self, request_factory):
+        r = FTPRealm(request_factory)
+        p = portal.Portal(r)
+        p.registerChecker(ZopeSimpleAuthenticatation(), credentials.IUsernamePassword)
+
+        self.portal = p
+
+class FTPRequestFactory(object):
+    """FTP Request factory
+
+    FTP request factories for a given database create FTP requets with
+    publications on the given database:
+        
+      >>> from ZODB.tests.util import DB
+      >>> db = DB()
+      >>> factory = FTPRequestFactory(db)
+      >>> from cStringIO import StringIO
+      >>> request = factory(StringIO(''), StringIO(),
+      ...                   {'credentials': None, 'path': '/'})
+      >>> request.publication.db is db
+      True
+      >>> db.close()
+
+    """
+    implements(IPublicationRequestFactory)
+
+    def __init__(self, db):
+        self.publication = FTPPublication(db)
+
+    def __call__(self, input_stream, output_steam, env):
+        request = FTPRequest(input_stream, output_steam, env)
+        request.setPublication(self.publication)
+        return request


Property changes on: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/server.py
___________________________________________________________________
Name: svn:eol-style
   + native

Deleted: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp.py	2005-04-25 13:30:42 UTC (rev 30158)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp.py	2005-04-25 13:40:50 UTC (rev 30159)
@@ -1,255 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (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.
-#
-##############################################################################
-"""FTP server
-
-$Id$
-"""
-from zope.app.publication.ftp import FTPPublication
-from zope.app.publication.interfaces import IPublicationRequestFactory
-from zope.publisher.ftp import FTPRequest
-from zope.server.ftp.logger import CommonFTPActivityLogger
-from zope.server.ftp.publisher import PublisherFTPServer
-from zope.app.server.servertype import ServerType
-import zope.interface
-
-class FTPRequestFactory(object):
-    """FTP Request factory
-
-    FTP request factories for a given database create FTP requets with
-    publications on the given database:
-        
-      >>> from ZODB.tests.util import DB
-      >>> db = DB()
-      >>> factory = FTPRequestFactory(db)
-      >>> from cStringIO import StringIO
-      >>> request = factory(StringIO(''), StringIO(),
-      ...                   {'credentials': None, 'path': '/'})
-      >>> request.publication.db is db
-      True
-      >>> db.close()
-
-    """
-    zope.interface.implements(IPublicationRequestFactory)
-
-    def __init__(self, db):
-        self.publication = FTPPublication(db)
-
-    def __call__(self, input_stream, output_steam, env):
-        request = FTPRequest(input_stream, output_steam, env)
-        request.setPublication(self.publication)
-        return request
-
-server = ServerType(
-    PublisherFTPServer,
-    FTPRequestFactory,
-    CommonFTPActivityLogger,
-    8021, True)
-
-
-
-import posixpath
-
-from cStringIO import StringIO
-
-from zope.server.interfaces.ftp import IFileSystem
-from zope.server.interfaces.ftp import IFileSystemAccess
-
-from zope.server.ftp.server import FTPServer
-from zope.publisher.publish import publish
-
-from zope.interface import implements
-
-class FileSystem(object):
-    """Generic Publisher FileSystem implementation."""
-
-    implements(IFileSystem)
-
-    def __init__ (self, credentials, request_factory):
-        self.credentials = credentials
-        self.request_factory = request_factory
-
-    def type(self, path):
-        if path == '/':
-            return 'd'
-
-        return self._execute(path, 'type')
-
-    def names(self, path, filter=None):
-        return self._execute(path, 'names', split=False, filter=filter)
-
-    def ls(self, path, filter=None):
-        return self._execute(path, 'ls', split=False, filter=filter)
-
-    def readfile(self, path, outstream, start=0, end=None):
-        return self._execute(path, 'readfile', 
-                             outstream=outstream, start=start, end=end)
-
-    def lsinfo(self, path):
-        return self._execute(path, 'lsinfo')
-
-    def mtime(self, path):
-        return self._execute(path, 'mtime')
-
-    def size(self, path):
-        return self._execute(path, 'size')
-
-    def mkdir(self, path):
-        return self._execute(path, 'mkdir')
-
-    def remove(self, path):
-        return self._execute(path, 'remove')
-
-    def rmdir(self, path):
-        return self._execute(path, 'rmdir')
-
-    def rename(self, old, new):
-        'See IWriteFileSystem'
-        old = self._translate(old)
-        new = self._translate(new)
-        path0, old = posixpath.split(old)
-        path1, new = posixpath.split(new)
-        assert path0 == path1
-        return self._execute(path0, 'rename', split=False, old=old, new=new)
-
-    def writefile(self, path, instream, start=None, end=None, append=False):
-        'See IWriteFileSystem'
-        return self._execute(
-            path, 'writefile',
-            instream=instream, start=start, end=end, append=append)
-
-    def writable(self, path):
-        'See IWriteFileSystem'
-        return self._execute(path, 'writable')
-
-    def _execute(self, path, command, split=True, **kw):
-        env = {}
-        env.update(kw)
-        env['command'] = command
-
-        path = self._translate(path)
-
-        if split:
-            env['path'], env['name'] = posixpath.split(path)
-        else:
-            env['path'] = path
-            
-        env['credentials'] = self.credentials
-        # NoOutput avoids creating a black hole.
-        request = self.request_factory(StringIO(''), NoOutput(), env)
-
-        # Note that publish() calls close() on request, which deletes the
-        # response from the request, so that we need to keep track of it.
-        response = request.response
-        publish(request)
-        return response.getResult()
-
-    def _translate (self, path):
-        # Normalize
-        path = posixpath.normpath(path)
-        if path.startswith('..'):
-            # Someone is trying to get lower than the permitted root.
-            # We just ignore it.
-            path = '/'
-        return path
-
-
-class NoOutput(object):
-    """An output stream lookalike that warns you if you try to
-    dump anything into it."""
-
-    def write(self, data):
-        raise RuntimeError, "Not a writable stream"
-
-    def flush(self):
-        pass
-
-    close = flush
-
-
-from twisted.protocols import ftp
-from twisted.internet import threads
-
-class ZopeFTPShell(object):
-    """ """
-    implements(ftp.IFTPShell)
-
-    def __init__(self, db, username, password):
-        self._dir = '/'
-        self.publication = FTPPublication(db)
-        self.credentials = (username, password)
-
-    def mapCPathToSPath(self, path):
-        return path
-
-    def pwd(self):
-        return self._dir
-
-    def cwd(self, path):
-        self._dir = posixpath.join(self._dir, path)
-
-    def cdup(self):
-        self.cwd('..')
-
-    def size(self, path):
-        return threads.deferredToThread(self._execute, path, 'size')
-
-    def mkd(self, path):
-        return threads.deferredToThread(self._execute, path, 'mkdir')
-
-    def rmd(self, path):
-        return threads.deferredToThread(self._execute, path, 'rmdir')
-
-    def dele(self, path):
-        return threads.deferredToThread(self._execute, path, 'remove')
-
-    def list(self, path):
-        return threads.deferredToThread(self._execute, path, 'ls')
-
-    def nlst(self, path):
-        pass
-
-    def retr(self, path):
-        pass
-
-    def stor(self, params):
-        pass
-
-    def mdtm(self, path):
-        pass
-
-    def _execute(self, path, command, split=True, **kw):
-        '''This is running in a thread!'''
-        path = posixpath.join(self._dir, path)
-
-        env = {}
-        env.update(kw)
-        env['command'] = command
-
-        path = self._translate(path)
-
-        if split:
-            env['path'], env['name'] = posixpath.split(path)
-        else:
-            env['path'] = path
-            
-        env['credentials'] = self.credentials
-        # NoOutput avoids creating a black hole.
-        request = FTPRequest(StringIO(''), output_steam, env)
-        request.setPublication(self.publication)
-
-        # Note that publish() calls close() on request, which deletes the
-        # response from the request, so that we need to keep track of it.
-        response = request.response
-        publish(request)
-        return response.getResult()



More information about the Zope3-Checkins mailing list