Commit 35fe223b authored by Hanno Schlichting's avatar Hanno Schlichting

Make OFS independent of webdav resource classes.

parent 992f73e2
......@@ -29,7 +29,6 @@ from DateTime import DateTime
from OFS.metaconfigure import get_packages_to_initialize
from OFS.metaconfigure import package_initialized
from OFS.userfolder import UserFolder
from webdav.NullResource import NullResource
from zExceptions import Redirect as RedirectException, Forbidden
from zope.interface import implements
......@@ -40,6 +39,11 @@ from FindSupport import FindSupport
from interfaces import IApplication
from misc_ import Misc_
try:
from webdav.NullResource import NullResource
except ImportError:
NullResource = None
LOG = getLogger('Application')
APP_MANAGER = None
......@@ -124,7 +128,7 @@ class Application(ApplicationDefaultPermissions,
pass
method = REQUEST.get('REQUEST_METHOD', 'GET')
if not method in ('GET', 'POST'):
if NullResource is not None and method not in ('GET', 'POST'):
return NullResource(self, name, REQUEST).__of__(self)
# Waaa. unrestrictedTraverse calls us with a fake REQUEST.
......
......@@ -17,7 +17,6 @@ Folders are the basic container objects and are analogous to directories.
from AccessControl.class_init import InitializeClass
from App.special_dtml import DTMLFile
from webdav.Collection import Collection
from zope.interface import implements
from OFS.FindSupport import FindSupport
......@@ -27,6 +26,12 @@ from OFS.PropertyManager import PropertyManager
from OFS.role import RoleManager
from OFS.SimpleItem import Item
try:
from webdav.Collection import Collection
except ImportError:
class Collection(object):
pass
manage_addFolderForm=DTMLFile('dtml/folderAdd', globals())
......
......@@ -46,8 +46,6 @@ from App.Management import Tabs
from App.special_dtml import DTMLFile
from DateTime import DateTime
from Persistence import Persistent
from webdav.Collection import Collection
from webdav.NullResource import NullResource
from zExceptions import BadRequest, ResourceLockedError
from zope.container.contained import notifyContainerModified
from zope.event import notify
......@@ -66,6 +64,16 @@ from OFS.XMLExportImport import importXML
from OFS.XMLExportImport import exportXML
from OFS.XMLExportImport import magic
try:
from webdav.Collection import Collection
from webdav.NullResource import NullResource
except ImportError:
NullResource = None
class Collection(object):
pass
# Constants: __replaceable__ flags:
NOT_REPLACEABLE = 0
REPLACEABLE = 1
......@@ -769,9 +777,10 @@ class ObjectManager(CopyContainer,
return self._getOb(key, None)
request = getattr(self, 'REQUEST', None)
if not isinstance(request, (str, NoneType)):
method=request.get('REQUEST_METHOD', 'GET')
if (request.maybe_webdav_client and
method not in ('GET', 'POST')):
method = request.get('REQUEST_METHOD', 'GET')
if (NullResource is not None and
request.maybe_webdav_client and
method not in ('GET', 'POST')):
return NullResource(self, key, request).__of__(self)
raise KeyError(key)
......
This diff is collapsed.
......@@ -47,7 +47,6 @@ from DocumentTemplate.html_quote import html_quote
from DocumentTemplate.ustr import ustr
from ExtensionClass import Base
from Persistence import Persistent
from webdav.Resource import Resource
from zExceptions import Redirect
from zExceptions.ExceptionFormatter import format_exception
from zope.interface import implements
......@@ -60,6 +59,12 @@ from OFS.CopySupport import CopySource
from OFS.role import RoleManager
from OFS.Traversable import Traversable
try:
from webdav.Resource import Resource
except ImportError:
class Resource(object):
pass
logger = logging.getLogger()
......
......@@ -195,7 +195,11 @@ class Traversable:
obj = self
# import time ordering problem
from webdav.NullResource import NullResource
try:
from webdav.NullResource import NullResource
except ImportError:
NullResource = None
resource = _marker
try:
while path:
......@@ -277,10 +281,12 @@ class Traversable:
else:
try:
next = obj[name]
# The item lookup may return a NullResource,
# if this is the case we save it and return it
# if all other lookups fail.
if isinstance(next, NullResource):
# The item lookup may return a
# NullResource, if this is the case we
# save it and return it if all other
# lookups fail.
if (NullResource is not None and
isinstance(next, NullResource)):
resource = next
raise KeyError(name)
except (AttributeError, TypeError):
......
......@@ -27,8 +27,16 @@ from Acquisition.interfaces import IAcquirer
from App.interfaces import INavigation
from App.interfaces import IUndoSupport
from persistent.interfaces import IPersistent
from webdav.interfaces import IDAVCollection
from webdav.interfaces import IDAVResource
try:
from webdav.interfaces import IDAVCollection
from webdav.interfaces import IDAVResource
except ImportError:
class IDAVCollection(Interface):
pass
class IDAVResource(Interface):
pass
class IOrderedContainer(Interface):
......
import unittest
try:
from webdav.NullResource import NullResource
except ImportError:
NullResource = None
class ApplicationTests(unittest.TestCase):
......@@ -90,18 +95,19 @@ class ApplicationTests(unittest.TestCase):
request = {'REQUEST_METHOD': 'GET'}
self.assertRaises(KeyError, app.__bobo_traverse__, request, 'NONESUCH')
def test___bobo_traverse__attribute_key_miss_R_M_not_GET_POST(self):
from Acquisition import aq_inner, aq_parent
from webdav.NullResource import NullResource
if NullResource is not None:
def test___bobo_traverse__attribute_key_miss_R_M_not_GET_POST(self):
from Acquisition import aq_inner, aq_parent
app = self._makeOne()
app._getOb = _noWay
request = {'REQUEST_METHOD': 'GOOFY'}
app = self._makeOne()
app._getOb = _noWay
request = {'REQUEST_METHOD': 'GOOFY'}
result = app.__bobo_traverse__(request, 'OTHER')
result = app.__bobo_traverse__(request, 'OTHER')
self.assertTrue(isinstance(result, NullResource))
self.assertTrue(aq_parent(aq_inner(result)) is app)
self.assertTrue(isinstance(result, NullResource))
self.assertTrue(aq_parent(aq_inner(result)) is app)
def _noWay(self, key, default=None):
raise KeyError(key)
......@@ -104,56 +104,5 @@ class TestPropertySheet(unittest.TestCase):
self.assertTrue(isinstance(inst.prop, tuple))
inst.manage_addProperty('prop2', ['xxx', 'yyy'], 'lines')
self.assertTrue(type(inst.getProperty('prop2')) == type(()))
self.assertTrue(type(inst.prop2) == type(()))
def test_dav__propstat_nullns(self):
# Tests 15 (propnullns) and 16 (propget) from the props suite
# of litmus version 10.5 (http://www.webdav.org/neon/litmus/)
# expose a bug in Zope propertysheet access via DAV. If a
# proppatch command sets a property with a null xmlns,
# e.g. with a PROPPATCH body like:
#
# <?xml version="1.0" encoding="utf-8" ?>
# <propertyupdate xmlns="DAV:">
# <set>
# <prop>
# <nonamespace xmlns="">randomvalue</nonamespace>
# </prop>
# </set>
# </propertyupdate>
#
# When we set properties in the null namespace, Zope turns
# around and creates (or finds) a propertysheet with the
# xml_namespace of None and sets the value on it. The
# response to a subsequent PROPFIND for the resource will fail
# because the XML generated by dav__propstat included a bogus
# namespace declaration (xmlns="None").
#
inst = self._makeOne('foo')
inst._md = {'xmlns':None}
resultd = {}
inst._setProperty('foo', 'bar')
inst.dav__propstat('foo', resultd)
self.assertEqual(len(resultd['200 OK']), 1)
self.assertEqual(resultd['200 OK'][0], '<foo xmlns="">bar</foo>\n')
def test_dav__propstat_notnullns(self):
# see test_dav__propstat_nullns
inst = self._makeOne('foo')
inst._md = {'xmlns':'http://www.example.com/props'}
resultd = {}
inst._setProperty('foo', 'bar')
inst.dav__propstat('foo', resultd)
self.assertEqual(len(resultd['200 OK']), 1)
self.assertEqual(resultd['200 OK'][0],
'<n:foo xmlns:n="http://www.example.com/props">bar'
'</n:foo>\n')
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(TestPropertyManager),
unittest.makeSuite(TestPropertySheet),
))
self.assertTrue(isinstance(inst.getProperty('prop2'), tuple))
self.assertTrue(isinstance(inst.prop2, tuple))
......@@ -435,6 +435,12 @@ class BaseRequest:
# Set the posttraverse for duration of the traversal here
self._post_traverse = post_traverse = []
# import time ordering problem
try:
from webdav.NullResource import NullResource
except ImportError:
NullResource = None
entry_name = ''
try:
# We build parents in the wrong order, so we
......@@ -459,11 +465,11 @@ class BaseRequest:
# This is webdav support. The last object in the path
# should not be acquired. Instead, a NullResource should
# be given if it doesn't exist:
if (no_acquire_flag and
hasattr(object, 'aq_base') and
not hasattr(object,'__bobo_traverse__')):
if (NullResource is not None and no_acquire_flag and
hasattr(object, 'aq_base') and
not hasattr(object, '__bobo_traverse__')):
if object.aq_parent is not object.aq_inner.aq_parent:
from webdav.NullResource import NullResource
object = NullResource(parents[-2], object.getId(),
self).__of__(parents[-2])
......
##############################################################################
#
# 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 sys
from webdav.xmltools import escape
if sys.version_info >= (3, 0):
basestring = str
unicode = str
def xml_escape(value):
if not isinstance(value, basestring):
value = unicode(value)
if not isinstance(value, unicode):
value = value.decode('utf-8')
value = escape(value)
return value.encode('utf-8')
class DAVPropertySheetMixin(object):
propstat = ('<d:propstat xmlns:n="%s">\n'
' <d:prop>\n'
'%s\n'
' </d:prop>\n'
' <d:status>HTTP/1.1 %s</d:status>\n%s'
'</d:propstat>\n')
propdesc = (' <d:responsedescription>\n'
' %s\n'
' </d:responsedescription>\n')
def dav__allprop(self, propstat=propstat):
# DAV helper method - return one or more propstat elements
# indicating property names and values for all properties.
result = []
for item in self._propertyMap():
name, type = item['id'], item.get('type', 'string')
value = self.getProperty(name)
if type == 'tokens':
value = ' '.join(map(str, value))
elif type == 'lines':
value = '\n'.join(map(str, value))
# check for xml property
attrs = item.get('meta', {}).get('__xml_attrs__', None)
if attrs is not None:
# It's a xml property. Don't escape value.
attrs = ''.join(' %s="%s"' % n for n in attrs.items())
else:
# It's a non-xml property. Escape value.
attrs = ''
if not hasattr(self, "dav__" + name):
value = xml_escape(value)
prop = ' <n:%s%s>%s</n:%s>' % (name, attrs, value, name)
result.append(prop)
if not result:
return ''
result = '\n'.join(result)
return propstat % (self.xml_namespace(), result, '200 OK', '')
def dav__propnames(self, propstat=propstat):
# DAV helper method - return a propstat element indicating
# property names for all properties in this PropertySheet.
result = []
for name in self.propertyIds():
result.append(' <n:%s/>' % name)
if not result:
return ''
result = '\n'.join(result)
return propstat % (self.xml_namespace(), result, '200 OK', '')
def dav__propstat(self, name, result,
propstat=propstat, propdesc=propdesc):
# DAV helper method - return a propstat element indicating
# property name and value for the requested property.
xml_id = self.xml_namespace()
propdict = self._propdict()
if name not in propdict:
if xml_id:
prop = '<n:%s xmlns:n="%s"/>\n' % (name, xml_id)
else:
prop = '<%s xmlns=""/>\n' % name
code = '404 Not Found'
if code not in result:
result[code] = [prop]
else:
result[code].append(prop)
return
else:
item = propdict[name]
name, type = item['id'], item.get('type', 'string')
value = self.getProperty(name)
if type == 'tokens':
value = ' '.join(map(str, value))
elif type == 'lines':
value = '\n'.join(map(str, value))
# allow for xml properties
attrs = item.get('meta', {}).get('__xml_attrs__', None)
if attrs is not None:
# It's a xml property. Don't escape value.
attrs = ''.join(' %s="%s"' % n for n in attrs.items())
else:
# It's a non-xml property. Escape value.
attrs = ''
if not hasattr(self, 'dav__%s' % name):
value = xml_escape(value)
if xml_id:
prop = '<n:%s%s xmlns:n="%s">%s</n:%s>\n' % (
name, attrs, xml_id, value, name)
else:
prop = '<%s%s xmlns="">%s</%s>\n' % (
name, attrs, value, name)
code = '200 OK'
if code not in result:
result[code] = [prop]
else:
result[code].append(prop)
return
del propstat
del propdesc
##############################################################################
#
# 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.
#
##############################################################################
from cgi import escape
import sys
from AccessControl.class_init import InitializeClass
from AccessControl.SecurityManagement import getSecurityManager
from App.Common import iso8601_date
from App.Common import rfc1123_date
from OFS.PropertySheets import Virtual, PropertySheet, View
from webdav.common import isDavCollection
from webdav.common import urlbase
from webdav.interfaces import IWriteLock
if sys.version_info >= (3, 0):
basestring = str
unicode = str
def absattr(attr):
if callable(attr):
return attr()
return attr
def xml_escape(value):
from webdav.xmltools import escape
if not isinstance(value, basestring):
value = unicode(value)
if not isinstance(value, unicode):
value = value.decode('utf-8')
value = escape(value)
return value.encode('utf-8')
class DAVProperties(Virtual, PropertySheet, View):
"""WebDAV properties"""
id = 'webdav'
_md = {'xmlns': 'DAV:'}
pm = ({'id': 'creationdate', 'mode': 'r'},
{'id': 'displayname', 'mode': 'r'},
{'id': 'resourcetype', 'mode': 'r'},
{'id': 'getcontenttype', 'mode': 'r'},
{'id': 'getcontentlength', 'mode': 'r'},
{'id': 'source', 'mode': 'r'},
{'id': 'supportedlock', 'mode': 'r'},
{'id': 'lockdiscovery', 'mode': 'r'},
)
def getProperty(self, id, default=None):
method = 'dav__%s' % id
if not hasattr(self, method):
return default
return getattr(self, method)()
def _setProperty(self, id, value, type='string', meta=None):
raise ValueError('%s cannot be set.' % escape(id))
def _updateProperty(self, id, value):
raise ValueError('%s cannot be updated.' % escape(id))
def _delProperty(self, id):
raise ValueError('%s cannot be deleted.' % escape(id))
def _propertyMap(self):
# Only use getlastmodified if returns a value
if hasattr(self.v_self(), '_p_mtime'):
return self.pm + ({'id': 'getlastmodified', 'mode': 'r'},)
return self.pm
def propertyMap(self):
return [dict.copy() for dict in self._propertyMap()]
def dav__creationdate(self):
return iso8601_date(43200.0)
def dav__displayname(self):
return absattr(xml_escape(self.v_self().title_or_id()))
def dav__resourcetype(self):
vself = self.v_self()
if isDavCollection(vself):
return '<n:collection/>'
return ''
def dav__getlastmodified(self):
return rfc1123_date(self.v_self()._p_mtime)
def dav__getcontenttype(self):
vself = self.v_self()
if hasattr(vself, 'content_type'):
return absattr(vself.content_type)
if hasattr(vself, 'default_content_type'):
return absattr(vself.default_content_type)
return ''
def dav__getcontentlength(self):
vself = self.v_self()
if hasattr(vself, 'get_size'):
return vself.get_size()
return ''
def dav__source(self):
vself = self.v_self()
if hasattr(vself, 'document_src'):
url = urlbase(vself.absolute_url())
return '\n <n:link>\n' \
' <n:src>%s</n:src>\n' \
' <n:dst>%s/document_src</n:dst>\n' \
' </n:link>\n ' % (url, url)
return ''
def dav__supportedlock(self):
vself = self.v_self()
out = '\n'
if IWriteLock.providedBy(vself):
out += (' <n:lockentry>\n'
' <d:lockscope><d:exclusive/></d:lockscope>\n'
' <d:locktype><d:write/></d:locktype>\n'
' </n:lockentry>\n ')
return out
def dav__lockdiscovery(self):
security = getSecurityManager()
user = security.getUser().getId()
vself = self.v_self()
out = '\n'
if IWriteLock.providedBy(vself):
locks = vself.wl_lockValues(killinvalids=1)
for lock in locks:
creator = lock.getCreator()[-1]
if creator == user:
fake = 0
else:
fake = 1
out = '%s\n%s' % (
out, lock.asLockDiscoveryProperty('n', fake=fake))
out = '%s\n' % out
return out
InitializeClass(DAVProperties)
##############################################################################
#
# 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 unittest
class TestPropertySheet(unittest.TestCase):
def _makeOne(self, *args, **kw):
from OFS.PropertySheets import PropertySheet
return PropertySheet(*args, **kw)
def test_dav__propstat_nullns(self):
# Tests 15 (propnullns) and 16 (propget) from the props suite
# of litmus version 10.5 (http://www.webdav.org/neon/litmus/)
# expose a bug in Zope propertysheet access via DAV. If a
# proppatch command sets a property with a null xmlns,
# e.g. with a PROPPATCH body like:
#
# <?xml version="1.0" encoding="utf-8" ?>
# <propertyupdate xmlns="DAV:">
# <set>
# <prop>
# <nonamespace xmlns="">randomvalue</nonamespace>
# </prop>
# </set>
# </propertyupdate>
#
# When we set properties in the null namespace, Zope turns
# around and creates (or finds) a propertysheet with the
# xml_namespace of None and sets the value on it. The
# response to a subsequent PROPFIND for the resource will fail
# because the XML generated by dav__propstat included a bogus
# namespace declaration (xmlns="None").
#
inst = self._makeOne('foo')
inst._md = {'xmlns': None}
resultd = {}
inst._setProperty('foo', 'bar')
inst.dav__propstat('foo', resultd)
self.assertEqual(len(resultd['200 OK']), 1)
self.assertEqual(resultd['200 OK'][0], '<foo xmlns="">bar</foo>\n')
def test_dav__propstat_notnullns(self):
# see test_dav__propstat_nullns
inst = self._makeOne('foo')
inst._md = {'xmlns': 'http://www.example.com/props'}
resultd = {}
inst._setProperty('foo', 'bar')
inst.dav__propstat('foo', resultd)
self.assertEqual(len(resultd['200 OK']), 1)
self.assertEqual(resultd['200 OK'][0],
'<n:foo xmlns:n="http://www.example.com/props">bar'
'</n:foo>\n')
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