Commit ab628670 authored by Hanno Schlichting's avatar Hanno Schlichting

Make webdav/ftp methods conditionally available based on ZServer presence.

parent ed87506a
...@@ -20,6 +20,7 @@ import warnings ...@@ -20,6 +20,7 @@ import warnings
from AccessControl.class_init import InitializeClass from AccessControl.class_init import InitializeClass
from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityInfo import ClassSecurityInfo
from Acquisition import Explicit from Acquisition import Explicit
from App import bbb
from App.Common import package_home from App.Common import package_home
from App.Common import rfc1123_date from App.Common import rfc1123_date
from App.config import getConfiguration from App.config import getConfiguration
...@@ -119,6 +120,7 @@ class ImageFile(Explicit): ...@@ -119,6 +120,7 @@ class ImageFile(Explicit):
return filestream_iterator(self.path, mode='rb') return filestream_iterator(self.path, mode='rb')
if bbb.HAS_ZSERVER:
security.declarePublic('HEAD') security.declarePublic('HEAD')
def HEAD(self, REQUEST, RESPONSE): def HEAD(self, REQUEST, RESPONSE):
""" """ """ """
......
...@@ -10,45 +10,11 @@ ...@@ -10,45 +10,11 @@
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
"""FTP Support for Zope classes.
Preliminary FTP support interface. Note, most FTP functions are import pkg_resources
provided by existing methods such as PUT and manage_delObjects.
All FTP methods should be governed by a single permission: HAS_ZSERVER = True
'FTP access'. try:
""" dist = pkg_resources.get_distribution('ZServer')
except pkg_resources.DistributionNotFound:
from zope.interface import implements HAS_ZSERVER = False
from interfaces import IFTPAccess
class FTPInterface:
"Interface for FTP objects"
implements(IFTPAccess)
# XXX The stat and list marshal format should probably
# be XML, not marshal, maybe Andrew K's xml-marshal.
# This will probably be changed later.
def manage_FTPstat(self, REQUEST):
"""Returns a stat-like tuple. (marshalled to a string) Used by
FTP for directory listings, and MDTM and SIZE"""
def manage_FTPlist(self, REQUEST):
"""Returns a directory listing consisting of a tuple of
(id,stat) tuples, marshaled to a string. Note, the listing it
should include '..' if there is a Folder above the current
one.
In the case of non-foldoid objects it should return a single
tuple (id,stat) representing itself."""
# Optional method to support FTP download.
# Should not be implemented by Foldoid objects.
def manage_FTPget(self):
"""Returns the source content of an object. For example, the
source text of a Document, or the data of a file."""
...@@ -133,6 +133,7 @@ class Application(ApplicationDefaultPermissions, Folder.Folder): ...@@ -133,6 +133,7 @@ class Application(ApplicationDefaultPermissions, Folder.Folder):
"""Utility function to return current date/time""" """Utility function to return current date/time"""
return DateTime(*args) return DateTime(*args)
if bbb.HAS_ZSERVER:
def DELETE(self, REQUEST, RESPONSE): def DELETE(self, REQUEST, RESPONSE):
"""Delete a resource object.""" """Delete a resource object."""
self.dav__init(REQUEST, RESPONSE) self.dav__init(REQUEST, RESPONSE)
......
...@@ -31,6 +31,7 @@ from AccessControl.requestmethod import requestmethod ...@@ -31,6 +31,7 @@ from AccessControl.requestmethod import requestmethod
from AccessControl.tainted import TaintedString from AccessControl.tainted import TaintedString
from DocumentTemplate.permissions import change_dtml_methods from DocumentTemplate.permissions import change_dtml_methods
from DocumentTemplate.security import RestrictedDTML from DocumentTemplate.security import RestrictedDTML
from OFS import bbb
from OFS.Cache import Cacheable from OFS.Cache import Cacheable
from OFS.History import Historical from OFS.History import Historical
from OFS.History import html_diff from OFS.History import html_diff
...@@ -366,6 +367,7 @@ class DTMLMethod(RestrictedDTML, ...@@ -366,6 +367,7 @@ class DTMLMethod(RestrictedDTML,
RESPONSE.setHeader('Content-Type', 'text/plain') RESPONSE.setHeader('Content-Type', 'text/plain')
return self.read() return self.read()
if bbb.HAS_ZSERVER:
security.declareProtected(change_dtml_methods, 'PUT') security.declareProtected(change_dtml_methods, 'PUT')
def PUT(self, REQUEST, RESPONSE): def PUT(self, REQUEST, RESPONSE):
""" Handle FTP / HTTP PUT requests. """ Handle FTP / HTTP PUT requests.
......
...@@ -37,6 +37,7 @@ from zope.contenttype import guess_content_type ...@@ -37,6 +37,7 @@ from zope.contenttype import guess_content_type
from zope.interface import implementedBy from zope.interface import implementedBy
from zope.interface import implements from zope.interface import implements
from OFS import bbb
from OFS.Cache import Cacheable from OFS.Cache import Cacheable
from OFS.interfaces import IWriteLock from OFS.interfaces import IWriteLock
from OFS.PropertyManager import PropertyManager from OFS.PropertyManager import PropertyManager
...@@ -595,23 +596,6 @@ class File(Persistent, Implicit, PropertyManager, ...@@ -595,23 +596,6 @@ class File(Persistent, Implicit, PropertyManager,
return next, size return next, size
security.declareProtected(change_images_and_files, 'PUT')
def PUT(self, REQUEST, RESPONSE):
"""Handle HTTP PUT requests"""
self.dav__init(REQUEST, RESPONSE)
self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
type = REQUEST.get_header('content-type', None)
file = REQUEST['BODYFILE']
data, size = self._read_data(file)
content_type = self._get_content_type(file, data, self.__name__,
type or self.content_type)
self.update_data(data, content_type, size)
RESPONSE.setStatus(204)
return RESPONSE
security.declareProtected(View, 'get_size') security.declareProtected(View, 'get_size')
def get_size(self): def get_size(self):
# Get the size of a file or image. # Get the size of a file or image.
...@@ -636,6 +620,24 @@ class File(Persistent, Implicit, PropertyManager, ...@@ -636,6 +620,24 @@ class File(Persistent, Implicit, PropertyManager,
def __len__(self): def __len__(self):
return 1 return 1
if bbb.HAS_ZSERVER:
security.declareProtected(change_images_and_files, 'PUT')
def PUT(self, REQUEST, RESPONSE):
"""Handle HTTP PUT requests"""
self.dav__init(REQUEST, RESPONSE)
self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
type = REQUEST.get_header('content-type', None)
file = REQUEST['BODYFILE']
data, size = self._read_data(file)
content_type = self._get_content_type(file, data, self.__name__,
type or self.content_type)
self.update_data(data, content_type, size)
RESPONSE.setStatus(204)
return RESPONSE
security.declareProtected(ftp_access, 'manage_FTPstat') security.declareProtected(ftp_access, 'manage_FTPstat')
security.declareProtected(ftp_access, 'manage_FTPlist') security.declareProtected(ftp_access, 'manage_FTPlist')
...@@ -647,11 +649,11 @@ class File(Persistent, Implicit, PropertyManager, ...@@ -647,11 +649,11 @@ class File(Persistent, Implicit, PropertyManager,
if self.ZCacheable_isCachingEnabled(): if self.ZCacheable_isCachingEnabled():
result = self.ZCacheable_get(default=None) result = self.ZCacheable_get(default=None)
if result is not None: if result is not None:
# We will always get None from RAMCacheManager but we will get # We will always get None from RAMCacheManager but we will
# something implementing the IStreamIterator interface # get something implementing the IStreamIterator interface
# from FileCacheManager. # from FileCacheManager.
# the content-length is required here by HTTPResponse, even # the content-length is required here by HTTPResponse,
# though FTP doesn't use it. # even though FTP doesn't use it.
RESPONSE.setHeader('Content-Length', self.size) RESPONSE.setHeader('Content-Length', self.size)
return result return result
......
...@@ -596,8 +596,15 @@ class ObjectManager(CopyContainer, ...@@ -596,8 +596,15 @@ class ObjectManager(CopyContainer,
listing.sort() listing.sort()
return listing return listing
# FTP support methods security.declareProtected(ftp_access, 'manage_hasId')
def manage_hasId(self, REQUEST):
""" check if the folder has an object with REQUEST['id'] """
if not REQUEST['id'] in self.objectIds():
raise KeyError(REQUEST['id'])
if bbb.HAS_ZSERVER:
# FTP support methods
security.declareProtected(ftp_access, 'manage_FTPlist') security.declareProtected(ftp_access, 'manage_FTPlist')
def manage_FTPlist(self, REQUEST): def manage_FTPlist(self, REQUEST):
"""Directory listing for FTP. """Directory listing for FTP.
...@@ -608,7 +615,8 @@ class ObjectManager(CopyContainer, ...@@ -608,7 +615,8 @@ class ObjectManager(CopyContainer,
ob = self ob = self
while 1: while 1:
if is_acquired(ob): if is_acquired(ob):
raise ValueError('FTP List not supported on acquired objects') raise ValueError(
'FTP List not supported on acquired objects')
if not hasattr(ob, '__parent__'): if not hasattr(ob, '__parent__'):
break break
ob = aq_parent(ob) ob = aq_parent(ob)
...@@ -652,13 +660,6 @@ class ObjectManager(CopyContainer, ...@@ -652,13 +660,6 @@ class ObjectManager(CopyContainer,
out = out + ((k, stat),) out = out + ((k, stat),)
return marshal.dumps(out) return marshal.dumps(out)
security.declareProtected(ftp_access, 'manage_hasId')
def manage_hasId(self, REQUEST):
""" check if the folder has an object with REQUEST['id'] """
if not REQUEST['id'] in self.objectIds():
raise KeyError(REQUEST['id'])
security.declareProtected(ftp_access, 'manage_FTPstat') security.declareProtected(ftp_access, 'manage_FTPstat')
def manage_FTPstat(self, REQUEST): def manage_FTPstat(self, REQUEST):
"""Psuedo stat, used by FTP for directory listings. """Psuedo stat, used by FTP for directory listings.
......
...@@ -270,6 +270,7 @@ class Item(Base, ...@@ -270,6 +270,7 @@ class Item(Base,
return () return ()
objectIds = objectItems = objectValues objectIds = objectItems = objectValues
if bbb.HAS_ZSERVER:
# FTP support methods # FTP support methods
def manage_FTPstat(self, REQUEST): def manage_FTPstat(self, REQUEST):
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
from zope.component.interfaces import IPossibleSite from zope.component.interfaces import IPossibleSite
from zope.container.interfaces import IContainer from zope.container.interfaces import IContainer
from zope.deferredimport import deprecated
from zope.interface import Attribute from zope.interface import Attribute
from zope.interface import Interface from zope.interface import Interface
from zope.interface.interfaces import IObjectEvent from zope.interface.interfaces import IObjectEvent
...@@ -171,27 +172,6 @@ class ICopySource(Interface): ...@@ -171,27 +172,6 @@ class ICopySource(Interface):
""" """
# XXX: might contain non-API methods and outdated comments;
# not synced with ZopeBook API Reference;
# based on OFS.FTPInterface.FTPInterface
class IFTPAccess(Interface):
"""Provide support for FTP access"""
def manage_FTPstat(REQUEST):
"""Returns a stat-like tuple. (marshalled to a string) Used by
FTP for directory listings, and MDTM and SIZE"""
def manage_FTPlist(REQUEST):
"""Returns a directory listing consisting of a tuple of
(id,stat) tuples, marshaled to a string. Note, the listing it
should include '..' if there is a Folder above the current
one.
In the case of non-foldoid objects it should return a single
tuple (id,stat) representing itself."""
# XXX: might contain non-API methods and outdated comments; # XXX: might contain non-API methods and outdated comments;
# not synced with ZopeBook API Reference; # not synced with ZopeBook API Reference;
# based on OFS.Traversable.Traversable # based on OFS.Traversable.Traversable
...@@ -519,7 +499,7 @@ class ILockItem(Interface): ...@@ -519,7 +499,7 @@ class ILockItem(Interface):
# XXX: might contain non-API methods and outdated comments; # XXX: might contain non-API methods and outdated comments;
# not synced with ZopeBook API Reference; # not synced with ZopeBook API Reference;
# based on OFS.SimpleItem.Item # based on OFS.SimpleItem.Item
class IItem(IZopeObject, IManageable, IFTPAccess, class IItem(IZopeObject, IManageable,
ICopySource, ITraversable, IOwned): ICopySource, ITraversable, IOwned):
__name__ = BytesLine(title=u"Name") __name__ = BytesLine(title=u"Name")
...@@ -1057,3 +1037,10 @@ class IObjectClonedEvent(IObjectEvent): ...@@ -1057,3 +1037,10 @@ class IObjectClonedEvent(IObjectEvent):
event.object is the copied object, already added to its container. event.object is the copied object, already added to its container.
Note that this event is dispatched to all sublocations. Note that this event is dispatched to all sublocations.
""" """
# BBB Zope 5.0
deprecated(
'Please import from webdav.interfaces.',
IFTPAccess='webdav.interfaces:IFTPAccess',
)
import unittest
class TestFTPInterface(unittest.TestCase):
def test_interfaces(self):
from OFS.interfaces import IFTPAccess
from OFS.FTPInterface import FTPInterface
from zope.interface.verify import verifyClass
verifyClass(IFTPAccess, FTPInterface)
...@@ -257,27 +257,6 @@ class FileTests(unittest.TestCase): ...@@ -257,27 +257,6 @@ class FileTests(unittest.TestCase):
self.assertEqual(resp.getStatus(), 200) self.assertEqual(resp.getStatus(), 200)
self.assertEqual(data, str(self.file.data)) self.assertEqual(data, str(self.file.data))
def testPUT(self):
s = '# some python\n'
# with content type
data = StringIO(s)
req = aputrequest(data, 'text/x-python')
req.processInputs()
self.file.PUT(req, req.RESPONSE)
self.assertEqual(self.file.content_type, 'text/x-python')
self.assertEqual(str(self.file.data), s)
# without content type
data.seek(0)
req = aputrequest(data, '')
req.processInputs()
self.file.PUT(req, req.RESPONSE)
self.assertEqual(self.file.content_type, 'text/x-python')
self.assertEqual(str(self.file.data), s)
def testIndexHtmlWithPdata(self): def testIndexHtmlWithPdata(self):
self.file.manage_upload('a' * (2 << 16)) # 128K self.file.manage_upload('a' * (2 << 16)) # 128K
self.file.index_html(self.app.REQUEST, self.app.REQUEST.RESPONSE) self.file.index_html(self.app.REQUEST, self.app.REQUEST.RESPONSE)
......
...@@ -37,6 +37,7 @@ from Shared.DC.Scripts.Script import Script ...@@ -37,6 +37,7 @@ from Shared.DC.Scripts.Script import Script
from Shared.DC.Scripts.Signature import FuncCode from Shared.DC.Scripts.Signature import FuncCode
from zExceptions import ResourceLockedError from zExceptions import ResourceLockedError
from Products.PageTemplates import bbb
from Products.PageTemplates.PageTemplate import PageTemplate from Products.PageTemplates.PageTemplate import PageTemplate
from Products.PageTemplates.PageTemplateFile import PageTemplateFile from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PageTemplates.PageTemplateFile import guess_type from Products.PageTemplates.PageTemplateFile import guess_type
...@@ -344,6 +345,7 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable, ...@@ -344,6 +345,7 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
'manage_beforeHistoryCopy', 'manage_beforeHistoryCopy',
'manage_afterHistoryCopy') 'manage_afterHistoryCopy')
if bbb.HAS_ZSERVER:
security.declareProtected(change_page_templates, 'PUT') security.declareProtected(change_page_templates, 'PUT')
def PUT(self, REQUEST, RESPONSE): def PUT(self, REQUEST, RESPONSE):
""" Handle HTTP PUT requests """ """ Handle HTTP PUT requests """
...@@ -359,7 +361,8 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable, ...@@ -359,7 +361,8 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
security.declareProtected(change_page_templates, 'manage_FTPput') security.declareProtected(change_page_templates, 'manage_FTPput')
manage_FTPput = PUT manage_FTPput = PUT
security.declareProtected(ftp_access, 'manage_FTPstat', 'manage_FTPlist') security.declareProtected(ftp_access, 'manage_FTPstat')
security.declareProtected(ftp_access, 'manage_FTPlist')
security.declareProtected(ftp_access, 'manage_FTPget') security.declareProtected(ftp_access, 'manage_FTPget')
def manage_FTPget(self): def manage_FTPget(self):
"Get source for FTP download" "Get source for FTP download"
......
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import pkg_resources
HAS_ZSERVER = True
try:
dist = pkg_resources.get_distribution('ZServer')
except pkg_resources.DistributionNotFound:
HAS_ZSERVER = False
...@@ -15,6 +15,7 @@ from zope.publisher.http import HTTPCharsets ...@@ -15,6 +15,7 @@ from zope.publisher.http import HTTPCharsets
from Testing.makerequest import makerequest from Testing.makerequest import makerequest
from Testing.ZopeTestCase import ZopeTestCase, installProduct from Testing.ZopeTestCase import ZopeTestCase, installProduct
from Products.PageTemplates.PageTemplateFile import guess_type
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
from Products.PageTemplates.ZopePageTemplate import manage_addPageTemplate from Products.PageTemplates.ZopePageTemplate import manage_addPageTemplate
from Products.PageTemplates.utils import encodingFromXMLPreamble from Products.PageTemplates.utils import encodingFromXMLPreamble
...@@ -308,9 +309,8 @@ class ZopePageTemplateFileTests(ZopeTestCase): ...@@ -308,9 +309,8 @@ class ZopePageTemplateFileTests(ZopeTestCase):
def _put(self, text): def _put(self, text):
zpt = self._createZPT() zpt = self._createZPT()
REQUEST = self.app.REQUEST content_type = guess_type('', text)
REQUEST.set('BODY', text) zpt.pt_edit(text, content_type)
zpt.PUT(REQUEST, REQUEST.RESPONSE)
return zpt return zpt
def testPutHTMLIso8859_15WithCharsetInfo(self): def testPutHTMLIso8859_15WithCharsetInfo(self):
...@@ -417,14 +417,6 @@ class ZPTRegressions(unittest.TestCase): ...@@ -417,14 +417,6 @@ class ZPTRegressions(unittest.TestCase):
pt = self.app.pt1 pt = self.app.pt1
self.assertEqual(pt.document_src(), self.text) self.assertEqual(pt.document_src(), self.text)
def testFTPGet(self):
# check for bug #2269
request = self.app.REQUEST
text = '<span tal:content="string:foobar"></span>'
self._addPT('pt1', text=text, REQUEST=request)
result = self.app.pt1.manage_FTPget()
self.assertEqual(result, text)
class ZPTMacros(zope.component.testing.PlacelessSetup, unittest.TestCase): class ZPTMacros(zope.component.testing.PlacelessSetup, unittest.TestCase):
......
...@@ -120,18 +120,6 @@ class TestFunctional(ZopeTestCase.FunctionalTestCase): ...@@ -120,18 +120,6 @@ class TestFunctional(ZopeTestCase.FunctionalTestCase):
self.assertEqual(response.getStatus(), 200) self.assertEqual(response.getStatus(), 200)
self.assertEqual(self.folder.index_html.title_or_id(), 'Foo') self.assertEqual(self.folder.index_html.title_or_id(), 'Foo')
def testPUTExisting(self):
# FTP new data into an existing object
self.setPermissions([change_dtml_documents])
put_data = StringIO('foo')
response = self.publish(self.folder_path + '/index_html',
request_method='PUT', stdin=put_data,
basic=self.basic_auth)
self.assertEqual(response.getStatus(), 204)
self.assertEqual(self.folder.index_html(), 'foo')
def testHEAD(self): def testHEAD(self):
# HEAD should work without passing stdin # HEAD should work without passing stdin
response = self.publish(self.folder_path + '/index_html', response = self.publish(self.folder_path + '/index_html',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment