[Zope3-checkins] SVN: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ upgraded the FTP shell to be compatible with the next version of the twisted

Michael Kerrin michael.kerrin at openapp.biz
Fri Jul 8 08:07:49 EDT 2005


Log message for revision 31034:
  upgraded the FTP shell to be compatible with the next version of the twisted
  ftp shell.
  

Changed:
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/ftp.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/server.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftpserver.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_ftpserver.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/utils.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_docs.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_publisher.py
  D   Zope3/branches/srichter-twisted-integration/src/zope/app/server/utils.py

-=-
Modified: 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-07-08 11:30:46 UTC (rev 31033)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py	2005-07-08 12:07:48 UTC (rev 31034)
@@ -14,10 +14,11 @@
 """FTP and SFTP server factories.
 """
 
-from zope.app.server.utils import FTPRequestFactory
 from zope.app.server.server import ServerType
 from zope.app.server.ftp.server import FTPFactory
 from zope.app.server.server import SSHServerType
+
+from utils import FTPRequestFactory
 from sftpserver import SFTPFactory
 
 def createFTPFactory(db):

Modified: 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-07-08 11:30:46 UTC (rev 31033)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/ftp.py	2005-07-08 12:07:48 UTC (rev 31034)
@@ -15,74 +15,35 @@
 """
 __docformat__="restructuredtext"
 
-## cStringIO is causing me problems with unicode charactors.
 from cStringIO import StringIO
-import posixpath
-from datetime import date, timedelta
+from types import StringTypes
 
 from zope.interface import implements
 
+from twisted.internet import threads, defer
 from twisted.protocols import ftp
 
-from zope.app.server.utils import PublisherFileSystem
+from utils import PublisherFileSystem
 
-def ls(ls_info):
-    """Formats a directory entry similarly to the 'ls' command.
-    """
+class ConsumerObject(object):
+    def __init__(self, fs, name):
+        self.fs = fs
+        self.name = name
+        self.total = 0
 
-    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,
-        }
+    def registerProducer(self, producer, streaming):
+        assert streaming
 
-    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
+    def unregisterProducer(self):
+        pass
 
-    info.update(ls_info)
+    def write(self, bytes):
+        ## XXX - this is going to mess up the transaction machinary since
+        ## for a big file this method could be called hundreds of times.
+        instream = StringIO(bytes)
+        self.fs.writefile(self.name, instream, start = self.total)
+        self.total += len(bytes)
 
-    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
@@ -92,218 +53,135 @@
     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)
+    def _path(self, path):
+        return '/' + '/'.join(path)
 
-        path = posixpath.normpath(path)
-        if path.startswith('..'):
-            path = '/'
+    def makeDirectory(self, path):
+        return threads.deferToThread(self.fs_access.mkdir, self._path(path))
 
-        return path, path
+    def removeDirectory(self, path):
+        return threads.deferToThread(self.fs_access.rmdir, self._path(path))
 
-    def pwd(self):
-        return self._dir
+    def removeFile(self, path):
+        return threads.deferToThread(self.fs_access.remove, self._path(path))
 
-    def cwd(self, path):
-        dummy, path = self.mapCPathToSPath(path)
+    def rename(self, fromPath, toPath):
+        return threads.deferToThread(self.fs_access.rename,
+                                     self._path(fromPath),
+                                     self._path(toPath))
 
-        if self.fs_access.type(path) is None:
-            raise ftp.FileNotFoundError(path)
+    def access(self, path):
+        def success(result):
+            return None
+        def failure(result):
+            ## XXX - return more appropriate results
+            raise ftp.PermissionDeniedError(path)
 
-        if self.fs_access.type(path) == 'd':
-            self._dir = path
-        else:
-            raise ftp.FileNotFoundError(path)
+        ## XXX - is ls the right method to use here - seems a bit slow.
+        d = threads.deferToThread(self.fs_access.ls, self._path(path))
+        d.addCallback(success)
+        d.addErrback(failure)
 
-    def cdup(self):
-        self.cwd('..')
+        return d
 
-    def size(self, path):
-        dummy, path = self.mapCPathToSPath(path)
+    def _gotlisting(self, result, keys = ()):
+        ent = []
+        for key in keys:
+            val = getattr(self, '_list_' + key)(result)
+            if isinstance(val, StringTypes):
+                ent.append(val.encode('utf-8'))
+            else:
+                ent.append(val)
+        return result['name'].encode('utf-8'), ent
 
-        fs = self.fs_access
-        if fs.type(path) != 'f':
-            raise ftp.FileNotFoundError(path)
-        return fs.size(path)
+    def _stat(self, path, keys):
+        if self.fs_access.type(path) == 'd':
+            raise ftp.WrongFileType()
+        result = self._gotlisting(self.fs_access.lsinfo(path), keys)
+        return result[1]
 
-    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 stat(self, path, keys=()):
+        return threads.deferToThread(self._stat, self._path(path), keys)
 
-    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 list(self, path, keys=()):
+        def gotresults(results, keys):
+            ret = []
+            for result in results:
+                ret.append(self._gotlisting(result, keys))
+            return ret
 
-    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)
+        d = threads.deferToThread(self.fs_access.ls, self._path(path))
+        d.addCallback(gotresults, keys)
 
-    def dele(self, path):
-        if not path:
-            raise ftp.CmdSyntaxError('Bad command arguments.')
-        path = self._generatePath(path)
+        return d
 
-        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 = '.'
+    def _list_size(self, value):
+        return value.get('size', 0)
+    def _list_hardlinks(self, value):
+        return value.get('nlinks', 1)
+    def _list_owner(self, value):
+        return value.get('owner_name', 'na')
+    def _list_group(self, value):
+        return value.get('group_name', 'na')
+    def _list_directory(self, value):
+        return value['type'] == 'd'
+    def _list_modified(self, value):
+        mtime = value.get('mtime', None)
+        if mtime:
+            return int(mtime.strftime('%s'))
+        return 0
+    def _list_permissions(self, value):
+        ret = 0
+        if value.get('other_executable', False):
+            ret |= 0001
+        if value.get('other_writable', False):
+            ret |= 0002
+        if value.get('other_readable', False):
+            ret |= 0004
+        if value.get('group_executable', True):
+            ret |= 0010
+        if value.get('group_writable', True):
+            ret |= 0020
+        if value.get('group_readable', True):
+            ret |= 0040
+        if value.get('owner_executable', True):
+            ret |= 0100
+        if value.get('owner_writable', True):
+            ret |= 0200
+        if value.get('owner_readable', True):
+            ret |= 0400
+        if value.get('type', 'f') == 'f':
+            ret |= 0100000
         else:
-            path = path_args[0]
+            ret |= 0040000
+        return ret
 
-        path = self._generatePath(path)
+    def send(self, path, consumer):
+        def finished(result, consumer):
+            consumer.transport.loseConnection()
+        def failed(result, path):
+            consumer.transport.loseConnection()
+            ## XXX - a more appropriate exception here.
+            raise ftp.PermissionDeniedError(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]]
+        p = self._path(path)
+        d = threads.deferToThread(self.fs_access.readfile, p, consumer)
+        d.addCallback(finished, consumer)
+        d.addErrback(failed, consumer, p)
+        return d
 
-        return '\r\n'.join(file_list) + '\r\n'
+    def receive(self, path):
+        def accessok(result, fs, path):
+            if not result:
+                raise ftp.PermissionDeniedError(path)
+            return ConsumerObject(fs, p)
+        def failure(result, path):
+            ## XXX - should be a better exception
+            raise ftp.PermissionDeniedError(path)
+        p = self._path(path)
+        d = threads.deferToThread(self.fs_access.writable, p)
+        d.addCallback(accessok, self.fs_access, p)
+        d.addErrback(failure, p)
 
-    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')
+        return d

Modified: 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-07-08 11:30:46 UTC (rev 31033)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/server.py	2005-07-08 12:07:48 UTC (rev 31034)
@@ -18,27 +18,22 @@
 from zope.interface import implements
 
 from twisted.cred import portal, credentials
-from twisted.protocols import ftp, policies
-from twisted.internet import reactor, defer
+from twisted.protocols import ftp
 
-from zope.app.server.server import ServerType
+from utils import ZopeSimpleAuthenticatation
+from ftp import ZopeFTPShell
 
-from zope.app.server.utils import PublisherFileSystem, \
-     ZopeSimpleAuthenticatation
-from zope.app.server.ftp.ftp import ZopeFTPShell
-
 class FTPRealm(object):
 
     implements(portal.IRealm)
 
-    def __init__(self, request_factory, logout = None):
+    def __init__(self, request_factory):
         self.request_factory = request_factory
-        self.logout = logout
 
     def requestAvatar(self, avatarId, mind, *interfaces):
         """
           >>> from ZODB.tests.util import DB
-          >>> from zope.app.server.utils import FTPRequestFactory
+          >>> from utils import FTPRequestFactory
           >>> creds = credentials.UsernamePassword('bob', '123')
           >>> db = DB()
           >>> request_factory = FTPRequestFactory(db)
@@ -57,7 +52,7 @@
         ZopeFTPShell should contain a PublisherFileSystem istance assigned to
         its fs_access attribute.
           
-          >>> from zope.app.server.utils import PublisherFileSystem
+          >>> from utils import PublisherFileSystem
           >>> print isinstance(result[1].fs_access, PublisherFileSystem)
           True
 
@@ -82,15 +77,13 @@
         if ftp.IFTPShell in interfaces:
             avatar = ZopeFTPShell(avatarId.username, avatarId.password,
                                   self.request_factory)
-            avatar.logout = self.logout
-            return ftp.IFTPShell, avatar, avatar.logout
+            return ftp.IFTPShell, avatar, lambda : None
         raise NotImplementedError, \
                   "Only IFTPShell interface is supported by this realm."
 
-class FTPFactory(policies.LimitTotalConnectionsFactory):
-    protocol = ftp.FTP
-    overflowProtocol = ftp.FTPOverflowProtocol
+class FTPFactory(ftp.FTPFactory):
     allowAnonymous = False
