[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