[Zope3-checkins] SVN: Zope3/branches/srichter-twisted-integration/ Patch to provide SFTP support for Zope.

Michael Kerrin michael.kerrin at openapp.biz
Sun May 8 17:44:44 EDT 2005


Log message for revision 30301:
  Patch to provide SFTP support for Zope.
  

Changed:
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/configure.zcml
  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
  D   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/interfaces.py
  D   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/publisher.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/server.py
  D   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/demofs.py
  D   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/fstests.py
  D   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_demofs.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_ftpserver.py
  D   Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_publisher.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/interfaces.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/main.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/schema.xml
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/server.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/__init__.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/configure.zcml
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/server.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/sftp.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/tests/
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/tests/__init__.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/tests/tests_sftpserver.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/demofs.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/fstests.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_demofs.py
  U   Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_docs.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_publisher.py
  A   Zope3/branches/srichter-twisted-integration/src/zope/app/server/utils.py
  A   Zope3/branches/srichter-twisted-integration/ssh_host_rsa_key
  A   Zope3/branches/srichter-twisted-integration/ssh_host_rsa_key.pub
  U   Zope3/branches/srichter-twisted-integration/zope.conf.in

-=-
Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/configure.zcml
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/configure.zcml	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/configure.zcml	2005-05-08 21:44:39 UTC (rev 30301)
@@ -24,8 +24,10 @@
 
   <utility
       name="FTP"
-      component=".ftp.server"
+      component=".ftp.ftpserver"
       provides=".interfaces.IServerType"
       />
 
+  <include package=".sftp" />
+
 </configure>

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-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -14,8 +14,9 @@
 """FTP server factories.
 """
 
+from zope.app.server.utils import FTPRequestFactory
 from zope.app.server.server import ServerType
-from zope.app.server.ftp.server import FTPRequestFactory, FTPFactory
+from zope.app.server.ftp.server import FTPFactory
 
 def createFTPFactory(db):
     request_factory = FTPRequestFactory(db)
@@ -24,4 +25,4 @@
 
     return factory
 
-server = ServerType(createFTPFactory, 8021)
+ftpserver = ServerType(createFTPFactory, 8021)

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-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/ftp.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -24,7 +24,7 @@
 
 from twisted.protocols import ftp
 
-from zope.app.server.ftp.publisher import PublisherFileSystem
+from zope.app.server.utils import PublisherFileSystem
 
 def ls(ls_info):
     """Formats a directory entry similarly to the 'ls' command.

Deleted: 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-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/interfaces.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -1,219 +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.
-#
-##############################################################################
-"""Interfaces related to the FTP server and Publisher
-"""
-__docformat__="restructuredtext"
-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.
-
-        """

Deleted: 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-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/publisher.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -1,129 +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.
-#
-##############################################################################
-"""Publisher File System Implementation
-"""
-__docformat__="restructuredtext"
-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

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-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/server.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -17,47 +17,75 @@
 
 from zope.interface import implements
 
-from twisted.cred import portal, checkers, credentials
+from twisted.cred import portal, credentials
 from twisted.protocols import ftp, policies
 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
+from zope.app.server.utils import PublisherFileSystem, \
+     ZopeSimpleAuthenticatation
+from zope.app.server.ftp.ftp import ZopeFTPShell
 
-class ZopeSimpleAuthenticatation(object):
-    implements(checkers.ICredentialsChecker)
+class FTPRealm(object):
 
-    credentialInterfaces = credentials.IUsernamePassword
+    implements(portal.IRealm)
 
-    def requestAvatarId(self, credentials):
-        """
-        see zope.server.ftp.publisher.PublisherFileSystemAccess
+    def __init__(self, request_factory, logout = None):
+        self.request_factory = request_factory
+        self.logout = logout
 
-        We can't actually do any authentication initially, as the
-        user may not be defined at the root.
+    def requestAvatar(self, avatarId, mind, *interfaces):
         """
-        # -> the user = username, password so we can authenticate later on.
-        return defer.succeed(credentials)
+          >>> from ZODB.tests.util import DB
+          >>> from zope.app.server.utils import FTPRequestFactory
+          >>> creds = credentials.UsernamePassword('bob', '123')
+          >>> db = DB()
+          >>> request_factory = FTPRequestFactory(db)
+          >>> realm = FTPRealm(request_factory)
+          >>> print realm.request_factory is request_factory
+          True
 
-class FTPRealm(object):
+        Now test this method
 
-    def __init__(self, request_factory, logout = None):
-        self.request_factory = request_factory
-        self.logout = logout
+          >>> result = realm.requestAvatar(creds, None, ftp.IFTPShell)
+          >>> print result[0] is ftp.IFTPShell
+          True
+          >>> print isinstance(result[1], ZopeFTPShell)
+          True
 
-    def requestAvatar(self, avatarId, mind, *interfaces):
+        ZopeFTPShell should contain a PublisherFileSystem istance assigned to
+        its fs_access attribute.
+          
+          >>> from zope.app.server.utils import PublisherFileSystem
+          >>> print isinstance(result[1].fs_access, PublisherFileSystem)
+          True
+
+        Make sure the PublisherFileSystems credentials are correct.
+          
+          >>> print result[1].fs_access.credentials[0] == 'bob'
+          True
+          >>> print result[1].fs_access.credentials[1] == '123'
+          True
+
+        This method only supports the ftp.IFTPShell has the interface for
+        the avatar.
+
+          >>> from zope.interface import Interface
+          >>> realm.requestAvatar(creds, None, Interface)
+          Traceback (most recent call last):
+          ...
+          NotImplementedError: Only IFTPShell interface is supported by this realm.
+          >>> db.close()
+
+        """
         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"
+                  "Only IFTPShell interface is supported by this realm."
 
 class FTPFactory(policies.LimitTotalConnectionsFactory):
     protocol = ftp.FTP
@@ -66,6 +94,30 @@
     timeOut = 600
 
     def __init__(self, request_factory):
+        """
+        The portal performs a simple authentication
+
+          >>> from ZODB.tests.util import DB
+          >>> from zope.app.server.utils import FTPRequestFactory
+          >>> db = DB()
+          >>> request_factory = FTPRequestFactory(db)
+          >>> ftpfactory = FTPFactory(request_factory)
+          >>> print ftpfactory.portal.realm.request_factory is request_factory
+          True
+
+        So the portal initializes ok.
+
+          >>> portal = ftpfactory.portal
+          >>> creds = credentials.UsernamePassword('bob', '123')
+          >>> deferred = portal.login(creds, None, ftp.IFTPShell)
+          >>> result = deferred.result
+          >>> print type(result)
+          <type 'tuple'>
+          >>> db.close()
+
+        The result variable should be the return value of the 'requestAvatar'
+        method of the FTPRealm method. This method contains its own test.
+        """
         r = FTPRealm(request_factory)
         p = portal.Portal(r)
         p.registerChecker(ZopeSimpleAuthenticatation(),
@@ -87,30 +139,3 @@
         # to avoid reactor complaints
         [p.setTimeout(None) for p in self.instances if p.timeOut is not None]
         policies.LimitTotalConnectionsFactory.stopFactory(self)
-
-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

Deleted: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/demofs.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/demofs.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/demofs.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -1,310 +0,0 @@
-##############################################################################
-# Copyright (c) 2003 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.
-##############################################################################
-"""Demo file-system implementation, for testing
-
-$Id: demofs.py 27459 2004-09-07 01:45:52Z shane $
-"""
-import posixpath
-from zope.security.interfaces import Unauthorized
-from zope.app.server.ftp.interfaces import IFileSystem
-## from zope.server.interfaces.ftp import IFileSystemAccess
-from zope.interface import implements
-
-execute = 1
-read = 2
-write = 4
-
-class File(object):
-    type = 'f'
-    modified=None
-
-    def __init__(self):
-        self.access = {'anonymous': read}
-
-    def accessable(self, user, access=read):
-        return (user == 'root'
-                or (self.access.get(user, 0) & access)
-                or (self.access.get('anonymous', 0) & access)
-                )
-
-    def grant(self, user, access):
-        self.access[user] = self.access.get(user, 0) | access
-
-    def revoke(self, user, access):
-        self.access[user] = self.access.get(user, 0) ^ access
-
-class Directory(File):
-
-    type = 'd'
-
-    def __init__(self):
-        super(Directory, self).__init__()
-        self.files = {}
-
-    def get(self, name, default=None):
-        return self.files.get(name, default)
-
-    def __getitem__(self, name):
-        return self.files[name]
-
-    def __setitem__(self, name, v):
-        self.files[name] = v
-
-    def __delitem__(self, name):
-        del self.files[name]
-
-    def __contains__(self, name):
-        return name in self.files
-
-    def __iter__(self):
-        return iter(self.files)
-
-class DemoFileSystem(object):
-    __doc__ = IFileSystem.__doc__
-
-    implements(IFileSystem)
-
-    File = File
-    Directory = Directory
-
-    def __init__(self, files, user=''):
-        self.files = files
-        self.user = user
-
-    def get(self, path, default=None):
-
-        while path.startswith('/'):
-            path = path[1:]
-
-        d = self.files
-        if path:
-            for name in path.split('/'):
-                if d.type is not 'd':
-                    return default
-                if not d.accessable(self.user):
-                    raise Unauthorized
-                d = d.get(name)
-                if d is None:
-                    break
-
-        return d
-
-    def getany(self, path):
-        d = self.get(path)
-        if d is None:
-            raise OSError("No such file or directory:", path)
-        return d
-
-    def getdir(self, path):
-        d = self.getany(path)
-        if d.type != 'd':
-            raise OSError("Not a directory:", path)
-        return d
-
-    def getfile(self, path):
-        d = self.getany(path)
-        if d.type != 'f':
-            raise OSError("Not a file:", path)
-        return d
-
-    def getwdir(self, path):
-        d = self.getdir(path)
-        if not d.accessable(self.user, write):
-            raise OSError("Permission denied")
-        return d
-
-    def type(self, path):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        f = self.get(path)
-        return getattr(f, 'type', None)
-
-    def names(self, path, filter=None):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        f = list(self.getdir(path))
-        if filter is not None:
-            f = [name for name in f if filter(name)]
-
-        return f
-
-    def _lsinfo(self, name, file):
-        info = {
-            'type': file.type,
-            'name': name,
-            'group_read': file.accessable(self.user, read),
-            'group_write': file.accessable(self.user, write),
-            }
-        if file.type == 'f':
-            info['size'] = len(file.data)
-        if file.modified is not None:
-            info['mtime'] = file.modified
-
-        return info
-
-    def ls(self, path, filter=None):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        f = self.getdir(path)
-        if filter is None:
-            return [self._lsinfo(name, f.files[name])
-                    for name in f
-                    ]
-
-        return [self._lsinfo(name, f.files[name])
-                for name in f
-                if filter(name)]
-
-    def readfile(self, path, outstream, start=0, end=None):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        f = self.getfile(path)
-
-        data = f.data
-        if end is not None:
-            data = data[:end]
-        if start:
-            data = data[start:]
-
-        outstream.write(data)
-
-    def lsinfo(self, path):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        f = self.getany(path)
-        return self._lsinfo(posixpath.split(path)[1], f)
-
-    def mtime(self, path):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        f = self.getany(path)
-        return f.modified
-
-    def size(self, path):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        f = self.getany(path)
-        return len(getattr(f, 'data', ''))
-
-    def mkdir(self, path):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        path, name = posixpath.split(path)
-        d = self.getwdir(path)
-        if name in d.files:
-            raise OSError("Already exists:", name)
-        newdir = self.Directory()
-        newdir.grant(self.user, read | write)
-        d.files[name] = newdir
-
-    def remove(self, path):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        path, name = posixpath.split(path)
-        d = self.getwdir(path)
-        if name not in d.files:
-            raise OSError("Not exists:", name)
-        f = d.files[name]
-        if f.type == 'd':
-            raise OSError('Is a directory:', name)
-        del d.files[name]
-
-    def rmdir(self, path):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        path, name = posixpath.split(path)
-        d = self.getwdir(path)
-        if name not in d.files:
-            raise OSError("Not exists:", name)
-        f = d.files[name]
-        if f.type != 'd':
-            raise OSError('Is not a directory:', name)
-        del d.files[name]
-
-    def rename(self, old, new):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        oldpath, oldname = posixpath.split(old)
-        newpath, newname = posixpath.split(new)
-
-        olddir = self.getwdir(oldpath)
-        newdir = self.getwdir(newpath)
-
-        if oldname not in olddir.files:
-            raise OSError("Not exists:", oldname)
-        if newname in newdir.files:
-            raise OSError("Already exists:", newname)
-
-        newdir.files[newname] = olddir.files[oldname]
-        del olddir.files[oldname]
-
-    def writefile(self, path, instream, start=None, end=None, append=False):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        path, name = posixpath.split(path)
-        d = self.getdir(path)
-        f = d.files.get(name)
-        if f is None:
-            d = self.getwdir(path)
-            f = d.files[name] = self.File()
-            f.grant(self.user, read | write)
-        elif f.type != 'f':
-            raise OSError("Can't overwrite a directory")
-
-        if not f.accessable(self.user, write):
-            raise OSError("Permission denied")
-
-        if append:
-            f.data += instream.read()
-        else:
-
-            if start:
-                if start < 0:
-                    raise ValueError("Negative starting file position")
-                prefix = f.data[:start]
-                if len(prefix) < start:
-                    prefix += '\0' * (start - len(prefix))
-            else:
-                prefix = ''
-                start=0
-
-            if end:
-                if end < 0:
-                    raise ValueError("Negative ending file position")
-                l = end - start
-                newdata = instream.read(l)
-
-                f.data = prefix+newdata+f.data[start+len(newdata):]
-            else:
-                f.data = prefix + instream.read()
-
-    def writable(self, path):
-        "See zope.server.interfaces.ftp.IFileSystem"
-        path, name = posixpath.split(path)
-        try:
-            d = self.getdir(path)
-        except OSError:
-            return False
-        if name not in d:
-            return d.accessable(self.user, write)
-        f = d[name]
-        return f.type == 'f' and f.accessable(self.user, write)
-
-## class DemoFileSystemAccess(object):
-##     __doc__ = IFileSystemAccess.__doc__
-
-##     implements(IFileSystemAccess)
-
-##     def __init__(self, files, users):
-##         self.files = files
-##         self.users = users
-
-##     def authenticate(self, credentials):
-##         "See zope.server.interfaces.ftp.IFileSystemAccess"
-##         user, password = credentials
-##         if user != 'anonymous':
-##             if self.users.get(user) != password:
-##                 raise Unauthorized
-##         return user
-
-##     def open(self, credentials):
-##         "See zope.server.interfaces.ftp.IFileSystemAccess"
-##         user = self.authenticate(credentials)
-##         return DemoFileSystem(self.files, user)

Deleted: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/fstests.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/fstests.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/fstests.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -1,156 +0,0 @@
-##############################################################################
-#
-# 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.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.
-#
-##############################################################################
-"""Abstract file-system tests
-
-$Id: fstests.py 26559 2004-07-15 21:22:32Z srichter $
-"""
-from StringIO import StringIO
-from zope.interface.verify import verifyObject
-from zope.app.server.ftp.interfaces import IFileSystem
-
-class FileSystemTests(object):
-    """Tests of a readable filesystem
-    """
-
-    filesystem = None
-    dir_name  = '/dir'
-    file_name = '/dir/file.txt'
-    unwritable_filename = '/dir/protected.txt'
-    dir_contents = ['file.txt', 'protected.txt']
-    file_contents = 'Lengthen your stride'
-
-    def test_type(self):
-        self.assertEqual(self.filesystem.type(self.dir_name), 'd')
-        self.assertEqual(self.filesystem.type(self.file_name), 'f')
-
-
-    def test_names(self):
-        lst = self.filesystem.names(self.dir_name)
-        lst.sort()
-        self.assertEqual(lst, self.dir_contents)
-
-    def test_readfile(self):
-        s = StringIO()
-        self.filesystem.readfile(self.file_name, s)
-        self.assertEqual(s.getvalue(), self.file_contents)
-
-
-    def testReadPartOfFile(self):
-        s = StringIO()
-        self.filesystem.readfile(self.file_name, s, 2)
-        self.assertEqual(s.getvalue(), self.file_contents[2:])
-
-
-    def testReadPartOfFile2(self):
-        s = StringIO()
-        self.filesystem.readfile(self.file_name, s, 1, 5)
-        self.assertEqual(s.getvalue(), self.file_contents[1:5])
-
-    def test_IFileSystemInterface(self):
-        verifyObject(IFileSystem, self.filesystem)
-
-    def testRemove(self):
-        self.filesystem.remove(self.file_name)
-        self.failIf(self.filesystem.type(self.file_name))
-
-
-    def testMkdir(self):
-        path = self.dir_name + '/x'
-        self.filesystem.mkdir(path)
-        self.assertEqual(self.filesystem.type(path), 'd')
-
-    def testRmdir(self):
-        self.filesystem.remove(self.file_name)
-        self.filesystem.rmdir(self.dir_name)
-        self.failIf(self.filesystem.type(self.dir_name))
-
-
-    def testRename(self):
-        self.filesystem.rename(self.file_name, self.file_name + '.bak')
-        self.assertEqual(self.filesystem.type(self.file_name), None)
-        self.assertEqual(self.filesystem.type(self.file_name + '.bak'), 'f')
-
-
-    def testWriteFile(self):
-        s = StringIO()
-        self.filesystem.readfile(self.file_name, s)
-        self.assertEqual(s.getvalue(), self.file_contents)
-
-        data = 'Always ' + self.file_contents
-        s = StringIO(data)
-        self.filesystem.writefile(self.file_name, s)
-
-        s = StringIO()
-        self.filesystem.readfile(self.file_name, s)
-        self.assertEqual(s.getvalue(), data)
-
-
-    def testAppendToFile(self):
-        data = ' again'
-        s = StringIO(data)
-        self.filesystem.writefile(self.file_name, s, append=True)
-
-        s = StringIO()
-        self.filesystem.readfile(self.file_name, s)
-        self.assertEqual(s.getvalue(), self.file_contents + data)
-
-    def testWritePartOfFile(self):
-        data = '123'
-        s = StringIO(data)
-        self.filesystem.writefile(self.file_name, s, 3, 6)
-
-        expect = self.file_contents[:3] + data + self.file_contents[6:]
-
-        s = StringIO()
-        self.filesystem.readfile(self.file_name, s)
-        self.assertEqual(s.getvalue(), expect)
-
-    def testWritePartOfFile_and_truncate(self):
-        data = '123'
-        s = StringIO(data)
-        self.filesystem.writefile(self.file_name, s, 3)
-
-        expect = self.file_contents[:3] + data
-
-        s = StringIO()
-        self.filesystem.readfile(self.file_name, s)
-        self.assertEqual(s.getvalue(), expect)
-
-    def testWriteBeyondEndOfFile(self):
-        partlen = len(self.file_contents) - 6
-        data = 'daylight savings'
-        s = StringIO(data)
-        self.filesystem.writefile(self.file_name, s, partlen)
-
-        expect = self.file_contents[:partlen] + data
-
-        s = StringIO()
-        self.filesystem.readfile(self.file_name, s)
-        self.assertEqual(s.getvalue(), expect)
-
-
-    def testWriteNewFile(self):
-        s = StringIO(self.file_contents)
-        self.filesystem.writefile(self.file_name + '.new', s)
-
-        s = StringIO()
-        self.filesystem.readfile(self.file_name, s)
-        self.assertEqual(s.getvalue(), self.file_contents)
-
-
-    def test_writable(self):
-        self.failIf(self.filesystem.writable(self.dir_name))
-        self.failIf(self.filesystem.writable(self.unwritable_filename))
-        self.failUnless(self.filesystem.writable(self.file_name))
-        self.failUnless(self.filesystem.writable(self.file_name+'1'))

Deleted: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_demofs.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_demofs.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_demofs.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -1,40 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2003 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.
-#
-##############################################################################
-"""Test the Demo Filesystem implementation.
-
-$Id: test_demofs.py 26476 2004-07-13 16:59:11Z srichter $
-"""
-import demofs
-from unittest import TestCase, TestSuite, main, makeSuite
-from fstests import FileSystemTests
-from StringIO import StringIO
-
-class Test(FileSystemTests, TestCase):
-
-    def setUp(self):
-        root = demofs.Directory()
-        root.grant('bob', demofs.write)
-        fs = self.filesystem = demofs.DemoFileSystem(root, 'bob')
-        fs.mkdir(self.dir_name)
-        fs.writefile(self.file_name, StringIO(self.file_contents))
-        fs.writefile(self.unwritable_filename, StringIO("save this"))
-        fs.get(self.unwritable_filename).revoke('bob', demofs.write)
-
-def test_suite():
-    return TestSuite((
-        makeSuite(Test),
-        ))
-
-if __name__=='__main__':
-    main(defaultTest='test_suite')

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-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_ftpserver.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -26,8 +26,8 @@
 
 from zope.app.server.ftp.server import FTPFactory
 
-from test_publisher import RequestFactory
-import demofs
+from zope.app.server.tests.test_publisher import RequestFactory
+from zope.app.server.tests import demofs
 
 class TestServerSetup(TestCase):
 

Deleted: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_publisher.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_publisher.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_publisher.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -1,123 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2003 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.
-#
-##############################################################################
-"""Test the FTP publisher.
-
-$Id: test_publisher.py 26559 2004-07-15 21:22:32Z srichter $
-"""
-import demofs
-from unittest import TestCase, TestSuite, main, makeSuite
-from fstests import FileSystemTests
-from StringIO import StringIO
-from zope.publisher.publish import mapply
-from zope.app.server.ftp.publisher import PublisherFileSystem
-
-class DemoFileSystem(demofs.DemoFileSystem):
-
-    def rename(self, path, old, new):
-        return demofs.DemoFileSystem.rename(
-            self, "%s/%s" % (path, old), "%s/%s" % (path, new)) 
-
-class Publication(object):
-
-    def __init__(self, root):
-        self.root = root
-
-    def beforeTraversal(self, request):
-        pass
-    
-    def getApplication(self, request):
-        return self.root
-
-    def afterTraversal(self, request, ob):
-        pass
-
-    def callObject(self, request, ob):
-        command = getattr(ob, request.env['command'])
-        if 'name' in request.env:
-            request.env['path'] += "/" + request.env['name']
-        return mapply(command, request = request.env)
-
-    def afterCall(self, request, ob):
-        pass
-
-    def endRequest(self, request, ob):
-        pass
-
-    def handleException(self, object, request, info, retry_allowed=True):
-        request.response._exc = info[:2]
-        
-
-class Request(object):
-
-    def __init__(self, input, output, env):
-        self.env = env
-        self.response = Response()
-        self.user = env['credentials']
-        del env['credentials']
-
-    def processInputs(self):
-        pass
-
-    def traverse(self, root):
-        root.user = self.user
-        return root
-
-    def close(self):
-        pass
-
-class Response(object):
-
-    _exc = _body = None
-
-    def setBody(self, result):
-        self._body = result
-
-    def outputBody(self):
-        pass
-
-    def getResult(self):
-        if self._exc:
-            raise self._exc[0], self._exc[1]
-        return self._body
-
-class RequestFactory(object):
-
-    def __init__(self, root):
-        self.pub = Publication(root)
-
-    def __call__(self, input, output, env):
-        r = Request(input, output, env)
-        r.publication = self.pub
-        return r
-
-class TestPublisherFileSystem(FileSystemTests, TestCase):
-
-    def setUp(self):
-        root = demofs.Directory()
-        root.grant('bob', demofs.write)
-        fs = DemoFileSystem(root, 'bob')
-        fs.mkdir(self.dir_name)
-        fs.writefile(self.file_name, StringIO(self.file_contents))
-        fs.writefile(self.unwritable_filename, StringIO("save this"))
-        fs.get(self.unwritable_filename).revoke('bob', demofs.write)
-
-        self.filesystem = PublisherFileSystem('bob', RequestFactory(fs))
-
-def test_suite():
-    return TestSuite((
-        makeSuite(TestPublisherFileSystem),
-        ))
-
-if __name__=='__main__':
-    main(defaultTest='test_suite')

Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/interfaces.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/interfaces.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/interfaces.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -43,3 +43,215 @@
         This differs only in respect to that it needs the private key path,
         certificate key path and TLS flag to instantiate the server.
         """
+
+class ISSHServerType(IServerType):
+    """SSH Server Type utility"""
+
+    def create(name, db, hostkey, ip = None, port = None, backlog = 50):
+        """Create a SSH server instance.
+
+        This differs only in respect to that it needs the host key path.
+        """
+
+## this is going here since I need it for FTP and SFTP.
+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.
+
+        """

Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/main.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/main.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/main.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -121,7 +121,7 @@
 
     rootService = ZopeService()
 
-    for server in options.servers + options.sslservers:
+    for server in options.servers + options.sslservers + options.sshservers:
         service = server.create(db)
         service.setServiceParent(rootService)
 

Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/schema.xml
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/schema.xml	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/schema.xml	2005-05-08 21:44:39 UTC (rev 30301)
@@ -27,6 +27,11 @@
     <key name="tls" datatype="boolean" default="false" />
   </sectiontype>
 
+  <sectiontype name="sshserver"
+               extends="server"
+               datatype="zope.app.server.server.SSHServerFactory">
+    <key name="hostkey" datatype="existing-file" required="yes" />
+  </sectiontype>
 
   <section type="ZODB.database" name="*" required="yes"
            attribute="database">
@@ -50,6 +55,7 @@
 
   <multisection type="server" name="*" attribute="servers" />
   <multisection type="sslserver" name="*" attribute="sslservers" />
+  <multisection type="sshserver" name="*" attribute="sshservers" />
 
   <key name="site-definition" default="site.zcml">
     <description>

Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/server.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/server.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/server.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -24,7 +24,8 @@
 
 from zope.interface import implements
 from zope.app import zapi
-from zope.app.server.interfaces import IServerType, ISSLServerType
+from zope.app.server.interfaces import IServerType, ISSLServerType, \
+     ISSHServerType
 
 class SSLNotSupported(Exception):
     ''' '''
@@ -113,6 +114,23 @@
                              interface=ip, backlog=backlog)
 
 
+class SSHServerType(ServerType):
+
+    implements(ISSHServerType)
+
+    def create(self, name, db, hostkey, ip = None, port = None, backlog = 50):
+        """ """
+        if port is None:
+            port = self._defaultPort
+
+        if ip is None:
+            ip = self._defaultIP
+
+        # Given a database, create a twisted.internet.interfaces.IServerFactory
+        factory = self._factory(db, hostkey)
+        return ZopeTCPServer(name, port, factory, interface = ip,
+                             backlog = backlog)
+
 class ServerFactory(object):
     """Factory for server objects.
 