+
     timeOut = 600
 
     def __init__(self, request_factory):
@@ -98,7 +91,7 @@
         The portal performs a simple authentication
 
           >>> from ZODB.tests.util import DB
-          >>> from zope.app.server.utils import FTPRequestFactory
+          >>> from utils import FTPRequestFactory
           >>> db = DB()
           >>> request_factory = FTPRequestFactory(db)
           >>> ftpfactory = FTPFactory(request_factory)
@@ -122,20 +115,4 @@
         p = portal.Portal(r)
         p.registerChecker(ZopeSimpleAuthenticatation(),
                           credentials.IUsernamePassword)
-
-        self.portal = p
-        self.instances = []
-
-    def buildProtocol(self, addr):
-        p = policies.LimitTotalConnectionsFactory.buildProtocol(self, addr)
-        if p is not None:
-            p.wrappedProtocol.portal = self.portal
-            p.wrappedProtocol.timeOut = self.timeOut
-        self.instances.append(p.wrappedProtocol)
-        return p
-
-    def stopFactory(self):
-        # make sure ftp instance's timeouts are set to None
-        # to avoid reactor complaints
-        [p.setTimeout(None) for p in self.instances if p.timeOut is not None]
-        policies.LimitTotalConnectionsFactory.stopFactory(self)
+        ftp.FTPFactory.__init__(self, p)

Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftpserver.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftpserver.py	2005-07-08 11:30:46 UTC (rev 31033)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftpserver.py	2005-07-08 12:07:48 UTC (rev 31034)
@@ -15,8 +15,6 @@
 """
 from zope.interface import implements
 
-from zope.app.server.utils import ZopeSimpleAuthenticatation
-from zope.app.server.utils import PublisherFileSystem
 from zope.app.server.interfaces import IFileSystem
 
 from twisted.cred.portal import IRealm, Portal
@@ -42,6 +40,7 @@
 from twisted.conch.avatar import ConchUser
 from twisted.conch.interfaces import IConchUser
 
+from utils import PublisherFileSystem, ZopeSimpleAuthenticatation
 
 class ZopeAvatar(ConchUser):
     implements(IConchUser)
@@ -68,7 +67,7 @@
 
     def requestAvatar(self, avatarId, mind, *interfaces):
         """
-          >>> from zope.app.server.utils import FTPRequestFactory
+          >>> from utils import FTPRequestFactory
           >>> from ZODB.tests.util import DB
           >>> from twisted.cred import credentials
           >>> creds = credentials.UsernamePassword('bob', '123')
@@ -89,7 +88,7 @@
         ZopeAvatar should contain a PublisherFileSystem instance assigned to
         its fs_access attribute.
           
-          >>> from zope.app.server.utils import PublisherFileSystem
+          >>> from utils import PublisherFileSystem
           >>> print isinstance(result[1].fs_access, PublisherFileSystem)
           True
 
@@ -149,7 +148,7 @@
         The portal performs a simple authentication
 
           >>> from ZODB.tests.util import DB
