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 ...@@ -29,7 +29,6 @@ from DateTime import DateTime
from OFS.metaconfigure import get_packages_to_initialize from OFS.metaconfigure import get_packages_to_initialize
from OFS.metaconfigure import package_initialized from OFS.metaconfigure import package_initialized
from OFS.userfolder import UserFolder from OFS.userfolder import UserFolder
from webdav.NullResource import NullResource
from zExceptions import Redirect as RedirectException, Forbidden from zExceptions import Redirect as RedirectException, Forbidden
from zope.interface import implements from zope.interface import implements
...@@ -40,6 +39,11 @@ from FindSupport import FindSupport ...@@ -40,6 +39,11 @@ from FindSupport import FindSupport
from interfaces import IApplication from interfaces import IApplication
from misc_ import Misc_ from misc_ import Misc_
try:
from webdav.NullResource import NullResource
except ImportError:
NullResource = None
LOG = getLogger('Application') LOG = getLogger('Application')
APP_MANAGER = None APP_MANAGER = None
...@@ -124,7 +128,7 @@ class Application(ApplicationDefaultPermissions, ...@@ -124,7 +128,7 @@ class Application(ApplicationDefaultPermissions,
pass pass
method = REQUEST.get('REQUEST_METHOD', 'GET') 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) return NullResource(self, name, REQUEST).__of__(self)
# Waaa. unrestrictedTraverse calls us with a fake REQUEST. # Waaa. unrestrictedTraverse calls us with a fake REQUEST.
......
...@@ -17,7 +17,6 @@ Folders are the basic container objects and are analogous to directories. ...@@ -17,7 +17,6 @@ Folders are the basic container objects and are analogous to directories.
from AccessControl.class_init import InitializeClass from AccessControl.class_init import InitializeClass
from App.special_dtml import DTMLFile from App.special_dtml import DTMLFile
from webdav.Collection import Collection
from zope.interface import implements from zope.interface import implements
from OFS.FindSupport import FindSupport from OFS.FindSupport import FindSupport
...@@ -27,6 +26,12 @@ from OFS.PropertyManager import PropertyManager ...@@ -27,6 +26,12 @@ from OFS.PropertyManager import PropertyManager
from OFS.role import RoleManager from OFS.role import RoleManager
from OFS.SimpleItem import Item from OFS.SimpleItem import Item
try:
from webdav.Collection import Collection
except ImportError:
class Collection(object):
pass
manage_addFolderForm=DTMLFile('dtml/folderAdd', globals()) manage_addFolderForm=DTMLFile('dtml/folderAdd', globals())
......
...@@ -46,8 +46,6 @@ from App.Management import Tabs ...@@ -46,8 +46,6 @@ from App.Management import Tabs
from App.special_dtml import DTMLFile from App.special_dtml import DTMLFile
from DateTime import DateTime from DateTime import DateTime
from Persistence import Persistent from Persistence import Persistent
from webdav.Collection import Collection
from webdav.NullResource import NullResource
from zExceptions import BadRequest, ResourceLockedError from zExceptions import BadRequest, ResourceLockedError
from zope.container.contained import notifyContainerModified from zope.container.contained import notifyContainerModified
from zope.event import notify from zope.event import notify
...@@ -66,6 +64,16 @@ from OFS.XMLExportImport import importXML ...@@ -66,6 +64,16 @@ from OFS.XMLExportImport import importXML
from OFS.XMLExportImport import exportXML from OFS.XMLExportImport import exportXML
from OFS.XMLExportImport import magic 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: # Constants: __replaceable__ flags:
NOT_REPLACEABLE = 0 NOT_REPLACEABLE = 0
REPLACEABLE = 1 REPLACEABLE = 1
...@@ -769,8 +777,9 @@ class ObjectManager(CopyContainer, ...@@ -769,8 +777,9 @@ class ObjectManager(CopyContainer,
return self._getOb(key, None) return self._getOb(key, None)
request = getattr(self, 'REQUEST', None) request = getattr(self, 'REQUEST', None)
if not isinstance(request, (str, NoneType)): if not isinstance(request, (str, NoneType)):
method=request.get('REQUEST_METHOD', 'GET') method = request.get('REQUEST_METHOD', 'GET')
if (request.maybe_webdav_client and if (NullResource is not None and
request.maybe_webdav_client and
method not in ('GET', 'POST')): method not in ('GET', 'POST')):
return NullResource(self, key, request).__of__(self) return NullResource(self, key, request).__of__(self)
raise KeyError(key) raise KeyError(key)
......
...@@ -21,25 +21,35 @@ from AccessControl.Permissions import access_contents_information ...@@ -21,25 +21,35 @@ from AccessControl.Permissions import access_contents_information
from AccessControl.Permissions import manage_properties from AccessControl.Permissions import manage_properties
from AccessControl.Permissions import view_management_screens from AccessControl.Permissions import view_management_screens
from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityInfo import ClassSecurityInfo
from AccessControl.SecurityManagement import getSecurityManager
from Acquisition import aq_base from Acquisition import aq_base
from Acquisition import aq_parent from Acquisition import aq_parent
from Acquisition import Implicit from Acquisition import Implicit
from App.Common import iso8601_date
from App.Common import rfc1123_date
from App.Dialogs import MessageDialog from App.Dialogs import MessageDialog
from App.Management import Tabs from App.Management import Tabs
from App.special_dtml import DTMLFile from App.special_dtml import DTMLFile
from ExtensionClass import Base from ExtensionClass import Base
from Persistence import Persistent from Persistence import Persistent
from Traversable import Traversable from Traversable import Traversable
from webdav.common import isDavCollection
from webdav.common import urlbase
from webdav.interfaces import IWriteLock
from zExceptions import BadRequest from zExceptions import BadRequest
from zExceptions import Redirect from zExceptions import Redirect
from ZPublisher.Converters import type_converters from ZPublisher.Converters import type_converters
try:
from webdav.PropertySheet import DAVPropertySheetMixin
except ImportError:
class DAVPropertySheetMixin(object):
pass
class Virtual:
"""A virtual propertysheet stores it's properties in it's instance."""
def __init__(self):
pass
def v_self(self):
return aq_parent(aq_parent(self))
class View(Tabs, Base): class View(Tabs, Base):
"""A view of an object, typically used for management purposes """A view of an object, typically used for management purposes
...@@ -101,7 +111,7 @@ class View(Tabs, Base): ...@@ -101,7 +111,7 @@ class View(Tabs, Base):
return '' return ''
class PropertySheet(Traversable, Persistent, Implicit): class PropertySheet(Traversable, Persistent, Implicit, DAVPropertySheetMixin):
"""A PropertySheet is a container for a set of related properties and """A PropertySheet is a container for a set of related properties and
metadata describing those properties. PropertySheets may or may not metadata describing those properties. PropertySheets may or may not
provide a web interface for managing its properties.""" provide a web interface for managing its properties."""
...@@ -309,110 +319,6 @@ class PropertySheet(Traversable, Persistent, Implicit): ...@@ -309,110 +319,6 @@ class PropertySheet(Traversable, Persistent, Implicit):
dict[p['id']] = p dict[p['id']] = p
return dict return dict
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 not result.has_key(code):
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 not result.has_key(code):
result[code]=[prop]
else: result[code].append(prop)
return
del propstat
del propdesc
# Web interface
manage = DTMLFile('dtml/properties', globals()) manage = DTMLFile('dtml/properties', globals())
security.declareProtected(manage_properties, 'manage_propertiesForm') security.declareProtected(manage_properties, 'manage_propertiesForm')
...@@ -490,16 +396,6 @@ class PropertySheet(Traversable, Persistent, Implicit): ...@@ -490,16 +396,6 @@ class PropertySheet(Traversable, Persistent, Implicit):
InitializeClass(PropertySheet) InitializeClass(PropertySheet)
class Virtual:
"""A virtual propertysheet stores it's properties in it's instance."""
def __init__(self):
pass
def v_self(self):
return aq_parent(aq_parent(self))
class DefaultProperties(Virtual, PropertySheet, View): class DefaultProperties(Virtual, PropertySheet, View):
"""The default property set mimics the behavior of old-style Zope """The default property set mimics the behavior of old-style Zope
properties -- it stores its property values in the instance of properties -- it stores its property values in the instance of
...@@ -511,115 +407,12 @@ class DefaultProperties(Virtual, PropertySheet, View): ...@@ -511,115 +407,12 @@ class DefaultProperties(Virtual, PropertySheet, View):
InitializeClass(DefaultProperties) InitializeClass(DefaultProperties)
class DAVProperties(Virtual, PropertySheet, View): # import cycles
"""WebDAV properties""" try:
from webdav.PropertySheets import DAVProperties
id='webdav' except ImportError:
_md={'xmlns': 'DAV:'} class DAVProperties(object):
pm=({'id':'creationdate', 'mode':'r'}, pass
{'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)
class PropertySheets(Traversable, Implicit, Tabs): class PropertySheets(Traversable, Implicit, Tabs):
...@@ -793,17 +586,3 @@ class vps(Base): ...@@ -793,17 +586,3 @@ class vps(Base):
def __of__(self, parent): def __of__(self, parent):
return self.c().__of__(parent) return self.c().__of__(parent)
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')
...@@ -47,7 +47,6 @@ from DocumentTemplate.html_quote import html_quote ...@@ -47,7 +47,6 @@ from DocumentTemplate.html_quote import html_quote
from DocumentTemplate.ustr import ustr from DocumentTemplate.ustr import ustr
from ExtensionClass import Base from ExtensionClass import Base
from Persistence import Persistent from Persistence import Persistent
from webdav.Resource import Resource
from zExceptions import Redirect from zExceptions import Redirect
from zExceptions.ExceptionFormatter import format_exception from zExceptions.ExceptionFormatter import format_exception
from zope.interface import implements from zope.interface import implements
...@@ -60,6 +59,12 @@ from OFS.CopySupport import CopySource ...@@ -60,6 +59,12 @@ from OFS.CopySupport import CopySource
from OFS.role import RoleManager from OFS.role import RoleManager
from OFS.Traversable import Traversable from OFS.Traversable import Traversable
try:
from webdav.Resource import Resource
except ImportError:
class Resource(object):
pass
logger = logging.getLogger() logger = logging.getLogger()
......
...@@ -195,7 +195,11 @@ class Traversable: ...@@ -195,7 +195,11 @@ class Traversable:
obj = self obj = self
# import time ordering problem # import time ordering problem
try:
from webdav.NullResource import NullResource from webdav.NullResource import NullResource
except ImportError:
NullResource = None
resource = _marker resource = _marker
try: try:
while path: while path:
...@@ -277,10 +281,12 @@ class Traversable: ...@@ -277,10 +281,12 @@ class Traversable:
else: else:
try: try:
next = obj[name] next = obj[name]
# The item lookup may return a NullResource, # The item lookup may return a
# if this is the case we save it and return it # NullResource, if this is the case we
# if all other lookups fail. # save it and return it if all other
if isinstance(next, NullResource): # lookups fail.
if (NullResource is not None and
isinstance(next, NullResource)):
resource = next resource = next
raise KeyError(name) raise KeyError(name)
except (AttributeError, TypeError): except (AttributeError, TypeError):
......
...@@ -27,8 +27,16 @@ from Acquisition.interfaces import IAcquirer ...@@ -27,8 +27,16 @@ from Acquisition.interfaces import IAcquirer
from App.interfaces import INavigation from App.interfaces import INavigation
from App.interfaces import IUndoSupport from App.interfaces import IUndoSupport
from persistent.interfaces import IPersistent 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): class IOrderedContainer(Interface):
......
import unittest import unittest
try:
from webdav.NullResource import NullResource
except ImportError:
NullResource = None
class ApplicationTests(unittest.TestCase): class ApplicationTests(unittest.TestCase):
...@@ -90,9 +95,9 @@ class ApplicationTests(unittest.TestCase): ...@@ -90,9 +95,9 @@ class ApplicationTests(unittest.TestCase):
request = {'REQUEST_METHOD': 'GET'} request = {'REQUEST_METHOD': 'GET'}
self.assertRaises(KeyError, app.__bobo_traverse__, request, 'NONESUCH') self.assertRaises(KeyError, app.__bobo_traverse__, request, 'NONESUCH')
if NullResource is not None:
def test___bobo_traverse__attribute_key_miss_R_M_not_GET_POST(self): def test___bobo_traverse__attribute_key_miss_R_M_not_GET_POST(self):
from Acquisition import aq_inner, aq_parent from Acquisition import aq_inner, aq_parent
from webdav.NullResource import NullResource
app = self._makeOne() app = self._makeOne()
app._getOb = _noWay app._getOb = _noWay
...@@ -103,5 +108,6 @@ class ApplicationTests(unittest.TestCase): ...@@ -103,5 +108,6 @@ class ApplicationTests(unittest.TestCase):
self.assertTrue(isinstance(result, NullResource)) self.assertTrue(isinstance(result, NullResource))
self.assertTrue(aq_parent(aq_inner(result)) is app) self.assertTrue(aq_parent(aq_inner(result)) is app)
def _noWay(self, key, default=None): def _noWay(self, key, default=None):
raise KeyError(key) raise KeyError(key)
...@@ -104,56 +104,5 @@ class TestPropertySheet(unittest.TestCase): ...@@ -104,56 +104,5 @@ class TestPropertySheet(unittest.TestCase):
self.assertTrue(isinstance(inst.prop, tuple)) self.assertTrue(isinstance(inst.prop, tuple))
inst.manage_addProperty('prop2', ['xxx', 'yyy'], 'lines') inst.manage_addProperty('prop2', ['xxx', 'yyy'], 'lines')
self.assertTrue(type(inst.getProperty('prop2')) == type(())) self.assertTrue(isinstance(inst.getProperty('prop2'), tuple))
self.assertTrue(type(inst.prop2) == type(())) self.assertTrue(isinstance(inst.prop2, tuple))
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),
))
...@@ -435,6 +435,12 @@ class BaseRequest: ...@@ -435,6 +435,12 @@ class BaseRequest:
# Set the posttraverse for duration of the traversal here # Set the posttraverse for duration of the traversal here
self._post_traverse = post_traverse = [] self._post_traverse = post_traverse = []
# import time ordering problem
try:
from webdav.NullResource import NullResource
except ImportError:
NullResource = None
entry_name = '' entry_name = ''
try: try:
# We build parents in the wrong order, so we # We build parents in the wrong order, so we
...@@ -459,11 +465,11 @@ class BaseRequest: ...@@ -459,11 +465,11 @@ class BaseRequest:
# This is webdav support. The last object in the path # This is webdav support. The last object in the path
# should not be acquired. Instead, a NullResource should # should not be acquired. Instead, a NullResource should
# be given if it doesn't exist: # be given if it doesn't exist:
if (no_acquire_flag and if (NullResource is not None and no_acquire_flag and
hasattr(object, 'aq_base') and hasattr(object, 'aq_base') and
not hasattr(object,'__bobo_traverse__')): not hasattr(object, '__bobo_traverse__')):
if object.aq_parent is not object.aq_inner.aq_parent: if object.aq_parent is not object.aq_inner.aq_parent:
from webdav.NullResource import NullResource
object = NullResource(parents[-2], object.getId(), object = NullResource(parents[-2], object.getId(),
self).__of__(parents[-2]) 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