@@ -168,3 +186,27 @@
             port=self.address[1],
             backlog=self.backlog,
             )
+
+
+class SSHServerFactory(object):
+    """Factory for SSH server objects. """
+
+    def __init__(self, section):
+        """Initialize the factory based on a <server> section."""
+        self.type = section.type
+        self.address = section.address
+        self.backlog = section.backlog
+        self.hostkey = section.hostkey
+
+    def create(self, database):
+        """Return a server based on the server types defined via ZCML."""
+
+        servertype = zapi.getUtility(IServerType, self.type)
+
+        return servertype.create(
+            self.type,
+            database,
+            hostkey = self.hostkey,
+            ip=self.address[0],
+            port=self.address[1],
+            backlog=self.backlog)

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/__init__.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/__init__.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/__init__.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1,37 @@
+##############################################################################
+#
+# Copyright (c) 2001,2002,2003 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.
+#
+##############################################################################
+""" SFTP Server Factories.
+"""
+from zope.app.server.utils import FTPRequestFactory
+HAS_CRYPTO = True
+try:
+    from zope.app.server.server import SSHServerType
+    from server import SFTPFactory
+except ImportError, e:
+    HAS_CRYPTO = False
+
+def createSFTPFactory(db, hostkey):
+    """
+    note that all SSH factories must contain the extra hostkey arguement.
+    """
+    request_factory = FTPRequestFactory(db)
+
+    factory = SFTPFactory(request_factory, hostkey = hostkey)
+
+    return factory
+
+if HAS_CRYPTO is True:
+    sftpserver = SSHServerType(createSFTPFactory, 8115)
+else:
+    sftpserver = False


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

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/configure.zcml
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/configure.zcml	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/configure.zcml	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1,15 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <utility
+     name="SFTP"
+     component=".sftpserver"
+     provides="..interfaces.IServerType"
+     />
+
+  <adapter
+     factory=".sftp.SFTPServerForZope"
+     for=".server.ZopeAvatar"
+     provides="twisted.conch.ssh.filetransfer.ISFTPServer"
+     />
+
+</configure>


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

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/server.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/server.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/server.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1,177 @@
+##############################################################################
+#
+# Copyright (c) 2001,2002,2003 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.
+#
+##############################################################################
+""" Twisted specific integration classes for SFTP.
+"""
+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
+from twisted.cred.credentials import IUsernamePassword
+
+from twisted.conch.ssh.filetransfer import FileTransferServer
+from twisted.conch.ssh.connection import SSHConnection
+from twisted.conch.ssh.session import SSHSession
+from twisted.conch.ssh.common import getNS
+from twisted.conch.ssh.forwarding import openConnectForwardingClient
+try:
+    from twisted.conch.ssh.factory import SSHFactory
+    from twisted.conch.ssh.userauth import SSHUserAuthServer
+    from twisted.conch.ssh.keys import getPublicKeyString, \
+         getPrivateKeyObject, objectType
+except ImportError, e:
+    SSHFactory = object
+    SSHUserAuthServer = None
+    getPublicKeyString = getPrivateKeyObject = objectType = None
+from twisted.conch.avatar import ConchUser
+from twisted.conch.interfaces import IConchUser
+
+
+class ZopeAvatar(ConchUser):
+    implements(IConchUser)
+    
+    def __init__(self, fs_access):
+        ConchUser.__init__(self)
+
+        assert IFileSystem.providedBy(fs_access), "Invalid File Publisher"
+        self.fs_access = fs_access
+
+        self.channelLookup.update(
+            {'session': SSHSession,
+             'direct-tcpip': openConnectForwardingClient})
+
+        self.subsystemLookup.update(
+            {'sftp': FileTransferServer})
+
+
+class SFTPRealm(object):
+    implements(IRealm)
+
+    def __init__(self, request_factory):
+        self.request_factory = request_factory
+
+    def requestAvatar(self, avatarId, mind, *interfaces):
+        """
+          >>> from zope.app.server.utils import FTPRequestFactory
+          >>> from ZODB.tests.util import DB
+          >>> from twisted.cred import credentials
+          >>> creds = credentials.UsernamePassword('bob', '123')
+          >>> db = DB()
+          >>> request_factory = FTPRequestFactory(db)
+          >>> realm = SFTPRealm(request_factory)
+          >>> print realm.request_factory is request_factory
+          True
+
+        Now test this method
+
+          >>> result = realm.requestAvatar(creds, None, IConchUser)
+          >>> print result[0] is IConchUser
+          True
+          >>> print isinstance(result[1], ZopeAvatar)
+          True
+
+        ZopeAvatar should contain a PublisherFileSystem instance assigned to
+        its fs_access attribute.
+          
+          >>> from zope.app.server.utils import PublisherFileSystem
+          >>> print isinstance(result[1].fs_access, PublisherFileSystem)
+          True
+
+        Make sure the PublisherFileSystems credentials are correct.
+          
+          >>> print result[1].fs_access.credentials[0] == 'bob'
+          True
+          >>> print result[1].fs_access.credentials[1] == '123'
+          True
+
+        This method only supports the IConchUser has the interface for
+        the avatar.
+
+          >>> from zope.interface import Interface
+          >>> realm.requestAvatar(creds, None, Interface)
+          Traceback (most recent call last):
+          ...
+          NotImplementedError: Only IConchUser interface is supported by this realm.
+          >>> db.close()
+
+        """
+        if IConchUser in interfaces:
+            fs_access = PublisherFileSystem(
+                (avatarId.username, avatarId.password),
+                self.request_factory)
+            avatar = ZopeAvatar(fs_access)
+            return IConchUser, avatar, lambda : None
+        raise NotImplementedError, \
+              "Only IConchUser interface is supported by this realm."
+
+
+class SFTPFactory(SSHFactory):
+    services = {
+        'ssh-userauth': SSHUserAuthServer,
+        'ssh-connection': SSHConnection
+        }
+
+    def getPublicKeys(self):
+        ks = {}
+        k = getPublicKeyString(self.hostkey + '.pub')
+        t = getNS(k)[0]
+        ks[t] = k
+        return ks
+
+    def getPrivateKeys(self):
+        ks = {}
+        k = getPrivateKeyObject(self.hostkey)
+        t = objectType(k)
+        ks[t] = k
+        return ks
+
+    def getPrimes(self):
+        return None
+
+    def __init__(self, request_factory, hostkey):
+        """
+        The portal performs a simple authentication
+
+          >>> from ZODB.tests.util import DB
+          >>> from zope.app.server.utils import FTPRequestFactory
+          >>> db = DB()
+          >>> request_factory = FTPRequestFactory(db)
+          >>> sftpfactory = SFTPFactory(request_factory, hostkey = None)
+          >>> print sftpfactory.portal.realm.request_factory is request_factory
+          True
+
+        So the portal initializes ok.
+          
+          >>> from twisted.cred import credentials
+          >>> portal = sftpfactory.portal
+          >>> creds = credentials.UsernamePassword('bob', '123')
+          >>> deferred = portal.login(creds, None, IConchUser)
+          >>> result = deferred.result
+          >>> print type(result)
+          <type 'tuple'>
+          >>> db.close()
+
+        The result variable should be the return value of the 'requestAvatar' method
+        of the SFTPRealm method. This method contains its own test.
+        """
+        self.hostkey = hostkey
+        
+        r = SFTPRealm(request_factory)
+        p = Portal(r)
+
+        p.registerChecker(ZopeSimpleAuthenticatation(),
+                          IUsernamePassword)
+        self.portal = p


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

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/sftp.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/sftp.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/sftp.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1,245 @@
+##############################################################################
+#
+# Copyright (c) 2001,2002,2003 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.
+#
+##############################################################################
+""" Implementation of the ISFTPServer ssh file transfer protocol for Zope.
+"""
+
+import posixpath, os, stat, array, datetime
+from cStringIO import StringIO
+
+from zope.interface import implements
+
+from twisted.conch.interfaces import ISFTPServer, ISFTPFile
+from twisted.conch.ssh.filetransfer import FXF_APPEND, FXF_READ
+
+class SFTPServerForZope(object):
+    implements(ISFTPServer)
+
+    def __init__(self, avatar):
+        self.avatar = avatar
+
+    def gotVersion(self, otherVersion, extData):
+        return {}
+
+    def openFile(self, filename, flags, attrs):
+        fp = ZopeSFTPFile(self, self._generatePath(filename), flags, attrs)
+
+        return fp
+
+    def removeFile(self, filename):
+        self.avatar.fs_access.remove(self._generatePath(filename))
+
+    def renameFile(self, oldpath, newpath):
+        oldpath = self._generatePath(oldpath)
+        newpath = self._generatePath(newpath)
+        self.avatar.fs_access.rename(oldpath, newpath)
+
+    def makeDirectory(self, path, attrs):
+        self.avatar.fs_access.mkdir(self._generatePath(path))
+
+    def removeDirectory(self, path):
+        self.avatar.fs_access.rmdir(self._generatePath(path))
+
+    def openDirectory(self, path):
+        return ZopeSFTPDirectory(self, self._generatePath(path))
+
+    def getAttrs(self, path, followLinks):
+        fp = ZopeSFTPFile(self, self._generatePath(path), FXF_READ, {})
+
+        return fp.getAttrs()
+
+    def setAttrs(self, path, attrs):
+        pass
+
+    def readLink(self, path):
+        raise NotImplementedError, "readLink not implemented."
+
+    def makeLink(self, linkPath, targetPath):
+        raise NotImplementedError, "makeLink not implemented."
+
+    def realPath(self, path):
+        return self._generatePath(path)
+
+    def extendedRequest(self, extendedName, extendedData):
+        raise NotImplementedError, \
+              "Zope doesn't support any SFTP extensions."
+
+    def _generatePath(self, args):
+        path = posixpath.join('/', args)
+        return posixpath.normpath(path)
+
+
+class ZopeSFTPDirectory(object):
+
+    def __init__(self, server, directory):
+        self.server = server
+        self.dir    = directory
+        self.files  = self.server.avatar.fs_access.names(directory)
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        try:
+            f = self.files.pop(0)
+        except IndexError:
+            raise StopIteration
+        else:
+            file = ZopeSFTPFile(self.server, posixpath.join(self.dir, f),
+                                FXF_READ, {})
+            s = file.getAttrs()
+            longname = _lsLine(f, s)
+            return (str(f), str(longname), s)
+
+    def close(self):
+        self.files = []
+
+
+class ZopeSFTPFile(object):
+    implements(ISFTPFile)
+
+    def __init__(self, server, filename, flags, attrs):
+        self.server   = server
+        self.filename = filename
+        self.attrs    = attrs
+
+        if flags & FXF_APPEND == FXF_APPEND:
+            self.append = True
+        else:
+            self.append = False
+
+    def close(self):
+        pass
+
+    def readChunk(self, offset, length):
+        outstream = StringIO()
+        self.server.avatar.fs_access.readfile(self.filename,
+                                              outstream,
+                                              start = offset,
+                                              end   = offset + length)
+        chunk = outstream.getvalue()
+        outstream.close()
+
+        return chunk
+
+    def writeChunk(self, offset, data):
+        instream = StringIO(data)
+        self.server.avatar.fs_access.writefile(self.filename,
+                                               instream,
+                                               start = offset,
+                                               end   = offset + len(data),
+                                               append = self.append)
+        instream.close()
+
+    def getAttrs(self):
+        attrs = self.server.avatar.fs_access.lsinfo(self.filename)
+
+        retattrs = {}
+        retattrs['size'] = attrs.get('size', 0)
+        ## uid
+        ## gid
+        ## permissions
+        permissions = 0
+        def _isKeyTrue(key):
+            return attrs.has_key(key) and attrs[key] is True
+        if _isKeyTrue('owner_readable'):
+            permissions |= stat.S_IRUSR
+        if _isKeyTrue('owner_writable'):
+            permissions |= stat.S_IWUSR
+        if _isKeyTrue('owner_executable'):
+            permissions |= stat.S_IXUSR
+        if _isKeyTrue('group_readable'):
+            permissions |= stat.S_IRGRP
+        if _isKeyTrue('group_writable'):
+            permissions |= stat.S_IWGRP
+        if _isKeyTrue('group_executable'):
+            permissions |= stat.S_IXGRP
+        if _isKeyTrue('other_readable'):
+            permissions |= stat.S_IROTH
+        if _isKeyTrue('other_writable'):
+            permissions |= stat.S_IWOTH
+        if _isKeyTrue('other_executable'):
+            permissions |= stat.S_IXOTH
+        filetype = self.server.avatar.fs_access.type(self.filename)
+        if filetype == 'd':
+            permissions |= stat.S_IFDIR
+        elif filetype == 'f':
+            permissions |= stat.S_IFREG
+        retattrs['permissions'] = permissions
+        ## atime
+        if attrs['mtime'] is not None:
+            retattrs['mtime'] = attrs['mtime']
+        return retattrs
+
+    def setAttrs(self, attrs):
+        ## IFileSystem doesn't currently support the setting of attributes.
+        pass
+
+## modified from twisted.consh.unix._lsLine
+def _lsLine(name, s):
+    ## mode = s.st_mode
+    mode = s['permissions']
+    perms = array.array('c', '-'*10)
+    ft = stat.S_IFMT(mode)
+    if stat.S_ISDIR(ft): perms[0] = 'd'
+    elif stat.S_ISCHR(ft): perms[0] = 'c'
+    elif stat.S_ISBLK(ft): perms[0] = 'b'
+    elif stat.S_ISREG(ft): perms[0] = '-'
+    elif stat.S_ISFIFO(ft): perms[0] = 'f'
+    elif stat.S_ISLNK(ft): perms[0] = 'l'
+    elif stat.S_ISSOCK(ft): perms[0] = 's'
+    else: perms[0] = '!'
+    # user
+    if mode&stat.S_IRUSR:perms[1] = 'r'
+    if mode&stat.S_IWUSR:perms[2] = 'w'
+    if mode&stat.S_IXUSR:perms[3] = 'x'
+    # group
+    if mode&stat.S_IRGRP:perms[4] = 'r'
+    if mode&stat.S_IWGRP:perms[5] = 'w'
+    if mode&stat.S_IXGRP:perms[6] = 'x'
+    # other
+    if mode&stat.S_IROTH:perms[7] = 'r'
+    if mode&stat.S_IWOTH:perms[8] = 'w'
+    if mode&stat.S_IXOTH:perms[9] = 'x'
+    # suid/sgid
+    if mode&stat.S_ISUID:
+        if perms[3] == 'x': perms[3] = 's'
+        else: perms[3] = 'S'
+    if mode&stat.S_ISGID:
+        if perms[6] == 'x': perms[6] = 's'
+        else: perms[6] = 'S'
+    l = perms.tostring()
+    ## l += str(s.st_nlink).rjust(5) + ' '
+    l += str(0).rjust(5) + ' '
+    ## un = str(s.st_uid)
+    un = s.get('owner_name', 'na')
+    l += un.ljust(9)
+    ## gr = str(s.st_gid)
+    gr = s.get('group_name', 'na')
+    l += gr.ljust(9)
+    ## sz = str(s.st_size)
+    sz = str(s.get('size', 0))
+    l += sz.rjust(8)
+    l += ' '
+    ## sixmo = 60 * 60 * 24 * 7 * 26
+    sixmo = datetime.timedelta(days = 26 * 7) # six months time delta object
+    ## if s.st_mtime + sixmo < time.time(): # last edited more than 6mo ago
+    mtime = s['mtime']
+    if (mtime + sixmo).date() < datetime.datetime.now().date():
+        ## l += time.strftime("%b %2d  %Y ", time.localtime(s.st_mtime))
+        l += mtime.strftime("%b %2d  %Y ") ## , time.localtime(mtime))
+    else:
+        ## l += time.strftime("%b %2d %H:%S ", time.localtime(s.st_mtime))
+        l += mtime.strftime("%b %2d %H:%S ") ## , time.localtime(mtime))
+    l += name
+    return l


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

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/tests/__init__.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/tests/__init__.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/tests/__init__.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1 @@
+# this is a module.


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

Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/tests/tests_sftpserver.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/tests/tests_sftpserver.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/tests/tests_sftpserver.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1,15 @@
+from unittest import TestSuite, main
+
+from zope.testing import doctest
+from zope.app.server.sftp import HAS_CRYPTO
+
+def test_suite():
+    if HAS_CRYPTO:
+        return TestSuite((
+            doctest.DocTestSuite('zope.app.server.sftp.server'),
+            ))
+    else:
+        return
+
+if __name__ == "__main__":
+    main(defaultTest = 'test_suite')


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