-          >>> from zope.app.server.utils import FTPRequestFactory
+          >>> from utils import FTPRequestFactory
           >>> db = DB()
           >>> request_factory = FTPRequestFactory(db)
           >>> sftpfactory = SFTPFactory(request_factory, hostkey = None)

Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_ftpserver.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_ftpserver.py	2005-07-08 11:30:46 UTC (rev 31033)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_ftpserver.py	2005-07-08 12:07:48 UTC (rev 31034)
@@ -62,6 +62,11 @@
 
         del self.serverProtocol
 
+        ## for some reason the threadpool isn't cleaning up.
+        from twisted.internet import reactor
+        if getattr(reactor, 'threadpool', None):
+            reactor.threadpool.stop()
+
     def test_serverUp(self):
         # test if we can bring the server up and down.
         pass
@@ -77,13 +82,15 @@
     def test_MLD(self):
         self._authLogin()
         responseLines = wait(self.client.queueStringCommand('MKD /newdir'))
-        self.assertEqual(['257 "/newdir" created.'], responseLines)
+        self.assertEqual(['257 "/newdir" created'], responseLines)
 
 
 def test_suite():
     return TestSuite((
         makeSuite(TestServerSetup),
         doctest.DocTestSuite('zope.app.server.ftp.server'),
+        doctest.DocTestSuite('zope.app.server.ftp.utils'),
+        doctest.DocTestSuite('zope.app.server.ftp.sftpserver'),
         ))
 
 if __name__=='__main__':

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/utils.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/utils.py	2005-07-08 11:30:46 UTC (rev 31033)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/utils.py	2005-07-08 12:07:48 UTC (rev 31034)
@@ -0,0 +1,184 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Contains the implementation of the Publisher File System for use within
+the FTP and SFTP servers. It also contains an implementation of
+twisted.cred.checkers.ICredentialsChecker for authentiating users against
+the twisted.cred.portal.Portal.
+"""
+__docformat__="restructuredtext"
+import posixpath
+from cStringIO import StringIO
+
+from zope.interface import implements
+from zope.publisher.publish import publish
+
+from zope.publisher.ftp import FTPRequest
+from zope.app.publication.interfaces import IPublicationRequestFactory
+from zope.app.publication.ftp import FTPPublication
+
+from zope.app.server.interfaces import IFileSystem
+
+from twisted.cred import checkers, credentials
+from twisted.internet import defer
+
+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 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
+
+
+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

Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_docs.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_docs.py	2005-07-08 11:30:46 UTC (rev 31033)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_docs.py	2005-07-08 12:07:48 UTC (rev 31034)
@@ -54,7 +54,7 @@
         doctest.DocFileSuite('../log.txt',
                              globs={'pprint': doctestunit.pprint},
                              optionflags=doctest.NORMALIZE_WHITESPACE),
-        doctest.DocTestSuite('zope.app.server.utils')
+        doctest.DocTestSuite('zope.app.server.ftp.utils')
         ))
 
 

Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_publisher.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_publisher.py	2005-07-08 11:30:46 UTC (rev 31033)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_publisher.py	2005-07-08 12:07:48 UTC (rev 31034)
@@ -20,7 +20,7 @@
 from fstests import FileSystemTests
 from StringIO import StringIO
 from zope.publisher.publish import mapply
-from zope.app.server.utils import PublisherFileSystem
+from zope.app.server.ftp.utils import PublisherFileSystem
 
 class DemoFileSystem(demofs.DemoFileSystem):
 

Deleted: Zope3/branches/srichter-twisted-integration/src/zope/app/server/utils.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/utils.py	2005-07-08 11:30:46 UTC (rev 31033)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/utils.py	2005-07-08 12:07:48 UTC (rev 31034)
@@ -1,184 +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.
-#
-##############################################################################
-"""Contains the implementation of the Publisher File System for use within
-the FTP and SFTP servers. It also contains an implementation of
-twisted.cred.checkers.ICredentialsChecker for authentiating users against
-the twisted.cred.portal.Portal.
-"""
-__docformat__="restructuredtext"
-import posixpath
-from cStringIO import StringIO
-
-from zope.interface import implements
-from zope.publisher.publish import publish
-
-from zope.publisher.ftp import FTPRequest
-from zope.app.publication.interfaces import IPublicationRequestFactory
-from zope.app.publication.ftp import FTPPublication
-
-from interfaces import IFileSystem
-
-from twisted.cred import checkers, credentials
-from twisted.internet import defer
-
-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 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
-
-
-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



More information about the Zope3-Checkins mailing list