Copied: Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/demofs.py (from rev 30300, Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/demofs.py)
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/demofs.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/demofs.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1,310 @@
+##############################################################################
+# Copyright (c) 2003 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.
+##############################################################################
+"""Demo file-system implementation, for testing
+
+$Id: demofs.py 27459 2004-09-07 01:45:52Z shane $
+"""
+import posixpath
+from zope.security.interfaces import Unauthorized
+from zope.app.server.interfaces import IFileSystem
+## from zope.server.interfaces.ftp import IFileSystemAccess
+from zope.interface import implements
+
+execute = 1
+read = 2
+write = 4
+
+class File(object):
+    type = 'f'
+    modified=None
+
+    def __init__(self):
+        self.access = {'anonymous': read}
+
+    def accessable(self, user, access=read):
+        return (user == 'root'
+                or (self.access.get(user, 0) & access)
+                or (self.access.get('anonymous', 0) & access)
+                )
+
+    def grant(self, user, access):
+        self.access[user] = self.access.get(user, 0) | access
+
+    def revoke(self, user, access):
+        self.access[user] = self.access.get(user, 0) ^ access
+
+class Directory(File):
+
+    type = 'd'
+
+    def __init__(self):
+        super(Directory, self).__init__()
+        self.files = {}
+
+    def get(self, name, default=None):
+        return self.files.get(name, default)
+
+    def __getitem__(self, name):
+        return self.files[name]
+
+    def __setitem__(self, name, v):
+        self.files[name] = v
+
+    def __delitem__(self, name):
+        del self.files[name]
+
+    def __contains__(self, name):
+        return name in self.files
+
+    def __iter__(self):
+        return iter(self.files)
+
+class DemoFileSystem(object):
+    __doc__ = IFileSystem.__doc__
+
+    implements(IFileSystem)
+
+    File = File
+    Directory = Directory
+
+    def __init__(self, files, user=''):
+        self.files = files
+        self.user = user
+
+    def get(self, path, default=None):
+
+        while path.startswith('/'):
+            path = path[1:]
+
+        d = self.files
+        if path:
+            for name in path.split('/'):
+                if d.type is not 'd':
+                    return default
+                if not d.accessable(self.user):
+                    raise Unauthorized
+                d = d.get(name)
+                if d is None:
+                    break
+
+        return d
+
+    def getany(self, path):
+        d = self.get(path)
+        if d is None:
+            raise OSError("No such file or directory:", path)
+        return d
+
+    def getdir(self, path):
+        d = self.getany(path)
+        if d.type != 'd':
+            raise OSError("Not a directory:", path)
+        return d
+
+    def getfile(self, path):
+        d = self.getany(path)
+        if d.type != 'f':
+            raise OSError("Not a file:", path)
+        return d
+
+    def getwdir(self, path):
+        d = self.getdir(path)
+        if not d.accessable(self.user, write):
+            raise OSError("Permission denied")
+        return d
+
+    def type(self, path):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        f = self.get(path)
+        return getattr(f, 'type', None)
+
+    def names(self, path, filter=None):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        f = list(self.getdir(path))
+        if filter is not None:
+            f = [name for name in f if filter(name)]
+
+        return f
+
+    def _lsinfo(self, name, file):
+        info = {
+            'type': file.type,
+            'name': name,
+            'group_read': file.accessable(self.user, read),
+            'group_write': file.accessable(self.user, write),
+            }
+        if file.type == 'f':
+            info['size'] = len(file.data)
+        if file.modified is not None:
+            info['mtime'] = file.modified
+
+        return info
+
+    def ls(self, path, filter=None):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        f = self.getdir(path)
+        if filter is None:
+            return [self._lsinfo(name, f.files[name])
+                    for name in f
+                    ]
+
+        return [self._lsinfo(name, f.files[name])
+                for name in f
+                if filter(name)]
+
+    def readfile(self, path, outstream, start=0, end=None):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        f = self.getfile(path)
+
+        data = f.data
+        if end is not None:
+            data = data[:end]
+        if start:
+            data = data[start:]
+
+        outstream.write(data)
+
+    def lsinfo(self, path):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        f = self.getany(path)
+        return self._lsinfo(posixpath.split(path)[1], f)
+
+    def mtime(self, path):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        f = self.getany(path)
+        return f.modified
+
+    def size(self, path):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        f = self.getany(path)
+        return len(getattr(f, 'data', ''))
+
+    def mkdir(self, path):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        path, name = posixpath.split(path)
+        d = self.getwdir(path)
+        if name in d.files:
+            raise OSError("Already exists:", name)
+        newdir = self.Directory()
+        newdir.grant(self.user, read | write)
+        d.files[name] = newdir
+
+    def remove(self, path):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        path, name = posixpath.split(path)
+        d = self.getwdir(path)
+        if name not in d.files:
+            raise OSError("Not exists:", name)
+        f = d.files[name]
+        if f.type == 'd':
+            raise OSError('Is a directory:', name)
+        del d.files[name]
+
+    def rmdir(self, path):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        path, name = posixpath.split(path)
+        d = self.getwdir(path)
+        if name not in d.files:
+            raise OSError("Not exists:", name)
+        f = d.files[name]
+        if f.type != 'd':
+            raise OSError('Is not a directory:', name)
+        del d.files[name]
+
+    def rename(self, old, new):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        oldpath, oldname = posixpath.split(old)
+        newpath, newname = posixpath.split(new)
+
+        olddir = self.getwdir(oldpath)
+        newdir = self.getwdir(newpath)
+
+        if oldname not in olddir.files:
+            raise OSError("Not exists:", oldname)
+        if newname in newdir.files:
+            raise OSError("Already exists:", newname)
+
+        newdir.files[newname] = olddir.files[oldname]
+        del olddir.files[oldname]
+
+    def writefile(self, path, instream, start=None, end=None, append=False):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        path, name = posixpath.split(path)
+        d = self.getdir(path)
+        f = d.files.get(name)
+        if f is None:
+            d = self.getwdir(path)
+            f = d.files[name] = self.File()
+            f.grant(self.user, read | write)
+        elif f.type != 'f':
+            raise OSError("Can't overwrite a directory")
+
+        if not f.accessable(self.user, write):
+            raise OSError("Permission denied")
+
+        if append:
+            f.data += instream.read()
+        else:
+
+            if start:
+                if start < 0:
+                    raise ValueError("Negative starting file position")
+                prefix = f.data[:start]
+                if len(prefix) < start:
+                    prefix += '\0' * (start - len(prefix))
+            else:
+                prefix = ''
+                start=0
+
+            if end:
+                if end < 0:
+                    raise ValueError("Negative ending file position")
+                l = end - start
+                newdata = instream.read(l)
+
+                f.data = prefix+newdata+f.data[start+len(newdata):]
+            else:
+                f.data = prefix + instream.read()
+
+    def writable(self, path):
+        "See zope.server.interfaces.ftp.IFileSystem"
+        path, name = posixpath.split(path)
+        try:
+            d = self.getdir(path)
+        except OSError:
+            return False
+        if name not in d:
+            return d.accessable(self.user, write)
+        f = d[name]
+        return f.type == 'f' and f.accessable(self.user, write)
+
+## class DemoFileSystemAccess(object):
+##     __doc__ = IFileSystemAccess.__doc__
+
+##     implements(IFileSystemAccess)
+
+##     def __init__(self, files, users):
+##         self.files = files
+##         self.users = users
+
+##     def authenticate(self, credentials):
+##         "See zope.server.interfaces.ftp.IFileSystemAccess"
+##         user, password = credentials
+##         if user != 'anonymous':
+##             if self.users.get(user) != password:
+##                 raise Unauthorized
+##         return user
+
+##     def open(self, credentials):
+##         "See zope.server.interfaces.ftp.IFileSystemAccess"
+##         user = self.authenticate(credentials)
+##         return DemoFileSystem(self.files, user)

Copied: Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/fstests.py (from rev 30300, Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/fstests.py)
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/fstests.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/fstests.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1,156 @@
+##############################################################################
+#
+# 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.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.
+#
+##############################################################################
+"""Abstract file-system tests
+
+$Id: fstests.py 26559 2004-07-15 21:22:32Z srichter $
+"""
+from StringIO import StringIO
+from zope.interface.verify import verifyObject
+from zope.app.server.interfaces import IFileSystem
+
+class FileSystemTests(object):
+    """Tests of a readable filesystem
+    """
+
+    filesystem = None
+    dir_name  = '/dir'
+    file_name = '/dir/file.txt'
+    unwritable_filename = '/dir/protected.txt'
+    dir_contents = ['file.txt', 'protected.txt']
+    file_contents = 'Lengthen your stride'
+
+    def test_type(self):
+        self.assertEqual(self.filesystem.type(self.dir_name), 'd')
+        self.assertEqual(self.filesystem.type(self.file_name), 'f')
+
+
+    def test_names(self):
+        lst = self.filesystem.names(self.dir_name)
+        lst.sort()
+        self.assertEqual(lst, self.dir_contents)
+
+    def test_readfile(self):
+        s = StringIO()
+        self.filesystem.readfile(self.file_name, s)
+        self.assertEqual(s.getvalue(), self.file_contents)
+
+
+    def testReadPartOfFile(self):
+        s = StringIO()
+        self.filesystem.readfile(self.file_name, s, 2)
+        self.assertEqual(s.getvalue(), self.file_contents[2:])
+
+
+    def testReadPartOfFile2(self):
+        s = StringIO()
+        self.filesystem.readfile(self.file_name, s, 1, 5)
+        self.assertEqual(s.getvalue(), self.file_contents[1:5])
+
+    def test_IFileSystemInterface(self):
+        verifyObject(IFileSystem, self.filesystem)
+
+    def testRemove(self):
+        self.filesystem.remove(self.file_name)
+        self.failIf(self.filesystem.type(self.file_name))
+
+
+    def testMkdir(self):
+        path = self.dir_name + '/x'
+        self.filesystem.mkdir(path)
+        self.assertEqual(self.filesystem.type(path), 'd')
+
+    def testRmdir(self):
+        self.filesystem.remove(self.file_name)
+        self.filesystem.rmdir(self.dir_name)
+        self.failIf(self.filesystem.type(self.dir_name))
+
+
+    def testRename(self):
+        self.filesystem.rename(self.file_name, self.file_name + '.bak')
+        self.assertEqual(self.filesystem.type(self.file_name), None)
+        self.assertEqual(self.filesystem.type(self.file_name + '.bak'), 'f')
+
+
+    def testWriteFile(self):
+        s = StringIO()
+        self.filesystem.readfile(self.file_name, s)
+        self.assertEqual(s.getvalue(), self.file_contents)
+
+        data = 'Always ' + self.file_contents
+        s = StringIO(data)
+        self.filesystem.writefile(self.file_name, s)
+
+        s = StringIO()
+        self.filesystem.readfile(self.file_name, s)
+        self.assertEqual(s.getvalue(), data)
+
+
+    def testAppendToFile(self):
+        data = ' again'
+        s = StringIO(data)
+        self.filesystem.writefile(self.file_name, s, append=True)
+
+        s = StringIO()
+        self.filesystem.readfile(self.file_name, s)
+        self.assertEqual(s.getvalue(), self.file_contents + data)
+
+    def testWritePartOfFile(self):
+        data = '123'
+        s = StringIO(data)
+        self.filesystem.writefile(self.file_name, s, 3, 6)
+
+        expect = self.file_contents[:3] + data + self.file_contents[6:]
+
+        s = StringIO()
+        self.filesystem.readfile(self.file_name, s)
+        self.assertEqual(s.getvalue(), expect)
+
+    def testWritePartOfFile_and_truncate(self):
+        data = '123'
+        s = StringIO(data)
+        self.filesystem.writefile(self.file_name, s, 3)
+
+        expect = self.file_contents[:3] + data
+
+        s = StringIO()
+        self.filesystem.readfile(self.file_name, s)
+        self.assertEqual(s.getvalue(), expect)
+
+    def testWriteBeyondEndOfFile(self):
+        partlen = len(self.file_contents) - 6
+        data = 'daylight savings'
+        s = StringIO(data)
+        self.filesystem.writefile(self.file_name, s, partlen)
+
+        expect = self.file_contents[:partlen] + data
+
+        s = StringIO()
+        self.filesystem.readfile(self.file_name, s)
+        self.assertEqual(s.getvalue(), expect)
+
+
+    def testWriteNewFile(self):
+        s = StringIO(self.file_contents)
+        self.filesystem.writefile(self.file_name + '.new', s)
+
+        s = StringIO()
+        self.filesystem.readfile(self.file_name, s)
+        self.assertEqual(s.getvalue(), self.file_contents)
+
+
+    def test_writable(self):
+        self.failIf(self.filesystem.writable(self.dir_name))
+        self.failIf(self.filesystem.writable(self.unwritable_filename))
+        self.failUnless(self.filesystem.writable(self.file_name))
+        self.failUnless(self.filesystem.writable(self.file_name+'1'))

Copied: Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_demofs.py (from rev 30300, Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_demofs.py)

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-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_docs.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -54,6 +54,7 @@
         doctest.DocFileSuite('../log.txt',
                              globs={'pprint': doctestunit.pprint},
                              optionflags=doctest.NORMALIZE_WHITESPACE),
+        doctest.DocTestSuite('zope.app.server.utils')
         ))
 
 

Copied: Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_publisher.py (from rev 30300, Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_publisher.py)
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/tests/test_publisher.py	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/tests/test_publisher.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1,123 @@
+##############################################################################
+#
+# Copyright (c) 2003 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.
+#
+##############################################################################
+"""Test the FTP publisher.
+
+$Id: test_publisher.py 26559 2004-07-15 21:22:32Z srichter $
+"""
+import demofs
+from unittest import TestCase, TestSuite, main, makeSuite
+from fstests import FileSystemTests
+from StringIO import StringIO
+from zope.publisher.publish import mapply
+from zope.app.server.utils import PublisherFileSystem
+
+class DemoFileSystem(demofs.DemoFileSystem):
+
+    def rename(self, path, old, new):
+        return demofs.DemoFileSystem.rename(
+            self, "%s/%s" % (path, old), "%s/%s" % (path, new)) 
+
+class Publication(object):
+
+    def __init__(self, root):
+        self.root = root
+
+    def beforeTraversal(self, request):
+        pass
+    
+    def getApplication(self, request):
+        return self.root
+
+    def afterTraversal(self, request, ob):
+        pass
+
+    def callObject(self, request, ob):
+        command = getattr(ob, request.env['command'])
+        if 'name' in request.env:
+            request.env['path'] += "/" + request.env['name']
+        return mapply(command, request = request.env)
+
+    def afterCall(self, request, ob):
+        pass
+
+    def endRequest(self, request, ob):
+        pass
+
+    def handleException(self, object, request, info, retry_allowed=True):
+        request.response._exc = info[:2]
+        
+
+class Request(object):
+
+    def __init__(self, input, output, env):
+        self.env = env
+        self.response = Response()
+        self.user = env['credentials']
+        del env['credentials']
+
+    def processInputs(self):
+        pass
+
+    def traverse(self, root):
+        root.user = self.user
+        return root
+
+    def close(self):
+        pass
+
+class Response(object):
+
+    _exc = _body = None
+
+    def setBody(self, result):
+        self._body = result
+
+    def outputBody(self):
+        pass
+
+    def getResult(self):
+        if self._exc:
+            raise self._exc[0], self._exc[1]
+        return self._body
+
+class RequestFactory(object):
+
+    def __init__(self, root):
+        self.pub = Publication(root)
+
+    def __call__(self, input, output, env):
+        r = Request(input, output, env)
+        r.publication = self.pub
+        return r
+
+class TestPublisherFileSystem(FileSystemTests, TestCase):
+
+    def setUp(self):
+        root = demofs.Directory()
+        root.grant('bob', demofs.write)
+        fs = DemoFileSystem(root, 'bob')
+        fs.mkdir(self.dir_name)
+        fs.writefile(self.file_name, StringIO(self.file_contents))
+        fs.writefile(self.unwritable_filename, StringIO("save this"))
+        fs.get(self.unwritable_filename).revoke('bob', demofs.write)
+
+        self.filesystem = PublisherFileSystem('bob', RequestFactory(fs))
+
+def test_suite():
+    return TestSuite((
+        makeSuite(TestPublisherFileSystem),
+        ))
+
+if __name__=='__main__':
+    main(defaultTest='test_suite')

Copied: Zope3/branches/srichter-twisted-integration/src/zope/app/server/utils.py (from rev 30300, 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-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/utils.py	2005-05-08 21:44:39 UTC (rev 30301)
@@ -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 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

Added: Zope3/branches/srichter-twisted-integration/ssh_host_rsa_key
===================================================================
--- Zope3/branches/srichter-twisted-integration/ssh_host_rsa_key	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/ssh_host_rsa_key	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWQIBAAKBgQDdsFPHwMxwC94cNJp/DCmLmQSs6w/qs9yRhXWCMpzzWIv9BP+j
+XXMlwSvORJENaQCawOZA1g5cbT44ooLGbXwSMyHjygN5C6J+Kmx6ZeE6Oj/Z/0Lz
+2ub17W1jMBSFuoJljK6mNB+DwnneF0HoA+9bKnyalzcsska2NRGs1URPlQIBIwKB
+gHhYZ/65HoX30Fh0U96RkuVhshS6HpVaUyptBUatwuqAhoIKBx4ramTz1fOhkJJA
+UMkJoZDpOv75eYx1ejE0HscJ50202CMsQ1cmgm2FSaDaKCpBUFyD0XUhJwqURq0U
+D6Yhvo6RoXHInP/qBtIw5LVgPQPkjdKE797bQTb6jKwrAkEA/rPfwjzYE1bZnxrK
+G6AUcLAgi/IK7lN4s94sN3PQMAyWCLl54aIE8QsPcV7+nWJVU7OWV/m9CcLBTgI8
+iL0HXQJBAN7RZ2nMsQUS9oe/vh7Udr5uKD5ej1gRpXXAj0XNrJtmdQ/m/UY5JnrU
+VYr+xIW/S++b/RN7jYs14R1Gds/U/ZkCQHu2c/9CH87hCp28jg1rAp0iWPOEMTHu
+B21Op8Mpn6JPQY81hFemd23Diyhv+ANNBN+DHSNqrEaRy5rrJLdxwb8CQBMZQ2DX
+B9vdDdEfEEvJEX4JcSnq2RYtZfQmcq42M13G9BdOUDk4GT26bbt07EX6dDkb/7/X
+Y+dcY8CuRLK6fCMCQEZ89lCt6ko74P6NE/ffCf7VET8bBh5/vuSqIgqNxvCLO1iE
+TE603x/EdIMeHfw8qmAmOsZZ7WU+frpx7JvpL8Y=
+-----END RSA PRIVATE KEY-----


Property changes on: Zope3/branches/srichter-twisted-integration/ssh_host_rsa_key
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Zope3/branches/srichter-twisted-integration/ssh_host_rsa_key.pub
===================================================================
--- Zope3/branches/srichter-twisted-integration/ssh_host_rsa_key.pub	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/ssh_host_rsa_key.pub	2005-05-08 21:44:39 UTC (rev 30301)
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA3bBTx8DMcAveHDSafwwpi5kErOsP6rPckYV1gjKc81iL/QT/o11zJcErzkSRDWkAmsDmQNYOXG0+OKKCxm18EjMh48oDeQuifipsemXhOjo/2f9C89rm9e1tYzAUhbqCZYyupjQfg8J53hdB6APvWyp8mpc3LLJGtjURrNVET5U= michael at training.office.openapp.biz


Property changes on: Zope3/branches/srichter-twisted-integration/ssh_host_rsa_key.pub
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: Zope3/branches/srichter-twisted-integration/zope.conf.in
===================================================================
--- Zope3/branches/srichter-twisted-integration/zope.conf.in	2005-05-08 08:15:23 UTC (rev 30300)
+++ Zope3/branches/srichter-twisted-integration/zope.conf.in	2005-05-08 21:44:39 UTC (rev 30301)
@@ -38,6 +38,13 @@
   address 8021
 </server>
 
+# You must install pycrypto to use the SFTP server.
+# <sshserver>
+#   type SFTP
+#   address 8115
+#   hostkey ssh_host_rsa_key
+# </sshserver>
+
 # Standard Filestorage
 <zodb>
   <filestorage>



More information about the Zope3-Checkins mailing list