[Checkins] SVN: zope.webdav/trunk/src/zope/webdav/ Initial import of zope.webdav.

Michael Kerrin michael.kerrin at openapp.biz
Wed Aug 23 16:21:49 EDT 2006


Log message for revision 69736:
  Initial import of zope.webdav.
  
  It contains support for all WebDAV methods including
  LOCK and UNLOCK, and all the properites defined in 
  draft-ietf-webdav-rfc2518bis-15.txt
  
  It also contains a new mechanism for specifing the
  properties defined on a resource - see datamodel.txt
  for more info on this.
  

Changed:
  A   zope.webdav/trunk/src/zope/webdav/README.txt
  A   zope.webdav/trunk/src/zope/webdav/SETUP.cfg
  A   zope.webdav/trunk/src/zope/webdav/TODO.txt
  A   zope.webdav/trunk/src/zope/webdav/__init__.py
  A   zope.webdav/trunk/src/zope/webdav/adapters.py
  A   zope.webdav/trunk/src/zope/webdav/configure.zcml
  A   zope.webdav/trunk/src/zope/webdav/copymove.py
  A   zope.webdav/trunk/src/zope/webdav/coreproperties.py
  A   zope.webdav/trunk/src/zope/webdav/datamodel.txt
  A   zope.webdav/trunk/src/zope/webdav/deadproperties.py
  A   zope.webdav/trunk/src/zope/webdav/exceptions/
  A   zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py
  A   zope.webdav/trunk/src/zope/webdav/exceptions/badrequest.pt
  A   zope.webdav/trunk/src/zope/webdav/exceptions/browser.py
  A   zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml
  A   zope.webdav/trunk/src/zope/webdav/exceptions/ftests/
  A   zope.webdav/trunk/src/zope/webdav/exceptions/ftests/__init__.py
  A   zope.webdav/trunk/src/zope/webdav/exceptions/ftests/test_baseexceptions.py
  A   zope.webdav/trunk/src/zope/webdav/exceptions/tests/
  A   zope.webdav/trunk/src/zope/webdav/exceptions/tests/__init__.py
  A   zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_multiviews.py
  A   zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py
  A   zope.webdav/trunk/src/zope/webdav/ftesting.zcml
  A   zope.webdav/trunk/src/zope/webdav/ftests/
  A   zope.webdav/trunk/src/zope/webdav/ftests/__init__.py
  A   zope.webdav/trunk/src/zope/webdav/ftests/dav.py
  A   zope.webdav/trunk/src/zope/webdav/ftests/test_copymove.py
  A   zope.webdav/trunk/src/zope/webdav/ftests/test_mkcol.py
  A   zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py
  A   zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py
  A   zope.webdav/trunk/src/zope/webdav/ftests/test_z3_locking.py
  A   zope.webdav/trunk/src/zope/webdav/ietree-configure.zcml
  A   zope.webdav/trunk/src/zope/webdav/ietree.py
  A   zope.webdav/trunk/src/zope/webdav/interfaces.py
  A   zope.webdav/trunk/src/zope/webdav/locking.py
  A   zope.webdav/trunk/src/zope/webdav/lockingutils.py
  A   zope.webdav/trunk/src/zope/webdav/mkcol.py
  A   zope.webdav/trunk/src/zope/webdav/properties.py
  A   zope.webdav/trunk/src/zope/webdav/propfind.py
  A   zope.webdav/trunk/src/zope/webdav/proppatch.py
  A   zope.webdav/trunk/src/zope/webdav/publisher.py
  A   zope.webdav/trunk/src/zope/webdav/testing.py
  A   zope.webdav/trunk/src/zope/webdav/tests/
  A   zope.webdav/trunk/src/zope/webdav/tests/__init__.py
  A   zope.webdav/trunk/src/zope/webdav/tests/test_copymove.py
  A   zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py
  A   zope.webdav/trunk/src/zope/webdav/tests/test_inputwidgets.py
  A   zope.webdav/trunk/src/zope/webdav/tests/test_locking.py
  A   zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py
  A   zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py
  A   zope.webdav/trunk/src/zope/webdav/tests/test_publisher.py
  A   zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py
  A   zope.webdav/trunk/src/zope/webdav/tests/test_zetree.py
  A   zope.webdav/trunk/src/zope/webdav/utils.py
  A   zope.webdav/trunk/src/zope/webdav/widgets.py
  A   zope.webdav/trunk/src/zope/webdav/z3-configure.zcml
  A   zope.webdav/trunk/src/zope/webdav/zetree.py
  A   zope.webdav/trunk/src/zope/webdav/zope.webdav-configure.zcml
  A   zope.webdav/trunk/src/zope/webdav/zope.webdav-ftesting.zcml

-=-
Added: zope.webdav/trunk/src/zope/webdav/README.txt
===================================================================
--- zope.webdav/trunk/src/zope/webdav/README.txt	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/README.txt	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,88 @@
+============
+Introduction
+============
+
+The *zope.webdav* package is an implementation of the WebDAV protocol.
+It includes support for all the different methods, properties and error codes
+has defined in RFC2518 and draft-ietf-webdav-rfc2518bis-15.txt.
+
+Installation
+============
+
+This module should be installed like any other Zope3 module, that is it should
+be copied verbatim into your Python path and all zcml slugs should be installed
+into the *package-includes* directory. The zcml slugs for this package include
+*zope.webdav-configure.zcml* and *ietree-configure.zcml*. If you want to run
+the functional tests successfully then you must also install the
+*zope.webdav-ftesting.zcml* file.
+
+Now the *ietree-configure.zcml* file tells Zope what ElementTree engine you
+want to use to process the XML. You will probably want to edit this file to
+use the elementtree implementation that is installed on your system. There are
+two supported implementations, ElementTree and lxml. To use ElementTree
+just make sure that the utility declaration for *zope.webdav.zetree.EtreeEtree*
+is the only uncommented utility declaration. For lxml just make sure that
+*zope.webdav.zetree.LxmlEtree* is the only uncommented utility declaration.
+
+Data Model
+==========
+
+Read how to extend the `WebDAV Data Model`_ by defining your own properties.
+
+XML Processing
+==============
+
+WebDAV uses XML for conveying property names and values to and from the client.
+This package uses ElementTree API for processing this XML. Because of this all
+property names must be strings that conform to the syntax of ElementTree's
+element name (or tag). For example, for the property *getcontenttype* which
+belongs to the *DAV:* XML namespace will be referenced by the name
+*{DAV:}getcontenttype* within *zope.webdav*.
+
+Error Handling
+==============
+
+WebDAV defines new HTTP status codes in order convey more meaningful
+information about what just happened back to the client. The WebDAV
+specification also defines a multi-status response (with HTTP status code
+207) for situations where multiple status codes are more appropriate. For
+example the PROPPATCH method can modify multiple properties with multiple
+degrees of success. For each property we can say if the property update
+succeeded, failed, the property did not exists, or the user did not have the
+appropriate permissions to modify that property. To deal with this we have
+defined new exceptions that should be raised if a request can not be processed
+for a specific reason.
+
+When a multi-status response is desirable then *zope.webdav* will encapsulate
+all the errors that occurred during the request into either a
+*webdav.interfaces.WebDAVErrors* or a *webdav.interfaces.WebDAVPropstatErrors*
+exception and throw this error to the publisher. The publisher will then abort
+the current transaction and look up and render a *IHTTPException* view of this
+error.
+
+This view will generally be either *webdav.exceptions.MultiStatusErrorView* or
+*webdav.exceptions.WebDAVPropstatErrorView*. Both these views work by looping
+over all exceptions contained within the error and for each error it looks up
+a *webdav.interfaces.IDAVErrorWidget* which will contain all the information
+necessary to build up the final multi-status XML element which is sent back
+to the client.
+
+Each of these new exception implemented a derived interface of the
+*webdav.interfaces.IDAVException* interface (although this isn't necessary).
+As of writing the following exceptions can occur:
+
++ *webdav.interfaces.ConflictError*
+
++ *webdav.interfaces.ForbiddenError*
+
++ *webdav.interfaces.UnprocessableError*
+
++ *webdav.interfaces.PropertyNotFound*
+
++ *webdav.interfaces.PreconditionFailed*
+
++ *webdav.interfaces.FailedDependency*
+
++ *webdav.interfaces.AlreadyLocked*
+
+.. _WebDAV Data Model: zope.webdav.datamodel/@@show.html


Property changes on: zope.webdav/trunk/src/zope/webdav/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/SETUP.cfg
===================================================================
--- zope.webdav/trunk/src/zope/webdav/SETUP.cfg	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/SETUP.cfg	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,7 @@
+# Tell zpkg how to install the ZCML slugs.
+
+<data-files zopeskel/etc/package-includes>
+  zope.webdav-configure.zcml
+  zope.webdav-ftesting.zcml
+  ietree-configure.zcml
+</data-files>

Added: zope.webdav/trunk/src/zope/webdav/TODO.txt
===================================================================
--- zope.webdav/trunk/src/zope/webdav/TODO.txt	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/TODO.txt	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,81 @@
+====
+TODO
+====
+
+Miscellaneous
+=============
+
+The *zope.webdav* package needs to be split up into *zope.webdav* and
+*zope.app.webdav*. This is because there are a lot of stuff in here that
+is Zope3 specific and when migrating this to Zope2 we don't need them. I
+started this process but gave up after a while. Hence we have the
+z3-configure.zcml file. This should be a basis for *zope.app.webdav*.
+
+The ElemenetTree support should to be moved out into its own project, has
+it is probable of interest to others.
+
+We need better integration with Zope3. That includes providing better support
+for caching (ETags), and all the "IF*" headers defined in the HTTP and WebDAV
+specs. Also the exceptions and there views are not neccessarly WebDAV specific
+so they could maybe be moved into there own package. So developers of other
+related packages can re-use them. Note that this will probable require some
+rewrite of the current exceptions if this is ever going to happen.
+
+Error Handling
+--------------
+
+- write a error widget for the Forbidden exception.
+
+- more documentation.
+
+- I added a `propertyname' to the IDAVException interface. This seems a bit
+  random now and should be taught over.
+
+- Add support for setting the pre/post condition XML elements within a
+  a error multi-status view. See section 16 in
+  draft-ietf-webdav-rfc2518bis-15.txt. Still will probable result in spliting
+  up the webdav exceptions. For example AlreadyLocked breaks up into two
+  exceptions. One for when the the object is locked and the other for when
+  the object is locked but the supplied lock request uri doesn't match the
+  current lock token request uri annotation.
+
+- Finish the support for setting the responsedescription XML element within
+  the multi-status elements.
+
+Locking
+-------
+
+- update the timeout header parsing to support multiple arguments.
+
+- shared lock tokens need testing.
+
+- test locking default timeout - this is an application specific problem. So
+  the IDAVLockmanager should be dealing with accepting or rejecting the
+  requested timeout.
+
+- lock on unmapped urls doesn't work yet. Zope3 will need to be changed
+  to get this to work.
+
+- more unit tests. Names on lockingutils.DAVActiveLock,
+  lockingutils.DAVLockdiscovery. In locking the UNLOCK, LOCK method needs
+  testing.
+
+Misc
+----
+
+- Unicode handling - make sure that this works has it is supposed to.
+
+- allow configuring certain parts of the WebDAV protocol like default timeouts
+  on locks, and allowing depth = infinity on PROPFIND requests via ZCML.
+
+- the option request handler needs to be fixed.
+
+- propfind, proppatch - if any property update, view goes horrible wrong then
+  a 500 status code needs to be updated.
+
+- egg support
+
+Finally
+=======
+
+XXX's - all these need to be removed.


Property changes on: zope.webdav/trunk/src/zope/webdav/TODO.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/__init__.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/__init__.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,17 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""WebDAV support for Zope.
+
+$Id$
+"""


Property changes on: zope.webdav/trunk/src/zope/webdav/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/adapters.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/adapters.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/adapters.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,189 @@
+##############################################################################
+# 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.
+##############################################################################
+"""Basic WebDAV storage adapters for Files and Folders content objects.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import component
+from zope import interface
+import zope.publisher.interfaces.http
+from zope.dublincore.interfaces import IDCTimes, IDCDescriptiveProperties
+from zope.annotation.interfaces import IAnnotatable
+import zope.app.file.interfaces
+
+import zope.webdav.coreproperties
+
+
+class DAVSchemaAdapter(object):
+    """
+      >>> from zope.app.file.file import File
+      >>> file = File('some data for a file', 'text/plain')
+      >>> adapter = DAVSchemaAdapter(file, None)
+      >>> adapter.displayname is None
+      True
+      >>> adapter.creationdate is None
+      True
+      >>> adapter.displayname = u'Test File Title'
+      Traceback (most recent call last):
+      ...
+      ValueError
+      >>> adapter.getlastmodified is None
+      True
+
+      >>> from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter
+      >>> from zope.dublincore.interfaces import IWriteZopeDublinCore
+      >>> from zope.annotation.attribute import AttributeAnnotations
+      >>> from zope.annotation.interfaces import IAnnotations
+      >>> from zope.annotation.interfaces import IAttributeAnnotatable
+      >>> import datetime
+      >>> from zope.datetime import tzinfo
+
+      >>> interface.classImplements(File, IAttributeAnnotatable)
+      >>> component.getGlobalSiteManager().registerAdapter(
+      ...     AttributeAnnotations)
+      >>> component.getGlobalSiteManager().registerAdapter(
+      ...     ZDCAnnotatableAdapter, provided = IWriteZopeDublinCore)
+
+      >>> file = File('some data for a file', 'text/plain')
+      >>> IWriteZopeDublinCore(file).created = now = datetime.datetime(
+      ...     2006, 5, 24, 0, 0, 58, tzinfo = tzinfo(60))
+      >>> IWriteZopeDublinCore(file).title = u'Example Test File'
+      >>> IWriteZopeDublinCore(file).modified = now = datetime.datetime(
+      ...     2006, 5, 24, 0, 0, 58, tzinfo = tzinfo(60))
+
+      >>> adapter = DAVSchemaAdapter(file, None)
+      >>> adapter.creationdate == now
+      True
+      >>> adapter.displayname
+      u'Example Test File'
+      >>> adapter.getlastmodified == now
+      True
+      >>> adapter.displayname = u'Changed Test File Title'
+      >>> IWriteZopeDublinCore(file).title
+      u'Changed Test File Title'
+
+      >>> component.getGlobalSiteManager().unregisterAdapter(
+      ...     AttributeAnnotations, provided = IAnnotations)
+      True
+      >>> component.getGlobalSiteManager().unregisterAdapter(
+      ...     ZDCAnnotatableAdapter, provided = IWriteZopeDublinCore)
+      True
+
+    """
+    interface.implements(zope.webdav.coreproperties.IDAVCoreSchema)
+    component.adapts(IAnnotatable,
+                     zope.publisher.interfaces.http.IHTTPRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    @property
+    def creationdate(self):
+        dc = IDCTimes(self.context, None)
+        if dc is None or dc.created is None:
+            return None
+        return dc.created
+
+    @apply
+    def displayname():
+        def get(self):
+            descproperties = IDCDescriptiveProperties(self.context, None)
+            if descproperties is None:
+                return None
+            return descproperties.title
+        def set(self, value):
+            descproperties = IDCDescriptiveProperties(self.context, None)
+            if descproperties is None:
+                raise ValueError("")
+            descproperties.title = value
+        return property(get, set)
+
+    @property
+    def getlastmodified(self):
+        dc = IDCTimes(self.context, None)
+        if dc is None or dc.modified is None:
+            return None
+        return dc.modified
+
+
+class DAVFileGetSchema(object):
+    """
+      >>> from zope.app.file.file import File
+      >>> from zope.interface.verify import verifyObject
+      >>> file = File('some data for the file', 'text/plain')
+      >>> adapter = DAVFileGetSchema(file, None)
+      >>> verifyObject(zope.webdav.coreproperties.IDAVGetSchema, adapter)
+      True
+      >>> adapter.getcontentlanguage is None
+      True
+      >>> adapter.getcontentlength
+      22
+      >>> adapter.getcontenttype
+      'text/plain'
+
+      >>> from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter
+      >>> from zope.dublincore.interfaces import IWriteZopeDublinCore
+      >>> from zope.annotation.attribute import AttributeAnnotations
+      >>> from zope.annotation.interfaces import IAnnotations
+      >>> from zope.annotation.interfaces import IAttributeAnnotatable
+      >>> from zope.datetime import tzinfo
+      >>> import datetime
+      >>> interface.classImplements(File, IAttributeAnnotatable)
+      >>> component.getGlobalSiteManager().registerAdapter(
+      ...     AttributeAnnotations)
+      >>> component.getGlobalSiteManager().registerAdapter(
+      ...     ZDCAnnotatableAdapter, provided = IWriteZopeDublinCore)
+
+      >>> file = File('some data for the file', 'text/plain')
+      >>> adapter = DAVFileGetSchema(file, None)
+
+      >>> adapter.getcontentlanguage is None
+      True
+      >>> adapter.getcontentlength
+      22
+      >>> adapter.getcontenttype
+      'text/plain'
+      >>> adapter.getetag is None # etag is None for now.
+      True
+
+      >>> component.getGlobalSiteManager().unregisterAdapter(
+      ...     AttributeAnnotations, provided = IAnnotations)
+      True
+      >>> component.getGlobalSiteManager().unregisterAdapter(
+      ...     ZDCAnnotatableAdapter, provided = IWriteZopeDublinCore)
+      True
+
+    """
+    interface.implements(zope.webdav.coreproperties.IDAVGetSchema)
+    component.adapts(zope.app.file.interfaces.IFile,
+                     zope.publisher.interfaces.http.IHTTPRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    @property
+    def getcontentlanguage(self):
+        return None
+
+    @property
+    def getetag(self):
+        return None
+
+    @property
+    def getcontentlength(self):
+        return self.context.getSize()
+
+    @property
+    def getcontenttype(self):
+        return self.context.contentType


Property changes on: zope.webdav/trunk/src/zope/webdav/adapters.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/configure.zcml	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/configure.zcml	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,248 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   i18n_domain="zope.app.dav">
+
+  <include package="zope.webdav.exceptions" />
+
+  <!--
+      Support for integrating the protocol request handlers found in
+      the webdav package with the Zope3 publisher.
+    -->
+  <publisher
+     name="WEBDAV"
+     factory=".publisher.WebDAVRequestFactory"
+     methods="PROPFIND PROPPATCH MKCOL LOCK UNLOCK COPY MOVE"
+     priority="30"
+     />
+
+  <adapter
+     factory=".propfind.PROPFIND"
+     name="PROPFIND"
+     />
+
+  <adapter
+     factory=".proppatch.PROPPATCH"
+     name="PROPPATCH"
+     />
+
+  <adapter
+     factory=".mkcol.NullResource"
+     name="MKCOL"
+     />
+
+  <adapter
+     factory=".copymove.COPY"
+     name="COPY"
+     />
+
+  <adapter
+     factory=".copymove.MOVE"
+     name="MOVE"
+     />
+
+  <adapter
+     for="zope.interface.Interface
+          zope.webdav.interfaces.IWebDAVRequest"
+     provides="zope.webdav.interfaces.IWebDAVMethod"
+     factory=".locking.LOCK"
+     name="LOCK"
+     />
+
+  <adapter
+     for="zope.interface.Interface
+          zope.webdav.interfaces.IWebDAVRequest"
+     provides="zope.webdav.interfaces.IWebDAVMethod"
+     factory=".locking.UNLOCK"
+     name="UNLOCK"
+     />
+
+  <!--
+      Collection of display widget definitions.
+    -->
+
+  <adapter
+     for="zope.schema.interfaces.IText
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.TextDAVWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.ITextLine
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.TextDAVWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.IInt
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.IntDAVWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.IFloat
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.IntDAVWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.IDatetime
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.DatetimeDAVWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.IDate
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.DatetimeDAVWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.ISequence
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.ListDAVWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.IObject
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.ObjectDAVWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.IURI
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.TextDAVWidget"
+     />
+
+  <adapter
+     for=".properties.DeadField
+          .interfaces.IWebDAVRequest"
+     factory=".properties.OpaqueWidget"
+     />
+
+  <!--
+      Collection of webdav input widgets definitions
+    -->
+
+  <adapter
+     for="zope.schema.interfaces.IText
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.TextDAVInputWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.ITextLine
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.TextDAVInputWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.IInt
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.IntDAVInputWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.IFloat
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.FloatDAVInputWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.IDatetime
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.DatetimeDAVInputWidget"
+     />
+
+  <adapter
+     for="zope.schema.interfaces.IDate
+          .interfaces.IWebDAVRequest"
+     factory=".widgets.DateDAVInputWidget"
+     />
+
+  <!--
+      Declare all properties managed by this package.
+    -->
+  <utility
+     component=".coreproperties.creationdate"
+     name="{DAV:}creationdate"
+     />
+
+  <utility
+     component=".coreproperties.displayname"
+     name="{DAV:}displayname"
+     />
+
+  <utility
+     component=".coreproperties.getcontentlanguage"
+     name="{DAV:}getcontentlanguage"
+     />
+
+  <utility
+     component=".coreproperties.getcontentlength"
+     name="{DAV:}getcontentlength"
+     />
+
+  <utility
+     component=".coreproperties.getcontenttype"
+     name="{DAV:}getcontenttype"
+     />
+
+  <utility
+     component=".coreproperties.getetag"
+     name="{DAV:}getetag"
+     />
+
+  <utility
+     component=".coreproperties.getlastmodified"
+     name="{DAV:}getlastmodified"
+     />
+
+  <utility
+     component=".coreproperties.lockdiscovery"
+     name="{DAV:}lockdiscovery"
+     />
+
+  <utility
+     component=".coreproperties.resourcetype"
+     name="{DAV:}resourcetype"
+     />
+
+  <utility
+     component=".coreproperties.supportedlock"
+     name="{DAV:}supportedlock"
+     />
+
+  <!--
+      Mandatory minimum storage adapter
+    -->
+  <adapter
+     factory=".coreproperties.ResourceTypeAdapter"
+     />
+
+  <!--
+      Zope3 support for WebDAV
+    -->
+  <include file="z3-configure.zcml" />
+
+  <configure
+     xmlns:zcml="http://namespaces.zope.org/zcml"
+     zcml:condition="have apidoc"
+     xmlns="http://namespaces.zope.org/apidoc">
+
+    <bookchapter
+       id="zope.webdav"
+       title="WebDAV"
+       doc_path="README.txt"
+       />
+
+    <bookchapter
+       id="zope.webdav.datamodel"
+       title="WebDAV Data Model"
+       doc_path="datamodel.txt"
+       parent="zope.webdav"
+       />
+
+  </configure>
+
+</configure>


Property changes on: zope.webdav/trunk/src/zope/webdav/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/copymove.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/copymove.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/copymove.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,164 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""COPY and MOVE support for WebDAV.
+
+This needs to be cleaned up in order to be easily ported to Zope2.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import urlparse
+
+from zope import interface
+from zope import component
+from zope.copypastemove.interfaces import IObjectCopier, IObjectMover
+from zope.traversing.api import traverse, getRoot
+from zope.traversing.interfaces import TraversalError
+from zope.traversing.browser import absoluteURL
+from zope.app.publication.http import MethodNotAllowed
+
+import zope.webdav.interfaces
+
+class Base(object):
+    interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+    component.adapts(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def getOverwrite(self):
+        overwrite = self.request.getHeader("overwrite", "t").lower().strip()
+        if overwrite == "t":
+            overwrite = True
+        elif overwrite == "f":
+            overwrite = False
+        else:
+            raise zope.webdav.interfaces.BadRequest(
+                self.request, message = u"Invalid overwrite header")
+
+        return overwrite
+
+    def getDestinationPath(self):
+        dest = self.request.getHeader("destination", None)
+        while dest and dest[-1] == "/":
+            dest = dest[:-1]
+        if not dest:
+            raise zope.webdav.interfaces.BadRequest(
+                self.request, message = u"No `destination` header sent.")
+
+        scheme, location, destpath, query, fragment = urlparse.urlsplit(dest)
+        # XXX - this is likely to break under virtual hosting.
+        if location and self.request.get("HTTP_HOST", None) is not None and \
+               location != self.request.get("HTTP_HOST"):
+            # This may occur when the destination is on another
+            # server, repository or URL namespace.  Either the source namespace
+            # does not support copying to the destination namespace, or the
+            # destination namespace refuses to accept the resource.  The client
+            # may wish to try GET/PUT and PROPFIND/PROPPATCH instead.
+            raise zope.webdav.interfaces.BadGateway(self.context, self.request)
+
+        return destpath
+
+    def getDestinationNameAndParentObject(self):
+        """Returns a tuple for destionation name, the parent folder object
+        and a boolean flag indicating wheater the the destinatin object will
+        have to be created or not.
+        """
+        destpath = self.getDestinationPath()
+        try:
+            destob = traverse(getRoot(self.context), destpath)
+        except TraversalError:
+            destob = None
+
+        overwrite = self.getOverwrite()
+
+        parentpath = destpath.split('/')
+        destname = parentpath.pop()
+        try:
+            parent = traverse(getRoot(self.context), parentpath)
+        except TraversalError:
+            raise zope.webdav.interfaces.ConflictError(
+                self.context, message = u"Destination resource has no parent")
+
+        if destob is not None and not overwrite:
+            raise zope.webdav.interfaces.PreconditionFailed(
+                self.context,
+                message = "destination exists and OverWrite header is F")
+        elif destob is not None and destob == self.context:
+            raise zope.webdav.interfaces.ForbiddenError(
+                self.context,
+                message = "destionation and source objects are the same")
+        elif destob is not None:
+            del parent[destname]
+
+        return destname, destob, parent
+
+
+class COPY(Base):
+    """COPY handler for all objects."""
+
+    def COPY(self):
+        copier = IObjectCopier(self.context, None)
+        if copier is None or not copier.copyable():
+            raise MethodNotAllowed(self.context, self.request)
+
+        # get the destination
+        destname, destob, parent = self.getDestinationNameAndParentObject()
+
+        if not copier.copyableTo(parent, destname):
+            # Conflict
+            raise zope.webdav.interfaces.ConflictError(
+                self.context,
+                message = u"can not copy to the destionation folder")
+
+        newdestname = copier.copyTo(parent, destname)
+
+        if destob is not None:
+            self.request.response.setStatus(204)
+        else:
+            self.request.response.setStatus(201)
+            self.request.response.setHeader(
+                "Location", absoluteURL(parent[newdestname], self.request))
+
+        return ""
+
+
+class MOVE(Base):
+    """MOVE handler for all objects."""
+
+    def MOVE(self):
+        mover = IObjectMover(self.context, None)
+        if mover is None or not mover.moveable():
+            raise MethodNotAllowed(self.context, self.request)
+
+        # get the destination
+        destname, destob, parent = self.getDestinationNameAndParentObject()
+
+        if not mover.moveableTo(parent, destname):
+            raise zope.webdav.interfaces.ConflictError(
+                self.context,
+                message = u"can not copy to the destionation folder")
+
+        newdestname = mover.moveTo(parent, destname)
+
+        if destob is not None:
+            self.request.response.setStatus(204)
+        else:
+            self.request.response.setStatus(201)
+            self.request.response.setHeader(
+                "Location", absoluteURL(parent[newdestname], self.request))
+
+        return ""


Property changes on: zope.webdav/trunk/src/zope/webdav/copymove.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/coreproperties.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/coreproperties.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/coreproperties.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,344 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Definition of all properties defined in the WebDAV specificiation.
+
+  o readonly -> that the property is protected.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import component
+from zope import schema
+from zope.app.i18n import ZopeMessageFactory as _
+import zope.app.container.interfaces
+import zope.publisher.interfaces.http
+
+from zope.webdav.properties import DAVProperty, DeadField
+import zope.webdav.widgets
+
+class IDAVCreationdate(interface.Interface):
+
+    creationdate = schema.Datetime(
+        title = _("Records the time and date the resource was created."),
+        description = _("""The DAV:creationdate property SHOULD be defined on
+                           all DAV compliant resources.  If present, it
+                           contains a timestamp of the moment when the resource
+                           was created.  Servers that are incapable of
+                           persistently recording the creation date SHOULD
+                           instead leave it undefined (i.e. report
+                           "Not Found" """),
+        readonly = True)
+
+
+class IDAVDisplayname(interface.Interface):
+
+    displayname = schema.TextLine(
+        title = _("Provides a name for the resource that is suitable for presentation to a user."),
+        description = _("""Contains a description of the resource that is
+                           suitable for presentation to a user.  This property
+                           is defined on the resource, and hence SHOULD have
+                           the same value independent of the Request-URI used
+                           to retrieve it (thus computing this property based
+                           on the Request-URI is deprecated).  While generic
+                           clients might display the property value to end
+                           users, client UI designers must understand that the
+                           method for identifying resources is still the URL.
+                           Changes to DAV:displayname do not issue moves or
+                           copies to the server, but simply change a piece of
+                           meta-data on the individual resource.  Two resources
+                           can have the same DAV:displayname value even within
+                           the same collection."""),
+        readonly = False)
+
+
+class IDAVGetcontentlanguage(interface.Interface):
+
+    getcontentlanguage = schema.TextLine(
+        title = _("GET Content-Language header."),
+        description = _("""Contains the Content-Language header value (from
+                           Section 14.12 of [RFC2616]) as it would be returned
+                           by a GET without accept headers.
+
+                           The DAV:getcontentlanguage property MUST be defined
+                           on any DAV compliant resource that returns the
+                           Content-Language header on a GET."""),
+        readonly = False)
+
+
+class IDAVGetcontentlength(interface.Interface):
+
+    getcontentlength = schema.Int(
+        title = _("Contains the Content-Length header returned by a GET without accept headers."),
+        description = _("""The DAV:getcontentlength property MUST be defined on
+                            any DAV compliant resource that returns the
+                            Content-Length header in response to a GET."""),
+        readonly = True)
+
+
+class IDAVGetcontenttype(interface.Interface):
+
+    getcontenttype = schema.TextLine(
+        title = _("Contains the Content-Type header value as it would be returned by a GET without accept headers."),
+        description = _("""This property MUST be defined on any DAV compliant
+                           resource that returns the Content-Type header in
+                           response to a GET."""),
+        readonly = False)
+
+
+class IDAVGetetag(interface.Interface):
+
+    getetag = schema.TextLine(
+        title = _("Contains the ETag header value as it would be returned by a GET without accept headers."),
+        description = _("""The getetag property MUST be defined on any DAV
+                           compliant resource that returns the Etag header.
+                           Refer to Section 3.11 of RFC2616 for a complete
+                           definition of the semantics of an ETag, and to
+                           Section 8.6 for a discussion of ETags in WebDAV."""),
+        readonly = True)
+
+
+class IDAVGetlastmodified(interface.Interface):
+
+    getlastmodified = schema.Datetime(
+        title = _("Contains the Last-Modified header value as it would be returned by a GET method without accept headers."),
+        description = _("""Note that the last-modified date on a resource SHOULD
+                           only reflect changes in the body (the GET responses)
+                           of the resource.  A change in a property only SHOULD
+                           NOT cause the last-modified date to change, because
+                           clients MAY rely on the last-modified date to know
+                           when to overwrite the existing body.  The
+                           DAV:getlastmodified property MUST be defined on any
+                           DAV compliant resource that returns the
+                           Last-Modified header in response to a GET."""),
+        readonly = True)
+
+
+class ILockEntry(interface.Interface):
+    """A DAV Sub property of the supportedlock property.
+    """
+    lockscope = schema.List(
+        title = u"Describes the exclusivity of a lock",
+        description = u"""Specifies whether a lock is an exclusive lock, or a
+                          shared lock.""")
+
+    locktype = schema.List(
+        title = u"Describes the access type of the lock.",
+        description = u"""Specifies the access type of a lock. At present,
+                          this specification only defines one lock type, the
+                          write lock.""")
+
+
+class IActiveLock(ILockEntry):
+    """A DAV Sub property of the lockdiscovery property.
+    """
+    depth = schema.Text(
+        title = u"Depth",
+        description = u"The value of the Depth header.",
+        required = False,
+        readonly = True)
+
+    owner = DeadField(
+        title = u"Owner",
+        description = u"""The owner XML element provides information sufficient
+                          for either directly contacting a principal (such as a
+                          telephone number or Email URI), or for discovering the
+                          principal (such as the URL of a homepage) who created
+                          a lock.
+
+                          The value provided MUST be treated as a dead property
+                          in terms of XML Information Item preservation.  The
+                          server MUST NOT alter the value unless the owner
+                          value provided by the client is empty.
+                          """,
+        required = False,
+        readonly = True)
+
+    timeout = schema.Text(
+        title = u"Timeout",
+        description = u"The timeout associated with a lock",
+        required = False,
+        readonly = True)
+
+    locktoken = schema.List(
+        title = u"Lock Token",
+        description = u"""The href contains one or more opaque lock token URIs
+                          which all refer to the same lock (i.e., the
+                          OpaqueLockToken-URI production in section 6.4).""",
+        value_type = schema.URI(
+              __name__ = u"href",
+              title = u"Href",
+              description = u"""The href contains a single lock token URI
+                                which refers to the lock.""",
+              ),
+        required = False,
+        readonly = True)
+
+    lockroot = schema.URI(
+        title = u"Lock root",
+        description = u"""
+        """,
+        readonly = True,
+        required = True)
+
+
+class IDAVLockdiscovery(interface.Interface):
+
+    lockdiscovery = schema.List(
+        title = _("Describes the active locks on a resource"),
+        description = _("""Returns a listing of who has a lock, what type of
+                           lock he has, the timeout type and the time remaining
+                           on the timeout, and the associated lock token.  If
+                           there are no locks, but the server supports locks,
+                           the property will be present but contain zero
+                           'activelock' elements.  If there is one or more
+                           lock, an 'activelock' element appears for each lock
+                           on the resource.  This property is NOT lockable with
+                           respect to write locks (Section 7)."""),
+        value_type = schema.Object(
+            __name__ = "activelock",
+            title = _(""),
+            description = _(""),
+            schema = IActiveLock,
+            readonly = True),
+        readonly = True)
+
+
+class IDAVResourcetype(interface.Interface):
+
+    resourcetype = schema.List(
+        title = _("Specifies the nature of the resource."),
+        description = _("""MUST be defined on all DAV compliant resources.  Each
+                           child element identifies a specific type the
+                           resource belongs to, such as 'collection', which is
+                           the only resource type defined by this specification
+                           (see Section 14.3).  If the element contains the
+                           'collection' child element plus additional
+                           unrecognized elements, it should generally be
+                           treated as a collection.  If the element contains no
+                           recognized child elements, it should be treated as a
+                           non-collection resource.  The default value is empty.
+                           This element MUST NOT contain text or mixed content.
+                           Any custom child element is considered to be an
+                           identifier for a resource type."""),
+        readonly = False)
+
+
+class IDAVSupportedlock(interface.Interface):
+
+    supportedlock = schema.List(
+        title = _("To provide a listing of the lock capabilities supported by the resource."),
+        description = _("""Returns a listing of the combinations of scope and
+                           access types which may be specified in a lock
+                           request on the resource.  Note that the actual
+                           contents are themselves controlled by access
+                           controls so a server is not required to provide
+                           information the client is not authorized to see.
+                           This property is NOT lockable with respect to
+                           write locks (Section 7)."""),
+        value_type = schema.Object(
+            __name__ = "lockentry",
+            title = _(""),
+            description = _(""),
+            schema = ILockEntry,
+            readonly = True),
+        readonly = True)
+
+
+class IDAVCoreSchema(IDAVCreationdate,
+                     IDAVDisplayname,
+                     IDAVGetlastmodified):
+    """Base core properties - note that resourcetype is complusory and is in
+    its own interface.
+    """
+
+
+class IDAVGetSchema(IDAVGetcontentlanguage,
+                    IDAVGetcontentlength,
+                    IDAVGetcontenttype,
+                    IDAVGetetag):
+    """Extended properties that only apply to certain content.
+    """
+
+
+class IDAVLockSupport(IDAVLockdiscovery,
+                      IDAVSupportedlock):
+    """
+    """
+
+
+################################################################################
+#
+# Collection of default properties has defined in Section 15.
+# subsection 1 -> 10 of draft-ietf-webdav-rfc2518bis-15.txt
+#
+################################################################################
+
+creationdate = DAVProperty("{DAV:}creationdate", IDAVCreationdate)
+creationdate.custom_widget = zope.webdav.widgets.ISO8601DatetimeDAVWidget
+
+displayname = DAVProperty("{DAV:}displayname", IDAVDisplayname)
+
+getcontentlanguage = DAVProperty("{DAV:}getcontentlanguage",
+                                 IDAVGetcontentlanguage)
+
+getcontentlength = DAVProperty("{DAV:}getcontentlength", IDAVGetcontentlength)
+
+getcontenttype = DAVProperty("{DAV:}getcontenttype", IDAVGetcontenttype)
+
+getetag = DAVProperty("{DAV:}getetag", IDAVGetetag)
+
+getlastmodified = DAVProperty("{DAV:}getlastmodified", IDAVGetlastmodified)
+
+resourcetype = DAVProperty("{DAV:}resourcetype", IDAVResourcetype)
+
+lockdiscovery = DAVProperty("{DAV:}lockdiscovery", IDAVLockdiscovery)
+
+supportedlock = DAVProperty("{DAV:}supportedlock", IDAVSupportedlock)
+
+################################################################################
+#
+# Default storage adapter for the only mandatory property.
+#
+################################################################################
+
+class ResourceTypeAdapter(object):
+    """
+      >>> from zope.app.file.file import File
+      >>> file = File('some data for a file', 'text/plain')
+      >>> adapter = ResourceTypeAdapter(file, None)
+      >>> adapter.resourcetype is None
+      True
+
+      >>> from zope.app.folder.folder import Folder
+      >>> folder = Folder()
+      >>> adapter = ResourceTypeAdapter(folder, None)
+      >>> adapter.resourcetype
+      [u'collection']
+
+    """
+    interface.implements(IDAVResourcetype)
+    component.adapts(interface.Interface,
+                     zope.publisher.interfaces.http.IHTTPRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    @property
+    def resourcetype(self):
+        if zope.app.container.interfaces.IReadContainer.providedBy(
+            self.context):
+            return [u'collection']
+        return None


Property changes on: zope.webdav/trunk/src/zope/webdav/coreproperties.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/datamodel.txt
===================================================================
--- zope.webdav/trunk/src/zope/webdav/datamodel.txt	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/datamodel.txt	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,380 @@
+=================
+WebDAV Properties
+=================
+
+.. contents::
+
+Introduction
+============
+
+The WebDAV data model consists of properties. Properties are name, value
+pairs whose name is a URI and whose value is a well formed XML fragment. All
+the WebDAV properties are split up into two categories, "live" and "dead"
+properties. Al live property is a property who has either some its semantics
+or syntax enforced by the server. A dead property has its syntax and semantics
+enforced by the client, and the server merely records the value of the property
+verbatim.
+
+Dead properties are just stored, edited or deleted through a adapter
+*zope.webdav.interfaces.IOpaquePropertyStorage* adapter. This just stores the
+data sent through a PROPPATCH request, performing no checks on the value of
+the data sent.
+
+Live properties are declared in advanace by declaring a utility which
+implements *zope.webdav.interfaces.IDAVProperty*. *IAVProperty* is just a data
+structure containing all the information neccessary to process a request.
+This information includes:
+
++ iface - a storage interface used to look up an adapter implementing
+
++ field - used to validate the data sent through PROPPATCH
+
++ restricted - 
+
++ custom_widget / custom_input_widget - 
+
++ restricted - 
+
+This document will describe how Zope maintains and queries the WebDAV data
+model for all content objects within it, and how you has a developer can
+extend this model to include your own properties which can then be rendered
+or modified via the PROPFIND and PROPPATCH methods respectively.
+
+Live properties
+===============
+
+For example will define the live property `{examplens:}age` which might
+describe the age of a resource (or object in Zope). First we need to setup
+the environment and some content. This WebDAV package should do all this
+when the system starts up.
+
+  >>> from zope import interface
+  >>> from zope import schema
+  >>> import zope.schema.interfaces
+  >>> from zope import component
+  >>> import zope.webdav.properties
+  >>> import zope.webdav.interfaces
+  >>> import zope.webdav.publisher
+  >>> import zope.webdav.widgets
+  >>> from cStringIO import StringIO
+  >>> import zope.webdav.testing
+  >>> etree = zope.webdav.testing.etreeSetup()
+  >>> component.getGlobalSiteManager().registerAdapter(
+  ...    zope.webdav.widgets.IntDAVWidget,
+  ...    (zope.schema.interfaces.IInt, zope.webdav.interfaces.IWebDAVRequest),
+  ...    zope.webdav.interfaces.IDAVWidget)
+  >>> component.getGlobalSiteManager().registerAdapter(
+  ...    zope.webdav.widgets.TextDAVWidget,
+  ...    (zope.schema.interfaces.ITextLine,
+  ...     zope.webdav.interfaces.IWebDAVRequest),
+  ...    zope.webdav.interfaces.IDAVWidget)
+  >>> class IDemoContent(interface.Interface):
+  ...    ageprop = schema.Int(title = u"Age of resource")
+  >>> class DemoContent(object):
+  ...    interface.implements(IDemoContent)
+  ...    ageprop = 10
+  >>> democontent = DemoContent()
+  >>> request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+
+First we must define an interface that defines how we must store and
+retrieve the property:
+
+  >>> class IAgeStorage(interface.Interface):
+  ...    age = schema.Int(
+  ...        title = u"Age",
+  ...        description = u"Age of the resoure")
+
+Now we must declare and register a named utility implementing *IDAVProperty*.
+
+  >>> ageProp = zope.webdav.properties.DAVProperty("{examplens:}age",
+  ...    IAgeStorage)
+  >>> component.getGlobalSiteManager().registerUtility(
+  ...    ageProp, zope.webdav.interfaces.IDAVProperty, "{examplens:}age")
+
+It order for it to be defined there must be exist a multi-adapter from the
+resource to *IAgeStorage*. This allows some properties to be defined for a
+one resource and not an other.
+
+Initially for our resource we have no defined properties, since we haven't
+yet implemented the storage adapter.
+
+  >>> list(zope.webdav.properties.getAllProperties(democontent, request))
+  []
+  >>> zope.webdav.properties.hasProperty(
+  ...    democontent, request, "{examplens:}age")
+  False
+  >>> zope.webdav.properties.getProperty(
+  ...    democontent, request, "{examplens:}age")
+  Traceback (most recent call last):
+  ...
+  PropertyNotFound: {examplens:}age
+
+Now define and register the storage adapter for our `age` property.
+
+  >>> class AgeStorage(object):
+  ...    interface.implements(IAgeStorage)
+  ...    component.adapts(IDemoContent, zope.webdav.interfaces.IWebDAVRequest)
+  ...    def __init__(self, context, request):
+  ...        self.context = context
+  ...    def get_age(self):
+  ...        return self.context.ageprop
+  ...    age = property(get_age)
+  >>> component.getGlobalSiteManager().registerAdapter(AgeStorage,
+  ...    (IDemoContent, zope.webdav.interfaces.IWebDAVRequest), IAgeStorage)
+
+Since we now defined a storage adapter for the `age` property on our
+DemoContent object, the property is defined on this resource.
+
+  >>> zope.webdav.properties.hasProperty(
+  ...    democontent, request, "{examplens:}age")
+  True
+
+So now we can ask the *zope.webdav.interfaces.getProperty* method to return the
+*IDAVProperty* utility representing this property and the storage adapter.
+
+  >>> prop, adapter = zope.webdav.properties.getProperty(
+  ...   democontent, request, "{examplens:}age")
+  >>> print prop #doctest:+ELLIPSIS
+  <zope.webdav.properties.DAVProperty ...
+  >>> isinstance(adapter, AgeStorage)
+  True
+
+Now the value of this property has stored in Zope is:
+
+  >>> prop.field.get(adapter)
+  10
+
+In order to render this value has a WebDAV response we ask the
+*zope.webdav.properties.getWidget* method to return a widget that knows how to
+render this type of property.
+
+  >>> davwidget = zope.webdav.properties.getWidget(prop, adapter, request)
+  >>> zope.webdav.testing.assertXMLEqual(davwidget.render(),
+  ...    """<E:age xmlns:E="examplens:">10</E:age>""")
+
+Finally the *zope.webdav.properties.getAllProperties* method contains one entry:
+
+  >>> ['%s, %s' %(prop.namespace, prop.__name__) for prop, adapter in 
+  ...    zope.webdav.properties.getAllProperties(democontent, request)]
+  ['examplens:, age']
+
+Clean up Live Properties Test
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By removing the storage adapter the above `age` property disappears.
+
+  >>> component.getGlobalSiteManager().unregisterAdapter(AgeStorage,
+  ...    (IDemoContent, zope.webdav.interfaces.IWebDAVRequest), IAgeStorage)
+  True
+
+  >>> prop, adapter = zope.webdav.properties.getProperty(democontent, request,
+  ...    "{examplens:}age")
+  Traceback (most recent call last):
+  ...
+  PropertyNotFound: {examplens:}age
+
+Dead Properties
+===============
+
+Dead properties are handled just slightly differently. We first need to setup
+a *zope.webdav.properties.IOpaquePropertyStorage* adapter for our demo content.
+
+  >>> import weakref
+  >>> deadprops = weakref.WeakKeyDictionary()
+  >>> class DeadProperties(object):
+  ...   interface.implements(zope.webdav.interfaces.IOpaquePropertyStorage)
+  ...   component.adapts(IDemoContent)
+  ...   def __init__(self, context):
+  ...       self._mapping = deadprops.setdefault(context, {})
+  ...   def getAllProperties(self):
+  ...       return self._mapping.keys()
+  ...   def hasProperty(self, tag):
+  ...       return tag in self._mapping
+  ...   def getProperty(self, tag):
+  ...       return self._mapping.get(tag, None)
+  ...   def setProperty(self, tag, value):
+  ...       self._mapping[tag] = value
+
+  >>> dpname = "{examplens:}deadprop"
+
+Firstly, we note that the dead property `dpname` doesn't exist because we
+have yet to register our DeadProperties class.
+
+  >>> zope.webdav.properties.hasProperty(democontent, request, dpname)
+  False
+  >>> [prop.__name__ for prop, adapter in
+  ...  zope.webdav.properties.getAllProperties(democontent, request)]
+  []
+  >>> zope.webdav.properties.getProperty(democontent, request, dpname)
+  Traceback (most recent call last):
+  ...
+  PropertyNotFound: {examplens:}deadprop
+
+  >>> component.getGlobalSiteManager().registerAdapter(DeadProperties)
+
+Since we have defined a *zope.webdav.properties.IOpaquePropertyStorage* adapter,
+we can now set and get any dead properties.
+
+  >>> prop, adapter = zope.webdav.properties.getProperty(
+  ...    democontent, request, dpname)
+  >>> prop #doctest:+ELLIPSIS
+  <zope.webdav.properties.OpaqueProperty object at ...
+  >>> prop.__name__
+  'deadprop'
+  >>> prop.namespace
+  'examplens:'
+
+But since no data has being stored for the `{examplens:}deadprop` property,
+it really doesn't exist yet even though we can get the property. If we want
+to call `getProperty` but not to generate the property we change the value
+of the keyword arguement `exists` to True.
+
+  >>> zope.webdav.properties.getProperty(
+  ...    democontent, request, dpname, exists = True)
+  Traceback (most recent call last):
+  ...
+  PropertyNotFound: {examplens:}deadprop
+
+  >>> list(zope.webdav.properties.getAllProperties(democontent, request))
+  []
+  >>> zope.webdav.properties.hasProperty(democontent, request, dpname)
+  False
+
+Now set some data.
+
+  >>> field = prop.field.bind(adapter)
+  >>> field.set(adapter, """<E:deadprop xmlns:E="examplens:">This is some content</E:deadprop>""")
+
+  >>> ['%s, %s' %(prop.namespace, prop.__name__) for prop, adapter in
+  ...  zope.webdav.properties.getAllProperties(democontent, request)]
+  ['examplens:, deadprop']
+  >>> zope.webdav.properties.hasProperty(democontent, request, dpname)
+  True
+  >>> prop, adapter = zope.webdav.properties.getProperty(
+  ...    democontent, request, dpname)
+  >>> prop is not None
+  True
+
+Grouping Properties
+===================
+
+Instead of writing multiple storage adapters, we can group properties into
+one storage adapter. For example suppose that we have another two live
+properties, name and title and we want to write a single storage adapter
+implementing both these properties. This could be handy if there already
+exists an adapter providing the storage functionality.
+
+  >>> class INameStorage(interface.Interface):
+  ...    name = schema.TextLine(
+  ...         title = u"Name",
+  ...         description = u"Name of the resource")
+  >>> class ITitleStorage(interface.Interface):
+  ...    title = schema.TextLine(
+  ...         title = u"Title",
+  ...         description = u"Title of the resource")
+  >>> nameProp = zope.webdav.properties.DAVProperty(
+  ...    "{examplens:}name", INameStorage)
+  >>> titleProp = zope.webdav.properties.DAVProperty("{examplens:}title",
+  ...    ITitleStorage)
+  >>> component.getGlobalSiteManager().registerUtility(
+  ...    nameProp, zope.webdav.interfaces.IDAVProperty, "{examplens:}name")
+  >>> component.getGlobalSiteManager().registerUtility(
+  ...    titleProp, zope.webdav.interfaces.IDAVProperty, "{examplens:}title")
+
+Now write the storage adapter and register with the component architecture.
+
+  >>> class Storage(object):
+  ...    component.adapts(IDemoContent, zope.webdav.interfaces.IWebDAVRequest)
+  ...    interface.implements(INameStorage, ITitleStorage)
+  ...    def __init__(self, context, request):
+  ...        self.context, self.request = context, request
+  ...    def name_get(self):
+  ...        return getattr(self.context, 'name', '')
+  ...    def name_set(self, value):
+  ...        self.context.name = value
+  ...    name =  property(name_get, name_set)
+  ...    def title_get(self):
+  ...        return getattr(self.context, 'title', '')
+  ...    def title_set(self, value):
+  ...        self.contexxt.title = value
+  ...    title = property(title_get, title_set)
+
+  >>> component.getGlobalSiteManager().registerAdapter(
+  ...    Storage, provided = INameStorage)
+  >>> component.getGlobalSiteManager().registerAdapter(
+  ...    Storage, provided = ITitleStorage)
+
+Now when we call either the *getProperty* or the *hasProperty* method for only
+one of the properties, name or title.
+
+  >>> nprop, adapter = zope.webdav.properties.getProperty(democontent, request,
+  ...    "{examplens:}name")
+  >>> titleprop, adapter = zope.webdav.properties.getProperty(
+  ...    democontent, request, "{examplens:}title")
+
+  >>> zope.webdav.properties.hasProperty(democontent, request,
+  ...    "{examplens:}name")
+  True
+
+Alternatively we can do the following, by extending the interfaces and then
+only registering the storage adapter once. First we need to clean up the
+previous tests.
+
+  >>> component.getGlobalSiteManager().unregisterAdapter(
+  ...    Storage, provided = INameStorage)
+  True
+  >>> component.getGlobalSiteManager().unregisterAdapter(
+  ...    Storage, provided = ITitleStorage)
+  True
+
+  >>> class INameTitleStorage(INameStorage, ITitleStorage):
+  ...    """Merge the name and title storage interfaces."""
+  >>> class NameTitleStorage(object):
+  ...    component.adapts(IDemoContent, zope.webdav.interfaces.IWebDAVRequest)
+  ...    interface.implements(INameTitleStorage)
+  ...    def __init__(self, context, request):
+  ...        self.context, self.request = context, request
+  ...    def name_get(self):
+  ...        return getattr(self.context, 'name', '')
+  ...    def name_set(self, value):
+  ...        self.context.name = value
+  ...    name =  property(name_get, name_set)
+  ...    def title_get(self):
+  ...        return getattr(self.context, 'title', '')
+  ...    def title_set(self, value):
+  ...        self.contexxt.title = value
+  ...    title = property(title_get, title_set)
+  >>> component.getGlobalSiteManager().registerAdapter(NameTitleStorage)
+
+  >>> zope.webdav.properties.hasProperty(
+  ...    democontent, request, "{examplens:}name")
+  True
+  >>> prop, adapter = zope.webdav.properties.getProperty(democontent, request,
+  ...    "{examplens:}name")
+  >>> props = [prop for prop, adapter in
+  ...          zope.webdav.properties.getAllProperties(democontent, request)]
+  >>> props = [prop.__name__ for prop in props]
+  >>> props.sort()
+  >>> props
+  ['deadprop', 'name', 'title']
+
+Cleanup Test
+============
+
+  >>> component.getGlobalSiteManager().unregisterAdapter(
+  ...    zope.webdav.widgets.IntDAVWidget,
+  ...    (zope.schema.interfaces.IInt, zope.webdav.interfaces.IWebDAVRequest),
+  ...    zope.webdav.interfaces.IDAVWidget)
+  True
+  >>> component.getGlobalSiteManager().unregisterAdapter(
+  ...    zope.webdav.widgets.TextDAVWidget,
+  ...    (zope.schema.interfaces.ITextLine,
+  ...     zope.webdav.interfaces.IWebDAVRequest),
+  ...    zope.webdav.interfaces.IDAVWidget)
+  True
+  >>> component.getGlobalSiteManager().unregisterUtility(
+  ...     ageProp, zope.webdav.interfaces.IDAVProperty, "{examplens:}age")
+  True
+  >>> component.getGlobalSiteManager().unregisterAdapter(DeadProperties)
+  True
+  >>> zope.webdav.testing.etreeTearDown()


Property changes on: zope.webdav/trunk/src/zope/webdav/datamodel.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/deadproperties.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/deadproperties.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/deadproperties.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,148 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Support for dead properties.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from BTrees.OOBTree import OOBTree
+
+from zope import interface
+from zope import component
+from zope.annotation.interfaces import IAnnotatable, IAnnotations
+
+import zope.webdav.interfaces
+
+_opaque_namespace_key = "zope.webdav.deadproperties.DAVOpaqueProperties"
+
+class OpaqueProperties(object):
+    """
+      >>> from zope.annotation.attribute import AttributeAnnotations
+      >>> from zope.interface.verify import verifyObject
+      >>> component.getGlobalSiteManager().registerAdapter(
+      ...     AttributeAnnotations, (IAnnotatable,), IAnnotations)
+
+    Initiial the object contains no dead properties.
+
+      >>> class DemoContent(object):
+      ...     interface.implements(IAnnotatable)
+      >>> resource = DemoContent()
+      >>> opaqueProperties = OpaqueProperties(resource)
+      >>> verifyObject(zope.webdav.interfaces.IOpaquePropertyStorage,
+      ...              opaqueProperties)
+      True
+      >>> annotations = IAnnotations(resource)
+      >>> list(annotations)
+      []
+      >>> list(opaqueProperties.getAllProperties())
+      []
+
+    `hasProperty` returns None since we haven't set any properties yet.
+
+      >>> opaqueProperties.hasProperty('{example:}testprop')
+      False
+      >>> opaqueProperties.getProperty('{example:}testprop') is None
+      True
+      >>> annotations = IAnnotations(resource)
+      >>> list(annotations)
+      []
+
+    Set the testprop property
+
+      >>> opaqueProperties.setProperty('{examplens:}testprop',
+      ...   '<E:testprop xmlns:E="examplens:">Test Property Value</E:testprop>')
+      >>> annotations = IAnnotations(resource)
+      >>> list(annotations[_opaque_namespace_key])
+      ['{examplens:}testprop']
+      >>> annotations[_opaque_namespace_key]['{examplens:}testprop']
+      '<E:testprop xmlns:E="examplens:">Test Property Value</E:testprop>'
+      >>> opaqueProperties.hasProperty('{examplens:}testprop')
+      True
+      >>> opaqueProperties.getProperty('{examplens:}testprop')
+      '<E:testprop xmlns:E="examplens:">Test Property Value</E:testprop>'
+      >>> list(opaqueProperties.getAllProperties())
+      ['{examplens:}testprop']
+      >>> opaqueProperties.hasProperty('{examplens:}testbadprop')
+      False
+
+    Now we will remove the property we just set up.
+
+      >>> opaqueProperties.removeProperty('{examplens:}testprop')
+      >>> annotations = IAnnotations(resource)
+      >>> list(annotations[_opaque_namespace_key])
+      []
+
+    Test multiple sets to this value.
+
+      >>> opaqueProperties.setProperty('{examplens:}prop0',
+      ...    '<E:prop0 xmlns:E="examplens:">PROP0</E:prop0>')
+      >>> opaqueProperties.setProperty('{examplens:}prop1',
+      ...    '<E:prop1 xmlns:E="examplens:">PROP1</E:prop1>')
+      >>> opaqueProperties.setProperty('{examplens:}prop2',
+      ...    '<E:prop2 xmlns:E="examplens:">PROP2</E:prop2>')
+      >>> list(opaqueProperties.getAllProperties())
+      ['{examplens:}prop0', '{examplens:}prop1', '{examplens:}prop2']
+
+      >>> opaqueProperties.removeProperty('{examplens:}prop0')
+      >>> opaqueProperties.removeProperty('{examplens:}prop1')
+      >>> list(opaqueProperties.getAllProperties())
+      ['{examplens:}prop2']
+
+    Cleanup this test.
+
+      >>> component.getGlobalSiteManager().unregisterAdapter(
+      ...     AttributeAnnotations, (IAnnotatable,), IAnnotations)
+      True
+
+    """
+    interface.implements(zope.webdav.interfaces.IOpaquePropertyStorage)
+    component.adapts(IAnnotatable)
+
+    _annotations = None
+
+    def __init__(self, context):
+        # __parent__ must be set in order for the security to work
+        self.__parent__ = context
+        annotations = IAnnotations(context)
+        oprops = annotations.get(_opaque_namespace_key)
+        if oprops is None:
+            self._annotations = annotations
+            oprops = OOBTree()
+
+        self._mapping = oprops
+
+    def _changed(self):
+        if self._annotations is not None:
+            self._annotations[_opaque_namespace_key] = self._mapping
+            self._annotations = None
+
+    def getAllProperties(self):
+        for tag in self._mapping.keys():
+            yield tag
+
+    def hasProperty(self, tag):
+        return tag in self._mapping
+
+    def getProperty(self, tag):
+        """Returns None."""
+        return self._mapping.get(tag, None)
+
+    def setProperty(self, tag, value):
+        self._mapping[tag] = value
+        self._changed()
+
+    def removeProperty(self, tag):
+        del self._mapping[tag]
+        self._changed()


Property changes on: zope.webdav/trunk/src/zope/webdav/deadproperties.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,239 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Common WebDAV error handling code.
+
+There are two types of error views. Ones that get caught by the WebDAV protocol
+and the other which escapes to the publisher. Both these views implement
+different interface which we can control through the WebDAV package via the
+IPublication.handleException method.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import schema
+from zope import component
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.app.http.interfaces import IHTTPException
+
+import zope.webdav.interfaces
+import zope.webdav.utils
+from zope.webdav.ietree import IEtree
+
+class DAVError(object):
+    interface.implements(zope.webdav.interfaces.IDAVErrorWidget)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    status = None
+
+    errors = []
+
+    propstatdescription = ""
+
+    responsedescription = ""
+
+
+class ConflictError(DAVError):
+    status = 409
+
+
+class ForbiddenError(DAVError):
+    status = 403
+
+
+class PropertyNotFoundError(DAVError):
+    status = 404
+
+
+class FailedDependencyError(DAVError):
+    # context is generally None for a failed dependency error.
+    status = 424
+
+
+class AlreadyLockedError(DAVError):
+    status = 423
+
+################################################################################
+#
+# Multi-status error view
+#
+################################################################################
+
+class MultiStatusErrorView(object):
+    component.adapts(zope.webdav.interfaces.IWebDAVErrors,
+                     zope.webdav.interfaces.IWebDAVRequest)
+    interface.implements(IHTTPException)
+
+    def __init__(self, error, request):
+        self.error = error
+        self.request = request
+
+    def __call__(self):
+        etree = component.getUtility(IEtree)
+        multistatus = zope.webdav.utils.MultiStatus()
+
+        seenContext = False
+        for error in self.error.errors:
+            if error.resource == self.error.context:
+                seenContext = True
+            
+            davwidget = component.getMultiAdapter(
+                (error, self.request), zope.webdav.interfaces.IDAVErrorWidget)
+
+            response = zope.webdav.utils.Response(
+                zope.webdav.utils.getObjectURL(error.resource, self.request))
+            response.status = davwidget.status
+            response.responsedescription += davwidget.responsedescription
+
+            multistatus.responses.append(response)
+
+        if not seenContext:
+            response = zope.webdav.utils.Response(
+                zope.webdav.utils.getObjectURL(
+                    self.error.context, self.request))
+            response.status = 424
+            multistatus.responses.append(response)
+
+        self.request.response.setStatus(207)
+        self.request.response.setHeader("content-type", "application/xml")
+        return etree.tostring(multistatus(), encoding = "utf-8")
+
+
+class WebDAVPropstatErrorView(object):
+    interface.implements(IHTTPException)
+    component.adapts(zope.webdav.interfaces.IWebDAVPropstatErrors,
+                     zope.webdav.interfaces.IWebDAVRequest)
+
+    def __init__(self, error, request):
+        self.error = error
+        self.request = request
+
+    def __call__(self):
+        etree = component.getUtility(IEtree)
+        multistatus = zope.webdav.utils.MultiStatus()
+
+        response = zope.webdav.utils.Response(
+            zope.webdav.utils.getObjectURL(self.error.context, self.request))
+        multistatus.responses.append(response)
+
+        for prop, error in self.error.items():
+            error_view = component.getMultiAdapter(
+                (error, self.request), zope.webdav.interfaces.IDAVErrorWidget)
+            propstat = response.getPropstat(error_view.status)
+
+            if zope.webdav.interfaces.IDAVProperty.providedBy(prop):
+                ## XXX - not tested - but is it needed?
+                prop = "{%s}%s" %(prop.namespace, prop.__name__)
+
+            propstat.properties.append(etree.Element(prop))
+            ## XXX - needs testing.
+            propstat.responsedescription += error_view.propstatdescription
+            response.responsedescription += error_view.responsedescription
+
+        self.request.response.setStatus(207)
+        self.request.response.setHeader("content-type", "application/xml")
+        return etree.tostring(multistatus(), encoding = "utf-8")
+
+################################################################################
+#
+# Some more generic exception view.
+#
+################################################################################
+
+class HTTPForbiddenError(object):
+    interface.implements(IHTTPException)
+    component.adapts(zope.webdav.interfaces.IForbiddenError,
+                     zope.webdav.interfaces.IHTTPRequest)
+
+    def __init__(self, error, request):
+        self.error = error
+        self.request = request
+
+    def __call__(self):
+        self.request.response.setStatus(403)
+        return ""
+
+
+class HTTPConflictError(object):
+    interface.implements(IHTTPException)
+    component.adapts(zope.webdav.interfaces.IConflictError,
+                     zope.webdav.interfaces.IHTTPRequest)
+
+    def __init__(self, error, request):
+        self.error = error
+        self.request = request
+
+    def __call__(self):
+        self.request.response.setStatus(409)
+        return ""
+
+
+class PreconditionFailed(object):
+    interface.implements(IHTTPException)
+    component.adapts(zope.webdav.interfaces.IPreconditionFailed,
+                     zope.webdav.interfaces.IHTTPRequest)
+
+    def __init__(self, error, request):
+        self.error = error
+        self.request = request
+
+    def __call__(self):
+        self.request.response.setStatus(412)
+        return ""
+
+
+class HTTPUnsupportedMediaTypeError(object):
+    interface.implements(IHTTPException)
+    component.adapts(zope.webdav.interfaces.IUnsupportedMediaType,
+                     zope.webdav.interfaces.IHTTPRequest)
+
+    def __init__(self, error, request):
+        self.error = error
+        self.request = request
+
+    def __call__(self):
+        self.request.response.setStatus(415)
+        return ""
+
+
+class UnprocessableError(object):
+    interface.implements(IHTTPException)
+    component.adapts(zope.webdav.interfaces.IUnprocessableError,
+                     zope.webdav.interfaces.IHTTPRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def __call__(self):
+        self.request.response.setStatus(422)
+        return ""
+
+
+class BadGateway(object):
+    interface.implements(IHTTPException)
+    component.adapts(zope.webdav.interfaces.IBadGateway,
+                     zope.webdav.interfaces.IHTTPRequest)
+
+    def __init__(self, error, request):
+        self.error = error
+        self.request = request
+
+    def __call__(self):
+        self.request.response.setStatus(502)
+        return ""


Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/exceptions/badrequest.pt
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/badrequest.pt	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/badrequest.pt	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,18 @@
+<html>
+
+  <body>
+
+    <div>
+
+      <h1>Bad Request</h1>
+
+      <p tal:define="msg view/message"
+         tal:condition="msg"
+         tal:content="msg"
+         />
+
+    </div>
+
+  </body>
+
+</html>

Added: zope.webdav/trunk/src/zope/webdav/exceptions/browser.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/browser.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/browser.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Common WebDAV error handling code.
+
+A lot of WebDAV requests can go badly wrong. If this is the case then return
+a snippet of HTML that could be displayed to the user describing what went
+left.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import component
+from zope.formlib import namedtemplate
+from zope.app.http.interfaces import IHTTPException
+from zope.app.pagetemplate import ViewPageTemplateFile
+
+import zope.webdav.interfaces
+
+class BadRequest(object):
+    interface.implements(IHTTPException)
+    component.adapts(zope.webdav.interfaces.IBadRequest,
+                     zope.webdav.interfaces.IHTTPRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def message(self):
+        return self.context.message
+
+    def __call__(self):
+        self.request.response.setStatus(400)
+        return self.template()
+
+    template = namedtemplate.NamedTemplate("default")
+
+
+default_template = namedtemplate.NamedTemplateImplementation(
+    ViewPageTemplateFile("badrequest.pt"), BadRequest)


Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/browser.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,174 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:browser="http://namespaces.zope.org/browser">
+
+  <!--
+      Error widgets
+    -->
+  <adapter
+     factory="zope.webdav.exceptions.ForbiddenError"
+     for="zope.webdav.interfaces.ForbiddenError
+          zope.webdav.interfaces.IWebDAVRequest"
+     provides="zope.webdav.interfaces.IDAVErrorWidget"
+     />
+
+  <adapter
+     factory="zope.webdav.exceptions.PropertyNotFoundError"
+     for="zope.webdav.interfaces.IPropertyNotFound
+          zope.webdav.interfaces.IWebDAVRequest"
+     provides="zope.webdav.interfaces.IDAVErrorWidget"
+     />
+
+  <adapter
+     factory="zope.webdav.exceptions.ConflictError"
+     for="zope.app.form.interfaces.IWidgetInputError
+          zope.webdav.interfaces.IWebDAVRequest"
+     provides="zope.webdav.interfaces.IDAVErrorWidget"
+     />
+
+  <adapter
+     factory="zope.webdav.exceptions.ConflictError"
+     for="zope.webdav.interfaces.IConflictError
+          zope.webdav.interfaces.IWebDAVRequest"
+     provides="zope.webdav.interfaces.IDAVErrorWidget"
+     />
+
+  <adapter
+     factory="zope.webdav.exceptions.AlreadyLockedError"
+     for="zope.webdav.interfaces.AlreadyLocked
+          zope.webdav.interfaces.IWebDAVRequest"
+     provides="zope.webdav.interfaces.IDAVErrorWidget"
+     />
+
+  <adapter
+     factory="zope.webdav.exceptions.FailedDependencyError"
+     for="zope.webdav.interfaces.IFailedDependency
+          zope.webdav.interfaces.IWebDAVRequest"
+     provides="zope.webdav.interfaces.IDAVErrorWidget"
+     />
+
+  <!--
+      Some default errors that can make it back to the publisher.
+    -->
+  <adapter
+     factory="zope.webdav.exceptions.UnprocessableError"
+     name="index.html"
+     />
+
+  <defaultView
+     for="zope.webdav.interfaces.IUnprocessableError"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     />
+
+  <adapter
+     factory="zope.webdav.exceptions.MultiStatusErrorView"
+     name="index.html"
+     />
+
+  <defaultView
+     for="zope.webdav.interfaces.IWebDAVErrors"
+     type="zope.webdav.interfaces.IWebDAVRequest"
+     name="index.html"
+     />
+
+  <adapter
+     factory="zope.webdav.exceptions.WebDAVPropstatErrorView"
+     name="index.html"
+     />
+
+  <defaultView
+     for="zope.webdav.interfaces.IWebDAVPropstatErrors"
+     type="zope.webdav.interfaces.IWebDAVRequest"
+     name="index.html"
+     />
+
+  <view
+     for="zope.webdav.interfaces.IPreconditionFailed"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     permission="zope.Public"
+     factory="zope.webdav.exceptions.PreconditionFailed"
+     />
+
+  <defaultView
+     for="zope.webdav.interfaces.IPreconditionFailed"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     />
+
+  <view
+     for="zope.webdav.interfaces.IBadGateway"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     permission="zope.Public"
+     factory="zope.webdav.exceptions.BadGateway"
+     />
+
+  <defaultView
+     for="zope.webdav.interfaces.IBadGateway"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     />
+
+  <view
+     for="zope.webdav.interfaces.IConflictError"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     permission="zope.Public"
+     factory="zope.webdav.exceptions.HTTPConflictError"
+     />
+
+  <defaultView
+     for="zope.webdav.interfaces.IConflictError"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     />
+
+  <view
+     for="zope.webdav.interfaces.IForbiddenError"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     permission="zope.Public"
+     factory="zope.webdav.exceptions.HTTPForbiddenError"
+     />
+
+  <defaultView
+     for="zope.webdav.interfaces.IForbiddenError"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     />
+
+  <view
+     for="zope.webdav.interfaces.IUnsupportedMediaType"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     permission="zope.Public"
+     factory="zope.webdav.exceptions.HTTPUnsupportedMediaTypeError"
+     />
+
+  <defaultView
+     for="zope.webdav.interfaces.IUnsupportedMediaType"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     />
+
+  <adapter
+     factory=".browser.default_template"
+     name="default"
+     />
+
+  <view
+     for="zope.webdav.interfaces.IBadRequest"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     permission="zope.Public"
+     factory="zope.webdav.exceptions.browser.BadRequest"
+     />
+
+  <defaultView
+     for="zope.webdav.interfaces.IBadRequest"
+     type="zope.publisher.interfaces.http.IHTTPRequest"
+     name="index.html"
+     />
+
+</configure>


Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/exceptions/ftests/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/ftests/__init__.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/ftests/__init__.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,16 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""
+$Id$
+"""


Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/ftests/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/exceptions/ftests/test_baseexceptions.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/ftests/test_baseexceptions.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/ftests/test_baseexceptions.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,49 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 Bad Request view.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from cStringIO import StringIO
+
+from zope.webdav.ftests import dav
+import zope.webdav.interfaces
+import zope.webdav.exceptions.browser
+
+class TestBadRequest(dav.DAVTestCase):
+
+    def test_badrequest(self):
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        error = zope.webdav.interfaces.BadRequest(
+            request, u"Some bad content in the request")
+
+        view = zope.webdav.exceptions.browser.BadRequest(error, request)
+        result = view()
+
+        self.assertEqual(request.response.getStatus(), 400)
+        self.assertEqual(
+            request.response.getHeader("content-type"), "text/html")
+        self.assert_("Some bad content in the request" in result)
+
+
+def test_suite():
+    suite = unittest.TestSuite((
+        unittest.makeSuite(TestBadRequest)))
+    return suite
+
+if __name__ == "__main__":
+    unittest.main(defaultTest = "test_suite")


Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/ftests/test_baseexceptions.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/exceptions/tests/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/tests/__init__.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/tests/__init__.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,16 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""
+$Id$
+"""


Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/tests/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_multiviews.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_multiviews.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_multiviews.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,249 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 multistatus views.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from cStringIO import StringIO
+
+from zope import interface
+from zope.interface.verify import verifyObject
+from zope import schema
+from zope import component
+from zope.traversing.browser.interfaces import IAbsoluteURL
+
+import zope.webdav.publisher
+from zope.webdav.testing import etreeSetup, etreeTearDown, assertXMLEqual
+
+class IResource(interface.Interface):
+
+    text = schema.TextLine(
+        title = u"Example Text Property")
+
+    intprop = schema.Int(
+        title = u"Example Int Property")
+
+
+class Resource(object):
+    interface.implements(IResource)
+
+    def __init__(self, text = u"", intprop = 0):
+        self.text = text
+        self.intprop = intprop
+
+
+class DummyResourceURL(object):
+    interface.implements(IAbsoluteURL)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    def __str__(self):
+        if getattr(self.context, "__parent__", None) is not None:
+            path = DummyResourceURL(self.context.__parent__, None)()
+        else:
+            path = ""
+
+        if getattr(self.context, "__name__", None) is not None:
+            path += "/" + self.context.__name__
+        elif IResource.providedBy(self.context):
+            path += "/resource"
+##         elif ICollection.providedBy(self.context):
+##             path += "/collection"
+        else:
+            raise ValueError("unknown context type")
+
+        return path
+
+    __call__ = __str__
+
+
+class TestRequest(zope.webdav.publisher.WebDAVRequest):
+
+    def __init__(self, properties = None, environ = {}):
+        if properties is not None:
+            body = """<?xml version="1.0" encoding="utf-8" ?>
+<propfind xmlns:D="DAV:" xmlns="DAV:">
+  %s
+</propfind>
+""" % properties
+        else:
+            body = ""
+
+        env = environ.copy()
+        env.setdefault("REQUEST_METHOD", "PROPFIND")
+        env.setdefault("CONTENT_TYPE", "text/xml")
+        env.setdefault("CONTENT_LENGTH", len(body))
+
+        super(TestRequest, self).__init__(StringIO(body), env)
+
+        # call processInputs now since we are in a unit test.
+        self.processInputs()
+
+
+class TestPropstatErrorView(unittest.TestCase):
+
+    def setUp(self):
+        super(TestPropstatErrorView, self).setUp()
+
+        etreeSetup()
+
+        gsm = component.getGlobalSiteManager()
+        gsm.registerAdapter(DummyResourceURL,
+                            (IResource, zope.webdav.interfaces.IWebDAVRequest))
+        gsm.registerAdapter(zope.webdav.exceptions.ForbiddenError,
+                            (zope.webdav.interfaces.IForbiddenError,
+                             zope.webdav.interfaces.IWebDAVRequest))
+
+    def tearDown(self):
+        super(TestPropstatErrorView, self).tearDown()
+
+        etreeTearDown()
+
+        gsm = component.getGlobalSiteManager()
+        gsm.unregisterAdapter(DummyResourceURL,
+                              (IResource,
+                               zope.webdav.interfaces.IWebDAVRequest))
+        gsm.unregisterAdapter(zope.webdav.exceptions.ForbiddenError,
+                              (zope.webdav.interfaces.IForbiddenError,
+                               zope.webdav.interfaces.IWebDAVRequest))
+
+    def test_propstat_interface(self):
+        resource = Resource()
+        error = zope.webdav.interfaces.WebDAVPropstatErrors(resource)
+        self.assertEqual(
+            verifyObject(zope.webdav.interfaces.IWebDAVPropstatErrors, error),
+            True)
+
+    def test_propstat_simple_errors(self):
+        resource = Resource()
+        error = zope.webdav.interfaces.WebDAVPropstatErrors(resource)
+        error["{DAV:}displayname"] = zope.webdav.interfaces.ForbiddenError(
+            resource, "{DAV:}displayname", message = u"readonly field")
+        request = TestRequest()
+
+        view = zope.webdav.exceptions.WebDAVPropstatErrorView(error, request)
+        result = view()
+
+        self.assertEqual(request.response.getStatus(), 207)
+        self.assertEqual(request.response.getHeader("content-type"), "application/xml")
+        assertXMLEqual(result, """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response>
+  <ns0:href>/resource</ns0:href>
+  <ns0:propstat>
+    <ns0:prop>
+      <ns0:displayname />
+    </ns0:prop>
+    <ns0:status>HTTP/1.1 403 Forbidden</ns0:status>
+  </ns0:propstat>
+</ns0:response></ns0:multistatus>""")
+
+
+class TestMSErrorView(unittest.TestCase):
+
+    def setUp(self):
+        super(TestMSErrorView, self).setUp()
+
+        etreeSetup()
+
+        gsm = component.getGlobalSiteManager()
+        gsm.registerAdapter(DummyResourceURL,
+                            (IResource, zope.webdav.interfaces.IWebDAVRequest))
+        gsm.registerAdapter(zope.webdav.exceptions.ForbiddenError,
+                            (zope.webdav.interfaces.IForbiddenError,
+                             zope.webdav.interfaces.IWebDAVRequest))
+        gsm.registerAdapter(zope.webdav.exceptions.PropertyNotFoundError,
+                            (zope.webdav.interfaces.IPropertyNotFound,
+                             zope.webdav.interfaces.IWebDAVRequest))
+
+    def tearDown(self):
+        super(TestMSErrorView, self).tearDown()
+
+        etreeTearDown()
+
+        gsm = component.getGlobalSiteManager()
+        gsm.unregisterAdapter(DummyResourceURL,
+                              (IResource,
+                               zope.webdav.interfaces.IWebDAVRequest))
+        gsm.unregisterAdapter(zope.webdav.exceptions.ForbiddenError,
+                              (zope.webdav.interfaces.IForbiddenError,
+                               zope.webdav.interfaces.IWebDAVRequest))
+        gsm.unregisterAdapter(zope.webdav.exceptions.PropertyNotFoundError,
+                              (zope.webdav.interfaces.IPropertyNotFound,
+                               zope.webdav.interfaces.IWebDAVRequest))
+
+    def test_multi_resource_error_interface(self):
+        resource = Resource()
+        error = zope.webdav.interfaces.WebDAVErrors(resource)
+        self.assertEqual(
+            verifyObject(zope.webdav.interfaces.IWebDAVErrors, error), True)
+
+    def test_multi_resource_error(self):
+        resource = Resource()
+        error = zope.webdav.interfaces.WebDAVErrors(resource)
+        error.append(zope.webdav.interfaces.ForbiddenError(
+            resource, "{DAV:}displayname", message = u"readonly field"))
+        request = TestRequest()
+
+        view = zope.webdav.exceptions.MultiStatusErrorView(error, request)
+        result = view()
+
+        self.assertEqual(request.response.getStatus(), 207)
+        self.assertEqual(request.response.getHeader("content-type"),
+                         "application/xml")
+
+        assertXMLEqual(result, """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response>
+  <ns0:href>/resource</ns0:href>
+  <ns0:status>HTTP/1.1 403 Forbidden</ns0:status>
+</ns0:response></ns0:multistatus>""")
+
+    def test_simple_seen_context(self):
+        resource = Resource()
+        resource1 = Resource()
+        resource1.__name__ = "secondresource"
+        error = zope.webdav.interfaces.WebDAVErrors(resource)
+        error.append(zope.webdav.interfaces.ForbiddenError(
+            resource, "{DAV:}displayname", message = u"readonly field"))
+        error.append(zope.webdav.interfaces.PropertyNotFound(
+            resource1, "{DAV:}getcontentlength", message = u"readonly field"))
+        request = TestRequest()
+
+        view = zope.webdav.exceptions.MultiStatusErrorView(error, request)
+        result = view()
+
+        self.assertEqual(request.response.getStatus(), 207)
+        self.assertEqual(request.response.getHeader("content-type"),
+                         "application/xml")
+
+        assertXMLEqual(result, """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response>
+  <ns0:href>/resource</ns0:href>
+  <ns0:status>HTTP/1.1 403 Forbidden</ns0:status>
+</ns0:response>
+<ns0:response>
+  <ns0:href>/secondresource</ns0:href>
+  <ns0:status>HTTP/1.1 404 Not Found</ns0:status>
+</ns0:response></ns0:multistatus>""")
+
+
+def test_suite():
+    suite = unittest.TestSuite((
+        unittest.makeSuite(TestPropstatErrorView),
+        unittest.makeSuite(TestMSErrorView),
+        ))
+    return suite


Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_multiviews.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,187 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 Bad Request view.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from cStringIO import StringIO
+from zope.app.testing.placelesssetup import PlacelessSetup
+from zope import component
+from zope import interface
+from zope.formlib.namedtemplate import INamedTemplate
+
+import zope.webdav.interfaces
+import zope.webdav.exceptions
+import zope.webdav.exceptions.browser
+from zope.webdav.publisher import WebDAVRequest
+from test_multiviews import TestRequest
+
+class TestExceptionViews(PlacelessSetup, unittest.TestCase):
+
+    def test_unprocessable(self):
+        request = WebDAVRequest(StringIO(""), {})
+        error = zope.webdav.interfaces.UnprocessableError(None)
+        view = zope.webdav.exceptions.UnprocessableError(error, request)
+
+        result = view()
+
+        self.assertEqual(request.response.getStatus(), 422)
+        self.assertEqual(result, "")
+
+    def test_precondition(self):
+        request = WebDAVRequest(StringIO(""), {})
+        error = zope.webdav.interfaces.PreconditionFailed(None)
+        view = zope.webdav.exceptions.PreconditionFailed(error, request)
+
+        result = view()
+
+        self.assertEqual(request.response.getStatus(), 412)
+        self.assertEqual(result, "")
+
+    def test_badgateway(self):
+        request = WebDAVRequest(StringIO(""), {})
+        error = zope.webdav.interfaces.BadGateway(None, request)
+        view = zope.webdav.exceptions.BadGateway(error, request)
+
+        result = view()
+
+        self.assertEqual(request.response.getStatus(), 502)
+        self.assertEqual(result, "")
+
+    def test_conflicterror(self):
+        request = WebDAVRequest(StringIO(""), {})
+        error = zope.webdav.interfaces.ConflictError(None, request)
+        view = zope.webdav.exceptions.HTTPConflictError(error, request)
+
+        result = view()
+
+        self.assertEqual(request.response.getStatus(), 409)
+        self.assertEqual(result, "")
+
+    def test_forbiddenerror(self):
+        request = WebDAVRequest(StringIO(""), {})
+        error = zope.webdav.interfaces.ForbiddenError(None, request)
+        view = zope.webdav.exceptions.HTTPForbiddenError(error, request)
+
+        result = view()
+
+        self.assertEqual(request.response.getStatus(), 403)
+        self.assertEqual(result, "")
+
+    def test_unsupportedmediatype(self):
+        request = WebDAVRequest(StringIO(""), {})
+        error = zope.webdav.interfaces.UnsupportedMediaType(None, request)
+        view = zope.webdav.exceptions.HTTPUnsupportedMediaTypeError(
+            error, request)
+
+        result = view()
+
+        self.assertEqual(request.response.getStatus(), 415)
+        self.assertEqual(result, "")
+
+
+class TestDAVErrors(unittest.TestCase):
+
+    def test_conflict_error(self):
+        errorview = zope.webdav.exceptions.ConflictError(None, None)
+
+        self.assertEqual(errorview.status, 409)
+        self.assertEqual(errorview.errors, [])
+        self.assertEqual(errorview.propstatdescription, "")
+        self.assertEqual(errorview.responsedescription, "")
+
+    def test_forbidden_error(self):
+        errorview = zope.webdav.exceptions.ForbiddenError(None, None)
+
+        self.assertEqual(errorview.status, 403)
+        self.assertEqual(errorview.errors, [])
+        self.assertEqual(errorview.propstatdescription, "")
+        self.assertEqual(errorview.responsedescription, "")
+
+    def test_propertyNotFound_error(self):
+        errorview = zope.webdav.exceptions.PropertyNotFoundError(None, None)
+
+        self.assertEqual(errorview.status, 404)
+        self.assertEqual(errorview.errors, [])
+        self.assertEqual(errorview.propstatdescription, "")
+        self.assertEqual(errorview.responsedescription, "")
+
+    def test_failedDependency_error(self):
+        errorview = zope.webdav.exceptions.FailedDependencyError(None, None)
+
+        self.assertEqual(errorview.status, 424)
+        self.assertEqual(errorview.errors, [])
+        self.assertEqual(errorview.propstatdescription, "")
+        self.assertEqual(errorview.responsedescription, "")
+
+    def test_alreadlocked_error(self):
+        errorview = zope.webdav.exceptions.AlreadyLockedError(None, None)
+
+        self.assertEqual(errorview.status, 423)
+        self.assertEqual(errorview.errors, [])
+        self.assertEqual(errorview.propstatdescription, "")
+        self.assertEqual(errorview.responsedescription, "")
+
+
+class DummyTemplate(object):
+
+    def __init__(self, context):
+        self.context = context
+
+    component.adapts(zope.webdav.exceptions.browser.BadRequest)
+    interface.implements(INamedTemplate)
+
+    def __call__(self):
+        return "Errr... bad request"
+
+
+class TestBadRequestView(unittest.TestCase):
+
+    def setUp(self):
+        component.getGlobalSiteManager().registerAdapter(
+            DummyTemplate, name = "default")
+
+    def tearDown(self):
+        component.getGlobalSiteManager().unregisterAdapter(
+            DummyTemplate, name = "default")
+
+    def test_badrequestView(self):
+        error = zope.webdav.interfaces.BadRequest(
+            None, message = u"Bad request data")
+        request = TestRequest()
+        view = zope.webdav.exceptions.browser.BadRequest(error, request)
+
+        result = view()
+        self.assertEqual(request.response.getStatus(), 400)
+        self.assertEqual(result, "Errr... bad request")
+
+    def test_badrequestView_message(self):
+        error = zope.webdav.interfaces.BadRequest(
+            None, message = u"Bad request data")
+        request = TestRequest()
+        view = zope.webdav.exceptions.browser.BadRequest(error, request)
+
+        self.assertEqual(view.message(), "Bad request data")
+
+
+def test_suite():
+    suite = unittest.TestSuite((
+        unittest.makeSuite(TestExceptionViews),
+        unittest.makeSuite(TestDAVErrors),
+        unittest.makeSuite(TestBadRequestView),
+        ))
+    return suite


Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/ftesting.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftesting.zcml	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftesting.zcml	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,42 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <class class=".ftests.dav.CollectionResource">
+    <require
+       permission="zope.View"
+       interface=".ftests.dav.ICollectionResource"
+       />
+    <require
+       permission="zope.ManageContent"
+       set_schema=".ftests.dav.ICollectionResource"
+       />
+  </class>
+
+  <class class=".ftests.dav.Resource">
+    <require
+       permission="zope.View"
+       interface=".ftests.dav.IResource"
+       />
+    <require
+       permission="zope.ManageContent"
+       set_schema=".ftests.dav.IResource"
+       />
+  </class>
+
+  <class class=".ftests.dav.EmptyCollectionResource">
+    <require
+       permission="zope.View"
+       interface="zope.app.container.interfaces.IReadContainer" 
+       />
+    <require
+       permission="zope.ManageContent"
+       interface="zope.app.container.interfaces.IWriteContainer"
+       />
+  </class>
+
+  <adapter
+     for=".ftests.dav.EmptyCollectionResource"
+     provides="zope.filerepresentation.interfaces.IWriteDirectory"
+     factory=".ftests.dav.EmptyWriteDirectory"
+     />
+
+</configure>


Property changes on: zope.webdav/trunk/src/zope/webdav/ftesting.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/ftests/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/__init__.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/__init__.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,17 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Collection of functional tests for zope.webdav
+
+$Id$
+"""


Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/ftests/dav.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/dav.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/dav.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,507 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Common utilities needed for writing WebDAV functional tests.
+
+XXX - This really needs some tidying up, also the setup should be moved to
+a global setup method so that individual tests can call it if they need to.
+
+$Id$
+"""
+
+from cStringIO import StringIO
+from BTrees.OOBTree import OOBTree
+
+import persistent
+import transaction
+
+from zope import interface
+from zope import component
+from zope import schema
+from zope.publisher.http import status_reasons
+from zope.app.testing.functional import HTTPTestCase, FunctionalTestSetup
+from zope.security.proxy import removeSecurityProxy
+from zope.app.folder.folder import Folder
+import zope.app.folder.interfaces
+from zope.app.file.file import File
+from zope.app.publication.http import HTTPPublication
+from zope.security.management import newInteraction, endInteraction
+from zope.security.testing import Principal, Participation
+
+import zope.webdav.interfaces
+from zope.webdav.publisher import WebDAVRequest
+from zope.webdav.ietree import IEtree
+from zope.webdav.properties import DAVProperty
+from zope.webdav.testing import assertXMLEqual
+import zope.webdav.coreproperties
+
+
+class IExamplePropertyStorage(interface.Interface):
+
+    exampleintprop = schema.Int(
+        title = u"Example Integer Property",
+        description = u"")
+
+    exampletextprop = schema.Text(
+        title = u"Example Text Property",
+        description = u"")
+
+exampleIntProperty = DAVProperty("{DAVtest:}exampleintprop",
+                                 IExamplePropertyStorage)
+
+exampleTextProperty = DAVProperty("{DAVtest:}exampletextprop",
+                                  IExamplePropertyStorage)
+
+
+ANNOT_KEY = "EXAMPLE_PROPERTY"
+class ExamplePropertyStorage(object):
+    interface.implements(IExamplePropertyStorage)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def _getproperty(name, default = None):
+        def get(self):
+            annots = getattr(removeSecurityProxy(self.context),
+                             "exampleannots", {})
+            return annots.get("%s_%s" %(ANNOT_KEY, name), default)
+        def set(self, value):
+            annots = getattr(removeSecurityProxy(self.context),
+                             "exampleannots", None)
+            if annots is None:
+                annots = removeSecurityProxy(
+                    self.context).exampleannots = OOBTree()
+            annots["%s_%s" %(ANNOT_KEY, name)] = value
+        return property(get, set)
+
+    exampleintprop = _getproperty("exampleintprop", default = 0)
+
+    exampletextprop = _getproperty("exampletextprop", default = u"")
+
+
+class TestWebDAVRequest(WebDAVRequest):
+    """."""
+    def __init__(self, elem = None):
+        if elem is not None:
+            body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propertyupdate xmlns:D="DAV:">
+  <D:set>
+    <D:prop />
+  </D:set>
+</D:propertyupdate>
+"""
+            f = StringIO(body)
+        else:
+            f = StringIO('')
+
+        super(TestWebDAVRequest, self).__init__(
+            f, {'CONTENT_TYPE': 'text/xml',
+                'CONTENT_LENGTH': len(f.getvalue()),
+                })
+
+        # processInputs to test request
+        self.processInputs()
+
+        # if elem is given insert it into the proppatch request.
+        if elem is not None:
+            self.xmlDataSource[0][0].append(elem)
+
+
+class EmptyCollectionResource(Folder):
+    """This collection doesn't contain any subitems
+    """
+    pass
+
+
+def EmptyWriteDirectory(context):
+    return None
+
+class ICollectionResource(zope.app.folder.interfaces.IFolder):
+
+    title = schema.TextLine(
+        title = u"Title",
+        description = u"Title of resource")
+
+
+class CollectionResource(Folder):
+    interface.implements(ICollectionResource)
+
+    title = None
+
+
+class IResource(interface.Interface):
+    """ """
+
+    title = schema.TextLine(
+        title = u"Title",
+        description = u"Title of resource")
+
+    content = schema.Bytes(
+        title = u"Content",
+        description = u"Content of the resource")
+
+class Resource(persistent.Persistent):
+    interface.implements(IResource)
+
+    title = None
+
+    content = None
+
+
+class DisplayNameStorageAdapter(object):
+    interface.implements(zope.webdav.coreproperties.IDAVDisplayname)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    @apply
+    def displayname():
+        def get(self):
+            return self.context.title
+        def set(self, value):
+            self.context.title = value
+        return property(get, set)
+
+
+class GETContentLength(object):
+    component.adapts(IResource, zope.webdav.interfaces.IWebDAVRequest)
+    interface.implements(zope.webdav.coreproperties.IDAVGetcontentlength)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    @property
+    def getcontentlength(self):
+        return len(self.context.content)
+
+
+class DeadProperties(object):
+    interface.implements(zope.webdav.interfaces.IOpaquePropertyStorage)
+
+    def __init__(self, context):
+        self.context = context
+        # This is only a test so aren't that concerned with security at this
+        # point.
+        self.annots = getattr(removeSecurityProxy(self.context), "annots", None)
+
+    def getAllProperties(self):
+        if self.annots is not None:
+            for tag in self.annots:
+                yield tag
+
+    def hasProperty(self, tag):
+        if self.annots is not None and tag in self.annots:
+            return True
+        return False
+
+    def getProperty(self, tag):
+        if self.annots is not None:
+            return self.annots.get(tag, None)
+        return None
+
+    def setProperty(self, tag, value):
+        if self.annots is None:
+            self.annots = removeSecurityProxy(self.context).annots = OOBTree()
+        self.annots[tag] = value
+
+    def removeProperty(self, tag):
+        del self.annots[tag]
+
+
+class DAVTestCase(HTTPTestCase):
+
+    def setUp(self):
+        super(DAVTestCase, self).setUp()
+
+        gsm = component.getGlobalSiteManager()
+
+        gsm.registerUtility(exampleIntProperty,
+                            name = "{DAVtest:}exampleintprop",
+                            provided = zope.webdav.interfaces.IDAVProperty)
+        gsm.registerUtility(exampleTextProperty,
+                            name = "{DAVtest:}exampletextprop",
+                            provided = zope.webdav.interfaces.IDAVProperty)
+        # this is to test the include and restricted allprop PROPFIND tests.
+        exampleTextProperty.restricted = False
+
+        gsm.registerAdapter(DisplayNameStorageAdapter,
+                            (IResource, zope.webdav.interfaces.IWebDAVRequest))
+        gsm.registerAdapter(DisplayNameStorageAdapter,
+                            (ICollectionResource,
+                             zope.webdav.interfaces.IWebDAVRequest))
+        gsm.registerAdapter(GETContentLength)
+
+        gsm.registerAdapter(DeadProperties, (IResource,))
+        gsm.registerAdapter(DeadProperties, (ICollectionResource,))
+
+        gsm.registerAdapter(ExamplePropertyStorage,
+                            (IResource, zope.webdav.interfaces.IWebDAVRequest),
+                            provided = IExamplePropertyStorage)
+
+    def tearDown(self):
+        gsm = component.getGlobalSiteManager()
+
+        gsm.unregisterUtility(exampleIntProperty,
+                              name = "{DAVtest:}exampleintprop",
+                              provided = zope.webdav.interfaces.IDAVProperty)
+        gsm.unregisterUtility(exampleTextProperty,
+                              name = "{DAVtest:}exampletextprop",
+                              provided = zope.webdav.interfaces.IDAVProperty)
+
+        gsm.unregisterAdapter(DisplayNameStorageAdapter,
+                            (IResource, zope.webdav.interfaces.IWebDAVRequest))
+        gsm.unregisterAdapter(DisplayNameStorageAdapter,
+                              (ICollectionResource,
+                               zope.webdav.interfaces.IWebDAVRequest))
+        gsm.unregisterAdapter(GETContentLength)
+
+        gsm.unregisterAdapter(DeadProperties, (IResource,))
+        gsm.unregisterAdapter(DeadProperties, (ICollectionResource,))
+
+        gsm.unregisterAdapter(ExamplePropertyStorage,
+                              (IResource,
+                               zope.webdav.interfaces.IWebDAVRequest),
+                              provided = IExamplePropertyStorage)
+
+        super(DAVTestCase, self).tearDown()
+
+        # logout just to make sure.
+        self.logout()
+
+    def login(self, principalid = "mgr"):
+        """Some locking methods new an interaction in order to lock a resource
+        """
+        principal = Principal(principalid)
+        participation = Participation(principal)
+        newInteraction(participation)
+
+    def logout(self):
+        """End the current interaction so we run the publish method.
+        """
+        endInteraction()
+
+    #
+    # Some methods for creating dummy content.
+    #
+    def createCollections(self, path):
+        collection = self.getRootFolder()
+        if path[0] == '/':
+            path = path[1:]
+        path = path.split('/')
+        for id in path[:-1]:
+            try:
+                collection = collection[id]
+            except KeyError:
+                collection[id] = CollectionResource()
+                collection = collection[id]
+        return collection, path[-1]
+
+    def createObject(self, path, obj):
+        collection, id = self.createCollections(path)
+        collection[id] = obj
+        transaction.commit()
+        return collection[id]
+
+    def addResource(self, path, content, title = None):
+        resource = Resource()
+        resource.content = content
+        resource.title = title
+        return self.createObject(path, resource)
+
+    def addCollection(self, path, title = None):
+        coll = CollectionResource()
+        coll.title = title
+        return self.createObject(path, coll)
+
+    def addFile(self, path, content, contentType):
+        resource = File(content, contentType)
+        return self.createObject(path, resource)
+
+    def createCollectionResourceStructure(self):
+        """  _____ rootFolder/ _____
+            /          \            \
+           r1       __ a/ __          b/
+                   /        \
+                   r2       r3
+        """
+        self.addResource("/r1", "first resource")
+        self.addResource("/a/r2", "second resource")
+        self.addResource("/a/r3", "third resource")
+        self.addCollection("/b")
+
+    def createFolderFileStructure(self):
+        """  _____ rootFolder/ _____
+            /          \            \
+           r1       __ a/ __          b/
+                   /        \
+                   r2       r3
+        """
+        self.addFile("/r1", "first resource", "test/plain")
+        self.addFile("/a/r2", "second resource", "text/plain")
+        self.addFile("/a/r3", "third resource", "text/plain")
+        self.createObject("/b", Folder())
+
+    #
+    # Now some methods for creating, and publishing request.
+    #
+    def makeRequest(self, path = "", basic = None, form = None, env = {},
+                    instream = None):
+        """Create a new WebDAV request
+        """
+        if instream is None:
+            instream = ""
+        environment = {"HTTP_HOST": "localhost",
+                       "HTTP_REFERER": "localhost"}
+        environment.update(env)
+
+        if instream and not environment.has_key("CONTENT_LENGTH"):
+            if getattr(instream, "getvalue", None) is not None:
+                instream = instream.getvalue()
+            environment["CONTENT_LENGTH"] = len(instream)
+
+        app = FunctionalTestSetup().getApplication()
+        request = app._request(path, instream, environment = environment,
+                               basic = basic, form = form,
+                               request = WebDAVRequest,
+                               publication = HTTPPublication)
+        return request
+
+    def checkPropfind(self, path = "/", basic = None, env = {},
+                      properties = None):
+        # - properties if set is a string containing the contents of the
+        #   propfind XML element has specified in the WebDAV spec.
+        if properties is not None:
+            body = """<?xml version="1.0" encoding="utf-8" ?>
+<propfind xmlns:D="DAV:" xmlns="DAV:">
+  %s
+</propfind>
+""" % properties
+            if not env.has_key("CONTENT_TYPE"):
+                env["CONTENT_TYPE"] = "application/xml"
+            env["CONTENT_LENGTH"] = len(body)
+        else:
+            body = ""
+            env["CONTENT_LENGTH"] = 0
+
+        if not env.has_key("REQUEST_METHOD"):
+            env["REQUEST_METHOD"] = "PROPFIND"
+
+        response = self.publish(path, basic = basic, env = env,
+                                request_body = body)
+
+        self.assertEqual(response.getStatus(), 207)
+        self.assertEqual(response.getHeader("content-type"), "application/xml")
+
+        respbody = response.getBody()
+        etree = component.getUtility(IEtree)
+        xmlbody = etree.fromstring(respbody)
+
+        return response, xmlbody
+
+    def checkProppatch(self, path = '/', basic = None, env = {},
+                       set_properties = None, remove_properties = None,
+                       handle_errors = True):
+        # - set_properties is None or a string that is the XML fragment
+        #   that should be included within the <D:set><D:prop> section of
+        #   a PROPPATCH request.
+        # - remove_properties is None or a string that is the XML fragment
+        #   that should be included within the <D:remove><D:prop> section of
+        #   a PROPPATCH request.
+        set_body = ""
+        if set_properties:
+            set_body = "<D:set><D:prop>%s</D:prop></D:set>" % set_properties
+
+        remove_body = ""
+        if remove_properties:
+            remove_body = "<D:remove><D:prop>%s</D:prop></D:remove>" % \
+                          remove_properties
+
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+  %s %s
+</D:propertyupdate>
+        """ %(set_body, remove_body)
+        body = body.encode("utf-8")
+
+        if not env.has_key("CONTENT_TYPE"):
+            env["CONTENT_TYPE"] = "application/xml"
+        env["CONTENT_LENGTH"] = len(body)
+
+        if not env.has_key("REQUEST_METHOD"):
+            env["REQUEST_METHOD"] = "PROPPATCH"
+
+        response = self.publish(path, basic = basic, env = env,
+                                request_body = body,
+                                handle_errors = handle_errors)
+
+        self.assertEqual(response.getStatus(), 207)
+        self.assertEqual(response.getHeader("content-type"), "application/xml")
+
+        respbody = response.getBody()
+        etree = component.getUtility(IEtree)
+        xmlbody = etree.fromstring(respbody)
+
+        return response, xmlbody
+
+    def assertMSPropertyValue(self, response, proptag, status = 200,
+                              tag = None, text_value = None,
+                              prop_element = None):
+        # For the XML response element make sure that the proptag belongs
+        # to the propstat element that has the given status.
+        #   - response - etree XML element
+        #   - proptag - tag name of the property we are testing
+        #   - status - integre status code
+        #   - tag - 
+        #   - text_value -
+        #   - propelement - etree Element that we compare with the property
+        #                   using zope.webdav.testing.assertXMLEqual
+        self.assertEqual(response.tag, "{DAV:}response")
+
+        # set to true if we found the property, under the correct status code
+        found_property = False
+
+        propstats = response.findall("{DAV:}propstat")
+        for propstat in propstats:
+            statusresp = propstat.findall("{DAV:}status")
+            self.assertEqual(len(statusresp), 1)
+
+            if statusresp[0].text == "HTTP/1.1 %d %s" %(
+                status, status_reasons[status]):
+                # make sure that proptag is in this propstat element
+                props = propstat.findall("{DAV:}prop/%s" % proptag)
+                self.assertEqual(len(props), 1)
+                prop = props[0]
+
+                # now test the the tag and text match this propstat element
+                if tag is not None:
+                    ## XXX - this is not right.
+                    ## self.assertEqual(len(prop), 1)
+                    self.assertEqual(prop[0].tag, tag)
+                else:
+                    self.assertEqual(len(prop), 0)
+                self.assertEqual(prop.text, text_value)
+
+                if prop_element is not None:
+                    assertXMLEqual(prop, prop_element)
+
+                found_property = True
+            else:
+                # make sure that proptag is NOT in this propstat element
+                props = propstat.findall("{DAV:}prop/%s" % proptag)
+                self.assertEqual(len(props), 0)
+
+        self.assert_(
+            found_property,
+            "The property %s doesn't exist for the status code %d" %(proptag,
+                                                                     status))


Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/dav.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/ftests/test_copymove.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_copymove.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_copymove.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,316 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Collection of functional tests for the LOCK method.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+import dav
+
+class COPYTestCase(dav.DAVTestCase):
+
+    def test_copy_file(self):
+        file = self.addFile("/sourcefile", "some file content", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "COPY",
+                   "DESTINATION": "http://localhost/destfile"})
+
+        self.assertEqual(response.getStatus(), 201)
+        self.assertEqual(response.getHeader("location"),
+                         "http://localhost/destfile")
+
+        self.assertEqual(self.getRootFolder()["destfile"].data,
+                         "some file content")
+        self.assertEqual(self.getRootFolder()["sourcefile"].data,
+                         "some file content")
+
+    def test_copy_file_nodest(self):
+        file = self.addFile("/sourcefile", "some file content", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "COPY"}, handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 400)
+
+    def test_copy_file_default_overwrite(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+        destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "COPY",
+                   "DESTINATION": "http://localhost/destfile"})
+
+        self.assertEqual(response.getStatus(), 204)
+        self.assertEqual(self.getRootFolder()["destfile"].data,
+                         "some source file")
+        self.assertEqual(self.getRootFolder()["sourcefile"].data,
+                         "some source file")
+
+    def test_copy_file_true_overwrite(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+        destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "COPY",
+                   "DESTINATION": "http://localhost/destfile",
+                   "OVERWRITE": "T"})
+
+        self.assertEqual(response.getStatus(), 204)
+        self.assertEqual(self.getRootFolder()["destfile"].data,
+                         "some source file")
+        self.assertEqual(self.getRootFolder()["sourcefile"].data,
+                         "some source file")
+
+    def test_copy_file_false_overwrite(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+        destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "COPY",
+                   "DESTINATION": "http://localhost/destfile",
+                   "OVERWRITE": "F"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 412)
+        self.assertEqual(self.getRootFolder()["destfile"].data,
+                         "some dest file")
+        self.assertEqual(self.getRootFolder()["sourcefile"].data,
+                         "some source file")
+
+    def test_copy_file_to_remove(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "COPY",
+                   "DESTINATION": "http://www.remove-server.com/destfile",
+                   "OVERWRITE": "T"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 502)
+
+    def test_copy_file_no_destparent(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "COPY",
+                   "DESTINATION": "http://localhost/bla/destfile",
+                   "OVERWRITE": "T"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 409)
+        self.assertEqual(list(self.getRootFolder().keys()), [u"sourcefile"])
+
+    def test_copy_to_same_file(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "COPY",
+                   "DESTINATION": "http://localhost/sourcefile",
+                   "OVERWRITE": "T"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 403)
+        self.assertEqual(self.getRootFolder()["sourcefile"].data,
+                         "some source file")
+
+    def test_bad_overwrite(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "COPY",
+                   "DESTINATION": "http://localhost/sourcefile",
+                   "OVERWRITE": "X"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 400)
+
+    def test_copy_folder(self):
+        self.createCollectionResourceStructure()
+
+        response = self.publish(
+            "/a", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "COPY",
+                   "DESTINATION": "http://localhost/c/"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 201)
+        self.assertEqual(response.getHeader("location"),
+                                            "http://localhost/c")
+        self.assertEqual(list(self.getRootFolder()["c"].keys()), [u"r2", u"r3"])
+
+
+class MOVETestCase(dav.DAVTestCase):
+    """These tests are very similar to the COPY tests. Actually I copied them
+    and modified them to work with MOVE.
+    """
+
+    def test_move_file(self):
+        file = self.addFile("/sourcefile", "some file content", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "MOVE",
+                   "DESTINATION": "http://localhost/destfile"})
+
+        self.assertEqual(response.getStatus(), 201)
+        self.assertEqual(response.getHeader("location"),
+                         "http://localhost/destfile")
+
+        self.assertEqual(self.getRootFolder()["destfile"].data,
+                         "some file content")
+        self.assert_("sourcefile" not in self.getRootFolder().keys())
+
+    def test_move_file_nodest(self):
+        file = self.addFile("/sourcefile", "some file content", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "MOVE"}, handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 400)
+
+    def test_move_file_default_overwrite(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+        destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "MOVE",
+                   "DESTINATION": "http://localhost/destfile"})
+
+        self.assertEqual(response.getStatus(), 204)
+        self.assertEqual(self.getRootFolder()["destfile"].data,
+                         "some source file")
+        self.assert_("sourcefile" not in self.getRootFolder().keys())
+
+    def test_move_file_true_overwrite(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+        destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "MOVE",
+                   "DESTINATION": "http://localhost/destfile",
+                   "OVERWRITE": "T"})
+
+        self.assertEqual(response.getStatus(), 204)
+        self.assertEqual(self.getRootFolder()["destfile"].data,
+                         "some source file")
+        self.assert_("sourcefile" not in self.getRootFolder().keys())
+
+    def test_move_file_false_overwrite(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+        destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "MOVE",
+                   "DESTINATION": "http://localhost/destfile",
+                   "OVERWRITE": "F"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 412)
+        self.assertEqual(self.getRootFolder()["destfile"].data,
+                         "some dest file")
+        self.assertEqual(self.getRootFolder()["sourcefile"].data,
+                         "some source file")
+
+    def test_move_file_to_remove(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "MOVE",
+                   "DESTINATION": "http://www.remove-server.com/destfile",
+                   "OVERWRITE": "T"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 502)
+
+    def test_move_file_no_destparent(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "MOVE",
+                   "DESTINATION": "http://localhost/bla/destfile",
+                   "OVERWRITE": "T"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 409)
+        self.assertEqual(list(self.getRootFolder().keys()), [u"sourcefile"])
+
+    def test_move_to_same_file(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "MOVE",
+                   "DESTINATION": "http://localhost/sourcefile",
+                   "OVERWRITE": "T"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 403)
+        self.assertEqual(self.getRootFolder()["sourcefile"].data,
+                         "some source file")
+
+    def test_bad_overwrite(self):
+        file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+        response = self.publish(
+            "/sourcefile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "MOVE",
+                   "DESTINATION": "http://localhost/sourcefile",
+                   "OVERWRITE": "X"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 400)
+
+    def test_move_folder(self):
+        self.createCollectionResourceStructure()
+
+        response = self.publish(
+            "/a", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "MOVE",
+                   "DESTINATION": "http://localhost/c/"},
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 201)
+        self.assertEqual(response.getHeader("location"),
+                                            "http://localhost/c")
+        self.assertEqual(list(self.getRootFolder()["c"].keys()), [u"r2", u"r3"])
+
+
+def test_suite():
+    return unittest.TestSuite((
+            unittest.makeSuite(COPYTestCase),
+            unittest.makeSuite(MOVETestCase),
+            ))
+
+
+if __name__ == "__main__":
+    unittest.main(defaultTest = "test_suite")
+


Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/test_copymove.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/ftests/test_mkcol.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_mkcol.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_mkcol.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,71 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Functional tests for MKCOL.
+
+$Id$
+"""
+import unittest
+import dav
+
+class MKCOLTestCase(dav.DAVTestCase):
+
+    def test_mkcol_not_folderish(self):
+        self.addResource("/bar/pt", u"<span />")
+        self.verifyStatus(path = "/bar/pt/foo", body = "", basic = "mgr:mgrpw",
+                          expected = 404)
+
+    def test_mkcol_not_folderish_existing(self):
+        self.addResource("/bar/pt", u"<span />")
+        self.verifyStatus(path = "/bar/pt", body = "", basic = "mgr:mgrpw",
+                          expected = 405)
+
+    def test_mkcol_not_existing(self):
+        self.verifyStatus(path = "/mkcol_test", body = "", basic = "mgr:mgrpw",
+                          expected = 201)
+
+    def test_mkcol_parent_not_existing(self):
+        self.verifyStatus(path = "/bar/mkcol_test", body = "",
+                          basic = "mgr:mgrpw", expected = 409)
+
+    def test_mkcol_existing(self):
+        self.addCollection("/bar/mkcol_test")
+        self.verifyStatus(path = "/bar", body = "", basic = "mgr:mgrpw",
+                          expected = 405)
+        
+    def test_mkcol_with_body(self):
+        self.verifyStatus(path = "/mkcol_test", body = "bla",
+                          basic = "mgr:mgrpw", expected = 415)
+
+    def test_mkcol_nowritedir(self):
+        self.createObject("/foo", dav.EmptyCollectionResource())
+        self.verifyStatus(path = "/foo/mkcol_test", body = "",
+                          basic = "mgr:mgrpw", expected = 403)
+
+    def verifyStatus(self, path, body, basic, expected = 201):
+        clen = len(body)
+        result = self.publish(path, basic, env = {"REQUEST_METHOD":"MKCOL",
+                                                  "CONTENT-LENGHT": clen},
+                              request_body = body, handle_errors = True)
+        self.assertEquals(result.getStatus(), expected)
+
+
+def test_suite():
+    suite = unittest.TestSuite((
+        unittest.makeSuite(MKCOLTestCase),
+        ))
+    return suite
+
+
+if __name__ == '__main__':
+    unittest.main()


Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/test_mkcol.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,489 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Collection of functional tests for PROPFIND zope.webdav
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from cStringIO import StringIO
+import transaction
+
+import dav
+from zope import component
+import zope.webdav.interfaces
+
+class PROPFINDTests(dav.DAVTestCase):
+
+    def test_badcontent(self):
+        response = self.publish("/", env = {"REQUEST_METHOD": "PROPFIND"},
+                                request_body = "some content",
+                                handle_errors = True)
+        self.assertEqual(response.getStatus(), 400)
+        self.assert_("PROPFIND requires a valid XML request"
+                     in response.getBody())
+
+    def test_invaliddepth(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propfind xmlns:D="DAV:">
+  <D:prop xmlns:R="http://ns.example.com/boxschema/">
+    <R:bigbox/>
+    <R:author/>
+    <R:DingALing/>
+    <R:Random/>
+  </D:prop>
+</D:propfind>"""
+        response = self.publish("/", env = {"REQUEST_METHOD": "PROPFIND",
+                                            "CONTENT_TYPE": "text/xml",
+                                            "DEPTH": "3",},
+                                request_body = StringIO(body),
+                                handle_errors = True)
+        self.assertEqual(response.getStatus(), 400)
+        self.assert_("Invalid Depth header supplied" in response.getBody())
+
+    def test_invalid_xml(self):
+        body = """<D:invalid xmlns:D="DAV:">Invalid</D:invalid>"""
+        response = self.publish("/", env = {"REQUEST_METHOD": "PROPFIND",
+                                            "CONTENT_TYPE": "text/xml",
+                                            "CONTENT_LENGTH": len(body),
+                                            },
+                                request_body = body,
+                                handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 422)
+
+    def test_simplepropfind_textxml(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<ff0:propfind xmlns:ff0="DAV:">
+  <ff0:prop>
+    <ff0:resourcetype/>
+  </ff0:prop>
+</ff0:propfind>"""
+        httpresponse, xmlbody = self.checkPropfind(
+            "/", env = {"DEPTH": "0", "CONTENT_TYPE": "text/xml"},
+            properties = "<D:prop><D:resourcetype/></D:prop>")
+        hrefs = xmlbody.findall("{DAV:}response/{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/")
+
+        props = xmlbody.findall("{DAV:}response/{DAV:}propstat/{DAV:}prop")
+        self.assertEqual(len(props), 1) # only one prop element
+        propel = props[0]
+
+        self.assertEqual(len(propel), 1) # only one property defined
+        self.assertEqual(propel[0].tag, "{DAV:}resourcetype")
+        self.assertEqual(propel[0].text, None)
+        self.assertEqual(len(propel[0]), 1)
+        self.assertEqual(propel[0][0].tag, "{DAV:}collection")
+
+    def test_propnames(self):
+        collection = self.addCollection("/coll")
+        
+        httpresponse, xmlbody = self.checkPropfind(
+            "/coll", env = {"DEPTH": "0"}, properties = "<D:propname />")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/coll/")
+
+        props = response.findall("{DAV:}propstat/{DAV:}prop")
+        self.assertEqual(len(props), 1)
+        propel = props[0]
+
+        self.assertMSPropertyValue(response, "{DAV:}resourcetype")
+        self.assertMSPropertyValue(response, "{DAV:}displayname")
+
+    def test_propnames_on_resource(self):
+        self.addResource("/r1", "some content")
+        
+        httpresponse, xmlbody = self.checkPropfind(
+            "/r1", env = {"DEPTH": "0"}, properties = "<D:propname />")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/r1")
+
+        props = response.findall("{DAV:}propstat/{DAV:}prop")
+        self.assertEqual(len(props), 1)
+
+        ## See README.txt for a list of properties defined for these tests.
+        self.assertMSPropertyValue(response, "{DAV:}resourcetype")
+        self.assertMSPropertyValue(response, "{DAV:}displayname")
+        self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop")
+        self.assertMSPropertyValue(response, "{DAV:}getcontentlength")
+        self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop")
+
+    def test_allprop(self):
+        collection = self.addCollection("/coll", title = u"Test Collection")
+        httpresponse, xmlbody = self.checkPropfind(
+            "/coll", env = {"DEPTH": "0"}, properties = "<D:allprop />")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/coll/")
+
+        props = response.findall("{DAV:}propstat/{DAV:}prop")
+        self.assertEqual(len(props), 1) # only one prop element
+
+        ## See README.txt for a list of properties defined for these tests.
+        self.assertMSPropertyValue(response, "{DAV:}resourcetype",
+                                   tag = "{DAV:}collection", text_value = None)
+        self.assertMSPropertyValue(response, "{DAV:}displayname",
+                                   text_value = "Test Collection")
+
+    def test_allprop_on_resource(self):
+        collection = self.addResource("/r1", "test resource content",
+                                      title = u"Test Resource")
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/r1", env = {"DEPTH": "0"}, properties = "<D:allprop />")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/r1")
+
+        props = response.findall("{DAV:}propstat/{DAV:}prop")
+        self.assertEqual(len(props), 1) # only one prop element
+
+        ## See README.txt for a list of properties defined for these tests.
+        self.assertMSPropertyValue(response, "{DAV:}resourcetype")
+        self.assertMSPropertyValue(response, "{DAV:}displayname",
+                                   text_value = "Test Resource")
+        self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop")
+        self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop",
+                                   text_value = "0")
+        self.assertMSPropertyValue(response, "{DAV:}getcontentlength",
+                                   text_value = "21")
+
+    def test_allprop_by_default(self):
+        self.addCollection("/coll")
+        httpresponse, xmlbody = self.checkPropfind("/coll",
+                                                   env = {"DEPTH": "0"},
+                                                   properties = "<D:prop />")
+        # the rest is copied from the previous code.
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/coll/")
+
+        props = response.findall("{DAV:}propstat/{DAV:}prop")
+        self.assertEqual(len(props), 1) # only one prop element
+
+        ## See README.txt for a list of properties defined for these tests.
+        self.assertMSPropertyValue(response, "{DAV:}resourcetype",
+                                   tag = "{DAV:}collection", text_value = None)
+        self.assertMSPropertyValue(response, "{DAV:}displayname")
+
+    def test_nobody_propfind(self):
+        self.addCollection("/coll", title = "Test Collection")
+        
+        httpresponse, xmlbody = self.checkPropfind("/coll",
+                                                   env = {"DEPTH": "0"})
+        # the rest is copied from the previous code.
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/coll/")
+
+        props = response.findall("{DAV:}propstat/{DAV:}prop")
+        self.assertEqual(len(props), 1) # only one prop element
+
+        ## See README.txt for a list of properties defined for these tests.
+        self.assertMSPropertyValue(response, "{DAV:}resourcetype",
+                                   tag = "{DAV:}collection")
+        self.assertMSPropertyValue(response, "{DAV:}displayname",
+                                   text_value = "Test Collection")
+
+    def test_notfound_property(self):
+        httpresponse, xmlbody = self.checkPropfind(
+            "/", env = {"DEPTH": "0"},
+            properties = "<D:prop><D:resourcetype /><D:missingproperty /></D:prop>")
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/")
+
+        self.assertMSPropertyValue(response, "{DAV:}resourcetype",
+                                   tag = "{DAV:}collection")
+        self.assertMSPropertyValue(response, "{DAV:}missingproperty",
+                                   status = 404)
+
+    def test_depthinf(self):
+        self.createCollectionResourceStructure()
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/", env = {"DEPTH": "infinity"},
+            properties = "<D:prop><D:resourcetype /></D:prop>")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 6)
+
+        # make sure we have all 200 status codes, and the hrefs differ
+        for response in responses:
+            propstats  = response.findall("{DAV:}propstat")
+            self.assertEqual(len(propstats), 1)
+            statusresp = response.findall("{DAV:}propstat/{DAV:}status")
+            self.assertEqual(len(statusresp), 1)
+            self.assertEqual(statusresp[0].text, "HTTP/1.1 200 OK")
+
+        hrefs = [href.text for href in
+                 xmlbody.findall("{DAV:}response/{DAV:}href")]
+        hrefs.sort()
+        self.assertEqual(hrefs, ['http://localhost/',
+                                 'http://localhost/a/',
+                                 'http://localhost/a/r2',
+                                 'http://localhost/a/r3',
+                                 'http://localhost/b/',
+                                 'http://localhost/r1'])
+
+    def test_depthone(self):
+        self.createCollectionResourceStructure()
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/", env = {"DEPTH": "1"},
+            properties = "<D:prop><D:resourcetype /></D:prop>")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 4)
+
+        # make sure we have all 200 status codes, and the hrefs differ
+        for response in responses:
+            propstats  = response.findall("{DAV:}propstat")
+            self.assertEqual(len(propstats), 1)
+            statusresp = response.findall("{DAV:}propstat/{DAV:}status")
+            self.assertEqual(len(statusresp), 1)
+            self.assertEqual(statusresp[0].text, "HTTP/1.1 200 OK")
+
+        hrefs = [href.text for href in
+                 xmlbody.findall("{DAV:}response/{DAV:}href")]
+        hrefs.sort()
+        self.assertEqual(hrefs, ['http://localhost/', 'http://localhost/a/',
+                                 'http://localhost/b/', 'http://localhost/r1'])
+
+    def test_opaque_properties(self):
+        file = self.addResource("/r", "some file content",
+                                title = "Test resource")
+
+        opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+        opaqueProperties.setProperty(
+            "{examplens:}testdeadprop",
+            """<E:testdeadprop xmlns:E="examplens:">TEST</E:testdeadprop>""")
+        transaction.commit()
+
+        properties = """<D:prop xmlns:E="examplens:">
+<D:resourcetype /><E:testdeadprop />
+</D:prop>
+"""
+        httpresponse, xmlbody = self.checkPropfind(
+            "/r", env = {"DEPTH": "0"}, properties = properties)
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+        propstats = response.findall("{DAV:}propstat")
+        self.assertEqual(len(propstats), 1)
+        props = propstats[0].findall("{DAV:}prop")
+        self.assertEqual(len(props), 1)
+
+        self.assertMSPropertyValue(response, "{DAV:}resourcetype")
+        self.assertMSPropertyValue(response, "{examplens:}testdeadprop",
+                                   text_value = "TEST")
+
+    def test_allprop_with_opaque_properties(self):
+        file = self.addResource("/r", "some file content",
+                                title = "Test Resource")
+
+        opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+        opaqueProperties.setProperty(
+            "{examplens:}testdeadprop",
+            """<E:testdeadprop xmlns:E="examplens:">TEST</E:testdeadprop>""")
+        transaction.commit()
+
+        properties = "<D:allprop />"
+        httpresponse, xmlbody = self.checkPropfind(
+            "/r", env = {"DEPTH": "0"}, properties = properties)
+
+    def test_unicode_title(self):
+        teststr = u"copyright \xa9 me"
+        file = self.addResource(u"/" + teststr, "some file content",
+                                title = teststr)
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/" + teststr.encode("utf-8"), env = {"DEPTH": "0",
+                                                  "CONTENT_TYPE": "text/xml"},
+            properties = "<D:prop><D:displayname /></D:prop>")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        self.assertMSPropertyValue(response, "{DAV:}displayname",
+                                   text_value = teststr)
+
+    def test_allprop_with_deadprops(self):
+        file = self.addResource("/r", "some content", title = "Test Resource")
+
+        opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+        opaqueProperties.setProperty("{deadprop:}deadprop",
+                                     """<X:deadprop xmlns:X="deadprop:">
+This is a dead property.</X:deadprop>""")
+        transaction.commit()
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/r", env = {"DEPTH": "0", "CONTENT_TYPE": "text/xml"},
+            properties = "<D:allprop />")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        self.assertMSPropertyValue(response, "{deadprop:}deadprop",
+                                   text_value = """
+This is a dead property.""")
+
+    def test_allprop_with_restricted(self):
+        file = self.addResource("/r", "some content", title = "Test Resource")
+
+        examplePropStorage = component.getMultiAdapter(
+            (file, dav.TestWebDAVRequest()), dav.IExamplePropertyStorage)
+        examplePropStorage.exampletextprop = "EXAMPLE TEXT PROP"
+        transaction.commit()
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/r", env = {"DEPTH": "0", "CONTENT_TYPE": "application/xml"},
+            properties = "<D:allprop />")
+
+        hrefs = xmlbody.findall("{DAV:}response/{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop",
+                                   text_value = "EXAMPLE TEXT PROP")
+
+        textprop = component.getUtility(zope.webdav.interfaces.IDAVProperty,
+                                        name = "{DAVtest:}exampletextprop")
+        textprop.restricted = True
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/r", env = {"DEPTH": "0", "CONTENT_TYPE": "application/xml"},
+            properties = "<D:allprop />")
+
+        hrefs = xmlbody.findall("{DAV:}response/{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+        props = xmlbody.findall("{DAV:}response/{DAV:}propstat/{DAV:}prop")
+        self.assertEqual(len(props), 1) # only one prop element
+
+        self.assertEqual([prop.tag for prop in
+                          props[0] if prop.tag == "{DAVtest:}exampletextprop"],
+                         [])
+
+    def test_allprop_with_include(self):
+        file = self.addResource("/r", "some content", title = "Test Resource")
+
+        examplePropStorage = component.getMultiAdapter(
+            (file, dav.TestWebDAVRequest()), dav.IExamplePropertyStorage)
+        examplePropStorage.exampletextprop = "EXAMPLE TEXT PROP"
+        transaction.commit()
+
+        textprop = component.getUtility(zope.webdav.interfaces.IDAVProperty,
+                                        name = "{DAVtest:}exampletextprop")
+        textprop.restricted = True
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/r", env = {"DEPTH": "0", "CONTENT_TYPE": "application/xml"},
+            properties = """<D:allprop />
+<D:include>
+  <Dtest:exampletextprop xmlns:Dtest="DAVtest:" />
+</D:include>
+""")
+
+        hrefs = xmlbody.findall("{DAV:}response/{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop",
+                                   text_value = "EXAMPLE TEXT PROP")
+
+    def test_propfind_onfile(self):
+        self.addFile("/testfile", "some file content", "text/plain")
+        httpresponse, xmlbody = self.checkPropfind(
+            "/testfile", env = {"DEPTH": "0"}, properties = "<D:allprop />")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/testfile")
+
+        propstats = response.findall("{DAV:}propstat")
+        self.assertEqual(len(propstats), 1)
+        props = propstats[0].findall("{DAV:}prop")
+        self.assertEqual(len(props), 1)
+
+        # all properties should be defined on a file.
+        self.assertMSPropertyValue(response, "{DAV:}resourcetype")
+        self.assertMSPropertyValue(response, "{DAV:}creationdate")
+        self.assertMSPropertyValue(response, "{DAV:}displayname")
+        self.assertMSPropertyValue(response, "{DAV:}getcontentlanguage")
+        self.assertMSPropertyValue(response, "{DAV:}getcontentlength",
+                                   text_value = "17")
+        self.assertMSPropertyValue(response, "{DAV:}getcontenttype",
+                                   text_value = "text/plain")
+        self.assertMSPropertyValue(response, "{DAV:}getetag")
+        self.assertMSPropertyValue(response, "{DAV:}getlastmodified")
+
+
+def test_suite():
+    return unittest.TestSuite((
+            unittest.makeSuite(PROPFINDTests),
+            ))
+
+if __name__ == "__main__":
+    unittest.main(defaultTest = "test_suite")


Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,300 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Collection of functional tests for PROPFIND zope.webdav
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+import transaction
+
+from zope import component
+
+import dav
+
+import zope.webdav.interfaces
+from zope.webdav.publisher import WebDAVRequest
+from zope.webdav.testing import assertXMLEqual
+
+class PROPPATCHTestCase(dav.DAVTestCase):
+
+    def test_badcontent(self):
+        response = self.publish("/", env = {"REQUEST_METHOD": "PROPPATCH"},
+                                request_body = "some content",
+                                handle_errors = True)
+        self.assertEqual(response.getStatus(), 400)
+        self.assert_(
+            "All PROPPATCH requests needs a XML body" in response.getBody())
+
+    def test_invalidxml(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propfind xmlns:D="DAV:">
+  <D:prop />
+</D:propfind>
+        """
+        response = self.publish("/", env = {"REQUEST_METHOD": "PROPPATCH",
+                                            "CONTENT_TYPE": "application/xml",
+                                            "CONTENT_LENGTH": len(body)},
+                                request_body = body,
+                                handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 422)
+        self.assertEqual(response.getBody(), "")
+
+    def test_setdisplayname(self):
+        set_properties = "<D:displayname>Test File</D:displayname>"
+        file = self.addResource("/r", "some content", "Test Resource")
+
+        self.assertEqual(self.getRootFolder()["r"].title, "Test Resource")
+
+        httpresponse, xmlbody = self.checkProppatch(
+            "/r", basic = "mgr:mgrpw", set_properties = set_properties)
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        self.assertMSPropertyValue(response, "{DAV:}displayname")
+
+        self.assertEqual(self.getRootFolder()["r"].title, u"Test File")
+
+    def test_readonly_property(self):
+        set_properties = "<D:getcontentlength>10</D:getcontentlength>"
+        file = self.addResource("/r", "some file content", "Test Resource")
+
+        httpresponse, xmlbody = self.checkProppatch(
+            "/r", basic = "mgr:mgrpw", set_properties = set_properties)
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+        self.assertMSPropertyValue(response, "{DAV:}getcontentlength",
+                                   status = 403)
+
+##     def test_property_notfound(self):
+##         set_properties = """
+##         <E:notfound xmlns:E="example:">Not Existent Prop</E:notfound>
+##         """
+##         file = self.addFile("/testfile", "some file content", "text/plain")
+
+##         httpresponse, xmlbody = self.checkProppatch(
+##             "/testfile", basic = "mgr:mgrpw", set_properties = set_properties)
+
+##         responses = xmlbody.findall("{DAV:}response")
+##         self.assertEqual(len(responses), 1)
+##         response = responses[0]
+##         hrefs = response.findall("{DAV:}href")
+##         self.assertEqual(len(hrefs), 1)
+##         self.assertEqual(hrefs[0].text, "http://localhost/testfile")
+
+##         self.assertMSPropertyValue(response, "{example:}notfound",
+##                                    status = 404)
+
+    def test_badinput(self):
+        set_properties = """
+        <E:exampleintprop xmlns:E="DAVtest:">BAD INT</E:exampleintprop>
+        """
+        resource = self.addResource("/testresource", "some resource content")
+
+        httpresponse, xmlbody = self.checkProppatch(
+            "/testresource", basic = "mgr:mgrpw",
+            set_properties = set_properties)
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/testresource")
+
+        self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop",
+                                   status = 409)
+
+    def test_badinput_plus_faileddep(self):
+        set_properties = """
+        <E:exampleintprop xmlns:E="DAVtest:">BAD INT</E:exampleintprop>
+        <E:exampletextprop xmlns:E="DAVtest:">
+          Test Property
+        </E:exampletextprop>
+        """
+        resource = self.addResource("/testresource", "some resource content")
+
+        request = WebDAVRequest(StringIO(""), {})
+        exampleStorage = component.getMultiAdapter((resource, request),
+                                                   dav.IExamplePropertyStorage)
+        # set up a default value to test later
+        exampleStorage.exampletextprop = u"Example Text Property"
+        transaction.commit()
+
+        httpresponse, xmlbody = self.checkProppatch(
+            "/testresource", basic = "mgr:mgrpw",
+            set_properties = set_properties)
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/testresource")
+
+        self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop",
+                                   status = 424)
+        self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop",
+                                   409)
+
+        exampleStorage = component.getMultiAdapter((resource, request),
+                                                   dav.IExamplePropertyStorage)
+        self.assertEqual(exampleStorage.exampletextprop,
+                         u"Example Text Property")
+
+    def test_proppatch_opaqueproperty(self):
+        set_properties = """<Z:Author xmlns:Z="http://ns.example.com/z39.50/">
+Jim Whitehead
+</Z:Author>
+        """
+        file = self.addResource("/r", "some content", "Test Resource")
+
+        httpresponse, xmlbody = self.checkProppatch(
+            "/r", basic = "mgr:mgrpw", set_properties = set_properties)
+
+        opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+        self.assertEqual(opaqueProperties.hasProperty(
+            "{http://ns.example.com/z39.50/}Author"), True)
+        assertXMLEqual(opaqueProperties.getProperty(
+            "{http://ns.example.com/z39.50/}Author"),
+            """<Z:Author xmlns:Z="http://ns.example.com/z39.50/">
+Jim Whitehead
+</Z:Author>""")
+
+    def test_set_multiple_dead_props(self):
+        set_properties = """<E:prop0 xmlns:E="example:">PROP0</E:prop0>
+<E:prop1 xmlns:E="example:">PROP0</E:prop1>
+<E:prop2 xmlns:E="example:">PROP0</E:prop2>
+<E:prop3 xmlns:E="example:">PROP0</E:prop3>
+        """
+
+        file = self.addResource("/r", "some content", "Test Resource")
+
+        httpresponse, xmlbody = self.checkProppatch(
+            "/r", basic = "mgr:mgrpw", set_properties = set_properties)
+
+        opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+        allprops = [tag for tag in opaqueProperties.getAllProperties()]
+        allprops.sort()
+        self.assertEqual(allprops, ["{example:}prop0", "{example:}prop1",
+                                    "{example:}prop2", "{example:}prop3"])
+
+    def test_unicode_title(self):
+        teststr = u"copyright \xa9 me"
+        set_properties = "<D:displayname>%s</D:displayname>" % teststr
+        file = self.addResource("/r", "some content", "Test Resource")
+
+        self.assertEqual(file.title, "Test Resource")
+
+        httpresponse, xmlbody = self.checkProppatch(
+            "/r", basic = "mgr:mgrpw", set_properties = set_properties)
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        self.assertMSPropertyValue(response, "{DAV:}displayname")
+
+    def test_remove_live_prop(self):
+        file = self.addResource("/r", "some content", "Test Resource")
+
+        opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+        opaqueProperties.setProperty("{deadprop:}deadprop",
+                                     """<X:deadprop xmlns:X="deadprop:">
+This is a dead property.</X:deadprop>""")
+        transaction.commit()
+
+        httpresponse, xmlbody = self.checkProppatch(
+            "/r", basic = "mgr:mgrpw",
+            remove_properties = """<E:exampleintprop xmlns:E="DAVtest:" />""")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+        propstat = response.findall("{DAV:}propstat")
+        self.assertEqual(len(propstat), 1)
+        propstat = propstat[0]
+
+        self.assertEqual(len(propstat), 2)
+
+        props = propstat.findall("{DAV:}prop")
+        self.assertEqual(len(props), 1)
+        self.assertEqual(len(props[0]), 1) # there is only one property.
+
+        self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop",
+                                   status = 409)
+
+    def test_remove_dead_prop(self):
+        proptag = "{deadprop:}deadprop"
+        file = self.addResource("/r", "some content", "Test Resource")
+
+        opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+        opaqueProperties.setProperty(proptag,
+                                     """<X:deadprop xmlns:X="deadprop:">
+This is a dead property.</X:deadprop>""")
+        transaction.commit()
+
+        httpresponse, xmlbody = self.checkProppatch(
+            "/r", basic = "mgr:mgrpw",
+            remove_properties = """<X:deadprop xmlns:X="deadprop:" />""")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        self.assertMSPropertyValue(response, proptag)
+
+        opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+        self.assertEqual(opaqueProperties.hasProperty(proptag), False)
+
+    def test_setting_unicode_title(self):
+        teststr = u"copyright \xa9 me"
+        file = self.addResource(u"/" + teststr, "some file content",
+                                title = "Old title")
+
+        httpresponse, xmlbody = self.checkProppatch(
+            "/" + teststr.encode("utf-8"), basic = "mgr:mgrpw",
+            set_properties = "<D:displayname>%s</D:displayname>" % teststr)
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        self.assertMSPropertyValue(response, "{DAV:}displayname")
+        resource = self.getRootFolder()[teststr]
+        self.assertEqual(resource.title, teststr)
+
+
+def test_suite():
+    return unittest.TestSuite((
+            unittest.makeSuite(PROPPATCHTestCase),
+            ))
+
+if __name__ == "__main__":
+    unittest.main(defaultTest = "test_suite")


Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/ftests/test_z3_locking.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_z3_locking.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_z3_locking.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,782 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Collection of functional tests for the LOCK method.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+import datetime
+import transaction
+from cStringIO import StringIO
+
+import zope.webdav.ftests.dav
+
+from zope import component
+from zope.app.publication.http import MethodNotAllowed
+from zope.app.testing.setup import addUtility
+import zope.locking.interfaces
+from zope.locking.utility import TokenUtility
+from zope.locking import tokens
+import zope.locking.utils
+from zope.security.interfaces import Unauthorized
+
+from zope.webdav.testing import assertXMLEqual
+from zope.webdav.interfaces import IDAVLockmanager
+import zope.webdav.publisher
+from zope.webdav.ietree import IEtree
+
+class LOCKNotAllowedTestCase(zope.webdav.ftests.dav.DAVTestCase):
+
+    def test_lock_file(self):
+        file = self.addFile("/testfilenotallowed",
+                            "some file content", "text/plain")
+        self.assertRaises(MethodNotAllowed, self.publish,
+            "/testfilenotallowed", basic = "mgr:mgrpw")
+
+    def test_lockingprops_noutility(self):
+        self.addFile("/testfile", "some file content", "text/plain")
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/testfile", env = {"DEPTH": "0"},
+            properties = """<D:prop>
+<D:supportedlock />
+<D:lockdiscovery />
+</D:prop>""")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        self.assertMSPropertyValue(response, "{DAV:}supportedlock",
+                                   status = 404)
+        self.assertMSPropertyValue(response, "{DAV:}lockdiscovery",
+                                   status = 404)
+
+
+class LOCKTestCase(zope.webdav.ftests.dav.DAVTestCase):
+
+    def _setup(self):
+        zope.webdav.ftests.dav.DAVTestCase.setUp(self)
+
+        self.oldnow = zope.locking.utils.now
+        def now():
+            return datetime.datetime(2006, 7, 27, 1, 9, 25)
+        zope.locking.utils.now = now
+
+    def setUp(self):
+        self._setup()
+
+        sitemanager = component.getSiteManager(self.getRootFolder())
+        self.utility = addUtility(sitemanager, "",
+                                  zope.locking.interfaces.ITokenUtility,
+                                  TokenUtility())
+        transaction.commit()
+
+    def _teardown(self):
+        zope.locking.utils.now = self.oldnow
+        del self.oldnow
+
+        zope.webdav.ftests.dav.DAVTestCase.tearDown(self)
+
+    def tearDown(self):
+        self._teardown()
+
+        sitemanager = component.getSiteManager(self.getRootFolder())
+        sitemanager.unregisterUtility(self.utility,
+                                      zope.locking.interfaces.ITokenUtility,
+                                      "")
+        del self.utility
+
+    def test_lock_file_unauthorized(self):
+        file = self.addFile("/testfile", "some file content", "text/plain")
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), False)
+
+        body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+
+        self.assertRaises(Unauthorized, self.publish, "/testfile",
+                          env = {"REQUEST_METHOD": "LOCK",
+                                 "DEPTH": "0",
+                                 "TIMEOUT": "Second-4100000000",
+                                 "CONTENT_TYPE": "text/xml"},
+                          request_body = body)
+
+    def test_invalid_xml(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:invalid xmlns:D="DAV:">Invalid XML</D:invalid>
+        """
+
+        response = self.publish(
+            "/", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "LOCK",
+                   "DEPTH": "0",
+                   "TIMEOUT": "Infinite, Second-4100000000",
+                   "CONTENT_TYPE": "text/xml"},
+            request_body = body,
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 422)
+
+    def test_lock_file(self):
+        file = self.addFile("/testfile", "some file content", "text/plain")
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), False)
+
+        body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+
+        response = self.publish(
+            "/testfile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "LOCK",
+                   "DEPTH": "0",
+                   "TIMEOUT": "Second-4100000000",
+                   "CONTENT_TYPE": "text/xml"},
+            request_body = body)
+
+        self.assertEqual(response.getStatus(), 200)
+        self.assertEqual(response.getHeader("content-type"), "application/xml")
+        locktoken = response.getHeader("lock-token")
+        self.assert_(locktoken and locktoken[0] == "<" and locktoken[-1] == ">")
+
+        expectedbody = """<ns0:prop xmlns:ns0="DAV:">
+<ns0:lockdiscovery>
+  <ns0:activelock>
+    <ns0:lockscope><ns0:exclusive /></ns0:lockscope>
+    <ns0:locktype><ns0:write /></ns0:locktype>
+    <ns0:depth>0</ns0:depth>
+    <ns0:owner>
+      <ns0:href>http://example.org/~ejw/contact.html</ns0:href>
+    </ns0:owner>
+    <ns0:timeout>Second-60800</ns0:timeout>
+    <ns0:locktoken>
+      <ns0:href>%s</ns0:href>
+    </ns0:locktoken>
+    <ns0:lockroot>http://localhost/testfile</ns0:lockroot>
+  </ns0:activelock>
+</ns0:lockdiscovery></ns0:prop>""" % locktoken[1:-1]
+
+        respbody = response.getBody()
+        assertXMLEqual(respbody, expectedbody)
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), True)
+
+    def test_already_exclusive_locked_file(self):
+        file = self.addFile("/testlockedfile",
+                            "some file content", "text/plain")
+
+        token = tokens.ExclusiveLock(file, "mgr")
+        token.duration = datetime.timedelta(seconds = 100)
+        self.utility.register(token)
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), True)
+        transaction.commit()
+
+        body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+
+        response = self.publish(
+            "/testlockedfile", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "LOCK",
+                   "DEPTH": "0",
+                   "TIMEOUT": "Second-4100000000",
+                   "CONTENT_TYPE": "text/xml"},
+            request_body = body,
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 207)
+        self.assertEqual(response.getHeader("content-type"), "application/xml")
+
+        expectedbody = """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response>
+  <ns0:href>http://localhost/testlockedfile</ns0:href>
+  <ns0:status>HTTP/1.1 423 Locked</ns0:status>
+</ns0:response></ns0:multistatus>"""
+
+        respbody = response.getBody()
+        assertXMLEqual(respbody, expectedbody)
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), True)
+
+    def test_lock_folder_depth_inf(self):
+        ## Test that when we lock a folder with depth infinity we the folder
+        ## and all sub resources lock tokens contain the same locktoken, and
+        ## lockroot.
+        self.createFolderFileStructure()
+
+        lockmanager = IDAVLockmanager(self.getRootFolder())
+        self.assertEqual(lockmanager.islocked(), False)
+
+        body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+
+        response = self.publish(
+            "/", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "LOCK",
+                   "DEPTH": "infinity",
+                   "TIMEOUT": "Second-4100000000",
+                   "CONTENT_TYPE": "text/xml"},
+            request_body = body)
+
+        self.assertEqual(response.getStatus(), 200)
+        self.assertEqual(response.getHeader("content-type"), "application/xml")
+        locktoken = response.getHeader("lock-token")
+        self.assert_(locktoken and locktoken[0] == "<" and locktoken[-1] == ">")
+        locktoken = locktoken[1:-1] # remove the <> characters
+
+        token = self.utility.get(self.getRootFolder())
+        self.assertEqual(
+            token.annotations["zope.webdav.lockingutils.info"]["token"],
+            locktoken)
+        token = self.utility.get(self.getRootFolder()["a"])
+        self.assertEqual(
+            token.annotations["zope.webdav.lockingutils.info"]["token"],
+            locktoken)
+
+        root = self.getRootFolder()
+        self.assertEqual(
+            IDAVLockmanager(root).getActivelock().locktoken[0], locktoken)
+        self.assertEqual(
+            IDAVLockmanager(root["a"]["r2"]).getActivelock().locktoken[0],
+            locktoken)
+
+        request = zope.webdav.publisher.WebDAVRequest(
+            StringIO(""), {"HTTP_HOST": "localhost"})
+
+        lockroot = IDAVLockmanager(root).getActivelock(request).lockroot
+        self.assertEqual(lockroot, "http://localhost")
+        lockroot = IDAVLockmanager(root["a"]["r3"]).getActivelock(
+            request).lockroot
+        self.assertEqual(lockroot, "http://localhost")
+
+    def test_lock_collection_depth_inf_withlockedsubitem(self):
+        self.login()
+        self.createFolderFileStructure()
+
+        lockmanager = IDAVLockmanager(self.getRootFolder()["a"]["r2"])
+        lockmanager.lock("exclusive", "write", """<D:owner>
+<D:href>http://webdav.org/</D:href></D:owner>""",
+                         duration = datetime.timedelta(100), depth = "0")
+        transaction.commit()
+        self.logout()
+
+        body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+
+        httpresponse = self.publish(
+            "/a", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "LOCK",
+                   "DEPTH": "infinity",
+                   "TIMEOUT": "Second-4100000000",
+                   "CONTENT_TYPE": "text/xml"},
+            request_body = body,
+            handle_errors = True)
+
+        etree = component.getUtility(IEtree)
+        xmlbody = etree.fromstring(httpresponse.getBody())
+
+        self.assertEqual(httpresponse.getStatus(), 207)
+        self.assertEqual(
+            httpresponse.getHeader("content-type"), "application/xml")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 2)
+
+        for response in responses:
+            hrefs = response.findall("{DAV:}href")
+            self.assertEqual(len(hrefs), 1)
+            statusresp = response.findall("{DAV:}status")
+            self.assertEqual(len(statusresp), 1)
+            statusresp = statusresp[0].text
+
+            if hrefs[0].text == "http://localhost/a/":
+                self.assertEqual(
+                    statusresp, "HTTP/1.1 424 Failed Dependency")
+            elif hrefs[0].text == "http://localhost/a/r2":
+                self.assertEqual(
+                    statusresp, "HTTP/1.1 423 Locked")
+            else:
+                self.fail("unexpected reponse with href: %s" % hrefs[0].text)
+
+        lockmanager = IDAVLockmanager(self.getRootFolder()["a"])
+        self.assertEqual(lockmanager.islocked(), False)
+
+        # this object was already locked.
+        lockmanager = IDAVLockmanager(self.getRootFolder()["a"]["r2"])
+        self.assertEqual(lockmanager.islocked(), True)
+
+    def test_lock_file_then_propfind(self):
+        ## Test that the locking properties get updated correctly whenever a
+        ## resource is locked. We do this by performing a PROPFIND on the
+        ## locked resource.
+        self.createFolderFileStructure()
+
+        body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+
+        response = self.publish(
+            "/a", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "LOCK",
+                   "DEPTH": "infinity",
+                   "TIMEOUT": "Second-4100000000",
+                   "CONTENT_TYPE": "text/xml"},
+            request_body = body)
+
+        self.assertEqual(response.getStatus(), 200)
+        self.assertEqual(response.getHeader("content-type"), "application/xml")
+        locktoken = response.getHeader("lock-token")
+        self.assert_(locktoken and locktoken[0] == "<" and locktoken[-1] == ">")
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/a/r2", env = {"DEPTH": "0", "CONTENT_TYPE": "text/xml"},
+            properties = "<D:allprop />")
+
+        hrefs = xmlbody.findall("{DAV:}response/{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/a/r2")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        propstat = response.findall("{DAV:}propstat")
+        self.assertEqual(len(propstat), 1)
+        propstat = propstat[0]
+
+        status = propstat.findall("{DAV:}status")
+        self.assertEqual(len(status), 1)
+        status = status[0]
+        self.assertEqual(status.text, "HTTP/1.1 200 OK")
+
+        props = propstat.findall("{DAV:}prop")
+        self.assertEqual(len(props), 1)
+        props = props[0]
+
+        supportedlock = props.findall("{DAV:}supportedlock")
+        self.assertEqual(len(supportedlock), 1)
+        assertXMLEqual(supportedlock[0], """<ns0:supportedlock xmlns:ns0="DAV:">
+<ns0:lockentry xmlns:ns0="DAV:">
+  <ns0:lockscope xmlns:ns0="DAV:"><ns0:exclusive xmlns:ns0="DAV:"/></ns0:lockscope>
+  <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+</ns0:lockentry>
+<ns0:lockentry xmlns:ns0="DAV:">
+  <ns0:lockscope xmlns:ns0="DAV:"><ns0:shared xmlns:ns0="DAV:"/></ns0:lockscope>
+  <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+</ns0:lockentry></ns0:supportedlock>""")
+
+        lockdiscovery = props.findall("{DAV:}lockdiscovery")
+        self.assertEqual(len(lockdiscovery), 1)
+        assertXMLEqual(lockdiscovery[0], """<ns0:lockdiscovery xmlns:ns0="DAV:">
+<ns0:activelock xmlns:ns0="DAV:">
+  <ns0:lockscope xmlns:ns0="DAV:"><ns0:exclusive xmlns:ns0="DAV:"/></ns0:lockscope>
+  <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+  <ns0:depth xmlns:ns0="DAV:">infinity</ns0:depth>
+  <ns0:owner xmlns:D="DAV:">
+    <ns0:href>http://example.org/~ejw/contact.html</ns0:href>
+  </ns0:owner>
+  <ns0:timeout xmlns:ns0="DAV:">Second-60800</ns0:timeout>
+  <ns0:locktoken xmlns:ns0="DAV:">
+    <ns0:href xmlns:ns0="DAV:">%s</ns0:href>
+  </ns0:locktoken>
+  <ns0:lockroot xmlns:ns0="DAV:">http://localhost/a</ns0:lockroot>
+</ns0:activelock></ns0:lockdiscovery>""" % locktoken[1:-1])
+
+    def test_recursive_lock(self):
+        self.createFolderFileStructure()
+
+        body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+
+        response = self.publish(
+            "/a", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "LOCK",
+                   "DEPTH": "infinity",
+                   "TIMEOUT": "Second-4100000000",
+                   "CONTENT_TYPE": "text/xml"},
+            request_body = body)
+
+        self.assertEqual(response.getStatus(), 200)
+        self.assertEqual(response.getHeader("content-type"), "application/xml")
+        locktoken = response.getHeader("lock-token")
+        self.assert_(locktoken and locktoken[0] == "<" and locktoken[-1] == ">")
+
+        rootfolder = self.getRootFolder()["a"]
+        subresource = rootfolder["r2"]
+
+        request = zope.webdav.publisher.WebDAVRequest(
+            StringIO(""), {"HTTP_HOST": "localhost"})
+
+        lockmanager = IDAVLockmanager(rootfolder)
+        self.assertEqual(lockmanager.getActivelock(request).lockroot,
+                         "http://localhost/a")
+        self.assertEqual(
+            lockmanager.getActivelock().locktoken[0], locktoken[1:-1])
+
+        lockmanager = IDAVLockmanager(subresource)
+        self.assertEqual(lockmanager.getActivelock(request).lockroot,
+                         "http://localhost/a")
+        self.assertEqual(
+            lockmanager.getActivelock().locktoken[0], locktoken[1:-1])
+
+    def test_lock_invalid_depth(self):
+        file = self.addResource("/testresource",
+                                "some file content", "Test Resource")
+
+        body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+
+        response = self.publish(
+            "/testresource", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "LOCK",
+                   "DEPTH": "1",
+                   "TIMEOUT": "Infinite, Second-4100000000",
+                   "CONTENT_TYPE": "text/xml"},
+            request_body = body,
+            handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 400)
+
+    def test_refresh_lock(self):
+        self.login()
+        self.createCollectionResourceStructure()
+
+        lockmanager = IDAVLockmanager(self.getRootFolder()["a"])
+        owner = """<D:owner xmlns:D="DAV:">
+<D:href>mailto:michael</D:href>
+</D:owner>"""
+        lockmanager.lock(u"exclusive", u"write", owner,
+                         datetime.timedelta(seconds = 1000), "infinity")
+        locktoken = lockmanager.getActivelock().locktoken[0]
+        self.assertEqual(lockmanager.getActivelock().timeout, u"Second-1000")
+        transaction.commit()
+        self.logout()
+
+        response = self.publish("/a/r2", basic = "mgr:mgrpw",
+                                env = {"REQUEST_METHOD": "LOCK",
+                                       "TIMEOUT": "Second-3600",
+                                       "IF": "<%s>" % locktoken})
+
+        self.assertEqual(response.getStatus(), 200)
+        assertXMLEqual(response.getBody(), """<ns0:prop xmlns:ns0="DAV:">
+<ns0:lockdiscovery xmlns:ns0="DAV:">
+  <ns0:activelock xmlns:ns0="DAV:">
+    <ns0:lockscope xmlns:ns0="DAV:"><ns0:exclusive xmlns:ns0="DAV:"/></ns0:lockscope>
+    <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+    <ns0:depth xmlns:ns0="DAV:">infinity</ns0:depth>
+    <ns0:owner xmlns:D="DAV:">
+<ns0:href>mailto:michael</ns0:href>
+    </ns0:owner>
+    <ns0:timeout xmlns:ns0="DAV:">Second-3600</ns0:timeout>
+    <ns0:locktoken xmlns:ns0="DAV:"><ns0:href xmlns:ns0="DAV:">%s</ns0:href></ns0:locktoken>
+    <ns0:lockroot xmlns:ns0="DAV:">http://localhost/a</ns0:lockroot>
+  </ns0:activelock>
+</ns0:lockdiscovery></ns0:prop>""" % locktoken)
+
+    def test_invalid_lock_request_uri(self):
+        self.login()
+        file = self.addResource("/testresource", "some file content",
+                                "Test Resource")
+
+        lockmanager = IDAVLockmanager(file)
+        lockmanager.lock(u"exclusive", u"write", u"Michael",
+                         datetime.timedelta(seconds = 3600), '0')
+        transaction.commit()
+        self.logout()
+
+        response = self.publish("/testresource", basic = "mgr:mgrpw",
+                                env = {"REQUEST_METHOD": "LOCK",
+                                       "TIMEOUT": "Second-3600",
+                                       "IF": "<BADLOCKTOKEN>"},
+                                handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 412)
+
+    def test_no_lock_request_uri(self):
+        self.login()
+        file = self.addResource("/testresource", "some file content",
+                                "Test Resource")
+
+        lockmanager = IDAVLockmanager(file)
+        lockmanager.lock(u"exclusive", u"write", u"Michael",
+                         datetime.timedelta(seconds = 3600), '0')
+        transaction.commit()
+        self.logout()
+
+        response = self.publish("/testresource", basic = "mgr:mgrpw",
+                                env = {"REQUEST_METHOD": "LOCK",
+                                       "TIMEOUT": "Second-3600"},
+                                handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 412)
+
+    def test_not_locked_resource(self):
+        file = self.addResource("/testresource", "some file content",
+                                "Test Resource")
+
+        response = self.publish("/testresource", basic = "mgr:mgrpw",
+                                env = {"REQUEST_METHOD": "LOCK",
+                                       "TIMEOUT": "Second-3600",
+                                       "IF": "<BADLOCKTOKEN>"},
+                                handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 412)
+
+
+class UNLOCKTestCase(zope.webdav.ftests.dav.DAVTestCase):
+
+    def setUp(self):
+        super(UNLOCKTestCase, self).setUp()
+
+        sitemanager = component.getSiteManager(self.getRootFolder())
+        self.utility = addUtility(sitemanager, "",
+                                  zope.locking.interfaces.ITokenUtility,
+                                  TokenUtility())
+
+    def tearDown(self):
+        del self.utility
+        super(UNLOCKTestCase, self).tearDown()
+
+    def test_unlock_file(self):
+        self.login()
+        file = self.addFile("/testfile", "some file content", "text/plain")
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), False)
+        lockmanager.lock(scope = "exclusive", type = "write",
+                         owner = """<D:owner xmlns:D="DAV:">
+  <D:href>mailto:michael at linux</D:href>
+</D:owner>""",
+                         duration = datetime.timedelta(100),
+                         depth = "0")
+        transaction.commit()
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), True)
+        locktoken = lockmanager.getActivelock().locktoken[0]
+
+        # end the current interaction
+        self.logout()
+
+        response = self.publish("/testfile", basic = "mgr:mgrpw",
+                                env = {"REQUEST_METHOD": "UNLOCK",
+                                       "LOCK_TOKEN": "<%s>" % locktoken})
+
+        self.assertEqual(response.getStatus(), 204)
+        self.assertEqual(response.getBody(), "")
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), False)
+
+    def test_unlock_file_bad_token(self):
+        self.login()
+        file = self.addFile("/testfile", "some file content", "text/plain")
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), False)
+        lockmanager.lock(scope = "exclusive", type = "write",
+                         owner = """<D:owner xmlns:D="DAV:">
+  <D:href>mailto:michael at linux</D:href>
+</D:owner>""",
+                         duration = datetime.timedelta(100),
+                         depth = "0")
+        transaction.commit()
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), True)
+        locktoken = "badtoken"
+
+        # end the current interaction
+        self.logout()
+
+        response = self.publish("/testfile", basic = "mgr:mgrpw",
+                                env = {"REQUEST_METHOD": "UNLOCK",
+                                       "LOCK_TOKEN": "<%s>" % locktoken},
+                                handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 409)
+        self.assertEqual(response.getBody(), "")
+
+        # file should be still locked
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), True)
+
+    def test_unlock_file_no_token(self):
+        self.login()
+        file = self.addFile("/testfile", "some file content", "text/plain")
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), False)
+        lockmanager.lock(scope = "exclusive", type = "write",
+                         owner = """<D:owner xmlns:D="DAV:">
+  <D:href>mailto:michael at linux</D:href>
+</D:owner>""",
+                         duration = datetime.timedelta(100),
+                         depth = "0")
+        transaction.commit()
+
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), True)
+
+        # end the current interaction
+        self.logout()
+
+        response = self.publish("/testfile", basic = "mgr:mgrpw",
+                                env = {"REQUEST_METHOD": "UNLOCK"},
+                                handle_errors = True)
+
+        self.assertEqual(response.getStatus(), 400)
+        self.assert_("No lock-token header supplied" in response.getBody())
+
+        # file should be still locked
+        lockmanager = IDAVLockmanager(file)
+        self.assertEqual(lockmanager.islocked(), True)
+
+    def test_lock_folder_depth_inf_then_unlock(self):
+        self.createFolderFileStructure()
+
+        body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+
+        response = self.publish(
+            "/a", basic = "mgr:mgrpw",
+            env = {"REQUEST_METHOD": "LOCK",
+                   "DEPTH": "infinity",
+                   "TIMEOUT": "Second-4100000000",
+                   "CONTENT_TYPE": "text/xml"},
+            request_body = body)
+
+        self.assertEqual(response.getStatus(), 200)
+        self.assertEqual(response.getHeader("content-type"), "application/xml")
+        locktoken = response.getHeader("lock-token")
+        self.assert_(locktoken and locktoken[0] == "<" and locktoken[-1] == ">")
+        locktoken = locktoken[1:-1] # remove the <> characters
+
+        response = self.publish("/a/r2", basic = "mgr:mgrpw",
+                                env = {"REQUEST_METHOD": "UNLOCK",
+                                       "LOCK_TOKEN": "<%s>" % locktoken})
+
+        self.assertEqual(response.getStatus(), 204)
+        self.assertEqual(response.getBody(), "")
+
+        lockmanager = IDAVLockmanager(self.getRootFolder()["a"]["r2"])
+        self.assertEqual(lockmanager.islocked(), False)
+
+        lockmanager = IDAVLockmanager(self.getRootFolder())
+        self.assertEqual(lockmanager.islocked(), False)
+
+    def test_supportedlock_prop(self):
+        file = self.addFile("/testfile", "some file content", "text/plain")
+        httpresponse, xmlbody = self.checkPropfind(
+            "/testfile", properties = "<D:prop><D:supportedlock /></D:prop>")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/testfile")
+
+        propstats = response.findall("{DAV:}propstat")
+        self.assertEqual(len(propstats), 1)
+        props = propstats[0].findall("{DAV:}prop")
+        self.assertEqual(len(props), 1)
+
+        self.assertEqual(len(props[0]), 1)
+
+        expected = """<D:supportedlock xmlns:D="DAV:">
+<D:lockentry>
+  <D:lockscope><D:exclusive /></D:lockscope>
+  <D:locktype><D:write /></D:locktype>
+</D:lockentry>
+<D:lockentry>
+  <D:lockscope><D:shared /></D:lockscope>
+  <D:locktype><D:write /></D:locktype>
+</D:lockentry></D:supportedlock>"""
+        self.assertMSPropertyValue(response, "{DAV:}supportedlock",
+                                   tag = "{DAV:}lockentry",
+                                   prop_element = expected)
+
+
+def test_suite():
+    return unittest.TestSuite((
+            unittest.makeSuite(LOCKNotAllowedTestCase),
+            unittest.makeSuite(LOCKTestCase),
+            unittest.makeSuite(UNLOCKTestCase),
+            ))
+
+
+if __name__ == "__main__":
+    unittest.main(defaultTest = "test_suite")


Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/test_z3_locking.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/ietree-configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ietree-configure.zcml	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ietree-configure.zcml	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,35 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <!--
+      ElementTree support for Zope (you probable what to edit this)
+
+      This file also configures which ElementTree engine to use for all the
+      zope.webdav unit tests also. See zope.webdav.testing.
+
+      The following ElementTree engines are supported.
+
+      zope.webdav.zetree.EtreeEtree - original elementtree python module
+
+      zope.webdav.zetree.LxmlEtree - the fast lxml engine.
+
+      zope.webdav.zetree.EtreePy25 - the original elementtree has included in
+                                     python2.5 and above.
+    -->
+
+  <utility
+     factory="zope.webdav.zetree.EtreeEtree"
+     />
+
+<!--
+  <utility
+     factory="zope.webdav.zetree.LxmlEtree"
+     />
+-->
+
+<!--
+  <utility
+     factory="zope.webdav.zetree.EtreePy25"
+     />
+-->
+
+</configure>


Property changes on: zope.webdav/trunk/src/zope/webdav/ietree-configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/ietree.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ietree.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ietree.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,94 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Zope Element Tree Support
+
+XXX - add comments into this interface.
+    - extend this interface to each of the different element tree engines. So
+      some example lxml supports things that the original elementtree
+      implementation did and vice versa. This way developers can say give me
+      an elementtree implementation support feature X. But we still need to be
+      carefull that we still have a common base interface from which to work
+      off.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+
+class IEtree(interface.Interface):
+
+    def Comment(text = None):
+        """
+        """
+
+    def dump(elem):
+        """
+        """
+
+    def Element(tag, attrib = {}, **extra):
+        """
+        """
+
+    def ElementTree(element = None, file = None):
+        """
+        """
+
+    def XML(text):
+        """
+        """
+
+    def fromstring(text):
+        """
+        """
+
+    def iselement(element):
+        """
+        """
+
+    def iterparse(source, events = None):
+        """
+        """
+
+    def parse(source, parser = None):
+        """
+        """
+
+    def PI(target, text = None):
+        """
+        """
+
+    def ProcessingInstruction(target, text = None):
+        """
+        """
+
+    def QName(text_or_uri, tag = None):
+        """
+        """
+
+    def SubElement(parent, tag, attrib = {}, **extra):
+        """
+        """
+
+    def tostring(element, encoding = None):
+        """
+        """
+
+    def TreeBuilder(element_factory = None):
+        """
+        """
+
+    def XMLTreeBuilder(html = 0, target = None):
+        """
+        """


Property changes on: zope.webdav/trunk/src/zope/webdav/ietree.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/interfaces.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/interfaces.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/interfaces.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,583 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""WebDAV-specific interfaces
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import UserDict
+from zope import interface
+from zope.interface.interfaces import IInterface
+from zope.interface.common.interfaces import IException
+from zope.interface.common.mapping import IMapping
+from zope.interface.common.sequence import IFiniteSequence
+from zope import schema
+from zope.schema.interfaces import IField
+from zope.component.interfaces import IView
+
+from zope.publisher.interfaces import IPublication
+from zope.publisher.interfaces.http import IHTTPRequest, IHTTPResponse
+from zope.app.publication.interfaces import IRequestFactory
+
+################################################################################
+#
+# Common WebDAV specific errors
+#
+################################################################################
+
+class IBadRequest(IException):
+    """
+    Some information passed in the request is in invalid.
+    """
+
+    request = interface.Attribute("""The request in which the error occured.""")
+
+    message = interface.Attribute("""Message to send back to the user.""")
+
+class BadRequest(Exception):
+    interface.implements(IBadRequest)
+
+    def __init__(self, request, message = None):
+        self.request = request
+        self.message = message
+
+    def __str__(self):
+        return "%r, %r" %(self.request, self.message)
+
+
+class IUnsupportedMediaType(IException):
+    """
+    Unsupported media type.
+    """
+
+    context = interface.Attribute(""" """)
+
+    message = interface.Attribute(""" """)
+
+class UnsupportedMediaType(Exception):
+    interface.implements(IUnsupportedMediaType)
+
+    def __init__(self, context, message = u""):
+        self.context = context
+        self.message = message
+
+    def __str__(self):
+        return "%r, %r" %(self.context, self.message)
+
+
+class IBadGateway(IException):
+    """
+    The server, while acting as a gateway or proxy, received an invalid
+    response from the upstream server it accessed in attempting to
+    fulfill the request.
+    """
+
+    context = interface.Attribute(""" """)
+
+    request = interface.Attribute(""" """)
+
+class BadGateway(Exception):
+    interface.implements(IBadGateway)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def __str__(self):
+        return "%r, %r" %(self.context, self.request)
+
+
+class IDAVException(IException):
+    """
+    Base interface for a common set of exceptions that should be raised
+    to inform the client that something has gone a bit to the left.
+    """
+
+    resource = interface.Attribute("""
+    Possible resource on which this error was raised.
+    """)
+
+    ## XXX - this attribute was added has an after-thought. It currently
+    ## isn't being used and has such it properly should be removed.
+    propertyname = interface.Attribute("""
+    Possible property name for which the error applies.
+    """)
+
+    message = interface.Attribute("""
+    Human readable message detailing what went wrong.
+    """)
+
+class DAVException(Exception):
+
+    def __init__(self, resource, propertyname = None, message = None):
+        self.resource = resource
+        self.propertyname = propertyname
+        self.message = message
+
+    def __str__(self):
+        return self.message
+
+
+class IConflictError(IDAVException):
+    """
+    The client has provided a value whose semantics are not appropriate for
+    current state of the resource.
+    """
+
+class ConflictError(DAVException):
+    interface.implements(IConflictError)
+
+
+class IForbiddenError(IDAVException):
+    """
+    The client, for reasons the server chooses not to specify, cannot alter
+    the resource.
+    """
+
+class ForbiddenError(DAVException):
+    interface.implements(IForbiddenError)
+
+
+class IUnprocessableError(IDAVException):
+    """
+    The entity body couldn't be parsed or is invalid.
+    """
+
+class UnprocessableError(DAVException):
+    interface.implements(IUnprocessableError)
+
+
+# XXX - PropertyNotFound should go away and instead we should
+# reuse the NotFound exception
+class IPropertyNotFound(IDAVException):
+    """
+    The requested property was not found.
+    """
+
+class PropertyNotFound(DAVException):
+    interface.implements(IPropertyNotFound)
+
+
+class IPreconditionFailed(IDAVException):
+    """
+    Some condition header failed to evalute to True.
+    """
+
+class PreconditionFailed(DAVException):
+    interface.implements(IPreconditionFailed)
+
+
+class IFailedDependency(IDAVException):
+    """
+    Method could not be performed on the resource because the requested
+    action depended on another action and that action failed.
+    """
+
+class FailedDependency(DAVException):
+    interface.implements(IFailedDependency)
+
+
+class IAlreadyLocked(IDAVException):
+    """
+    The resource is already locked.
+    """
+
+class AlreadyLocked(Exception):
+    interface.implements(IAlreadyLocked)
+
+    def __init__(self, context, message = None):
+        self.resource = context
+        self.propertyname = None
+        self.message = None
+
+
+class IWebDAVErrors(IFiniteSequence, IException):
+    """
+    List-like container of all exceptions that occured during the process
+    of a request. All the exceptions should be viewed and returned to the
+    client via a multi-status response.
+    """
+
+    def append(error):
+        """
+        Append a error to the collection of errors.
+        """
+
+class WebDAVErrors(Exception):
+    """
+    Collect has many errors has we can and then provide a view of all the
+    errors to the user.
+    """
+    interface.implements(IWebDAVErrors)
+
+    def __init__(self, context, errors = ()):
+        Exception.__init__(self)
+        self.errors = errors
+        self.context = context
+
+    def append(self, error):
+        self.errors += (error,)
+
+    def __len__(self):
+        return len(self.errors)
+
+    def __iter__(self):
+        return iter(self.errors)
+
+    def __getitem__(self, index):
+        return self.errors[index]
+
+
+class IWebDAVPropstatErrors(IMapping, IException):
+    """
+    Exception containing a mapping of property names to exceptions. This is
+    used to collect all the exceptions that occured when modifying the
+    properties on a resource.
+    """
+
+    context = interface.Attribute("""
+    The context on which all the properties are defined.
+    """)
+
+class WebDAVPropstatErrors(UserDict.IterableUserDict, Exception):
+    interface.implements(IWebDAVPropstatErrors)
+
+    def __init__(self, context):
+        ## Allowing users to pass the list of errors into this exception
+        ## is causing problems. This optional dictionary was remembering
+        ## the arguements from previous functional tests.
+        ## See the test_remove_live_prop in test_propfind.
+        UserDict.IterableUserDict.__init__(self)
+        Exception.__init__(self)
+        self.context = context
+
+################################################################################
+#
+# WebDAV related publication interfaces.
+#
+################################################################################
+
+class IWebDAVMethod(interface.Interface):
+    """
+    A object implementing this method is a callable object that implements
+    one of the WebDAV specific methods.
+    """
+
+
+class IWebDAVResponse(IHTTPResponse):
+    """
+    A WebDAV Response object.
+    """
+
+
+class IWebDAVRequest(IHTTPRequest):
+    """
+    A WebDAV Request.
+
+    This request should only be used to respond to a WebDAV specific request
+    that contains either XML or nothing has a payload.
+    """
+
+    xmlDataSource = interface.Attribute("""
+    xml.dom.Document instance representing the input stream has an XML document.
+
+    If there was no input or the input wasn't in XML then this attribute
+    is None.
+    """)
+
+    content_type = interface.Attribute("""
+    A string representing the content type of the input stream without any
+    parameters.
+    """)
+
+
+class IWebDAVRequestFactory(IRequestFactory):
+    """
+    WebDAV request factory.
+    """
+
+################################################################################
+#
+# WebDAV interfaces for widgets, properties and management of the widgets
+# and properties.
+#
+################################################################################
+
+class IBaseDAVWidget(IView):
+    """
+    DAVWidget are views of IDAVProperty objects, generally used by PROPFIND
+
+     - context is a IDAVProperty.
+
+     - request is a IHTTPRequest.
+
+    """
+
+    name = interface.Attribute("""
+    The local naem of the property.
+    """)
+
+    namespace = interface.Attribute("""
+    The XML namespace to which the property belongs.
+    """)
+
+
+class IIDAVWidget(IInterface):
+    """
+    Meta-interface marker for classes that when instanciated will implement
+    IDAVWidget.
+    """
+
+
+class IIDAVInputWidget(IInterface):
+    """
+    Meta-interface marker for classes that when instanciated will implement
+    IDAVInputWidget.
+    """
+
+
+class IDAVInputWidget(IBaseDAVWidget):
+    """
+    For use with in the PROPPATCH method.
+    """
+
+    def toFieldValue(element):
+        """
+        Convert the ElementTree element to a value which can be legally
+        assigned to the field.
+        """
+
+    def getInputValue():
+        """
+        Return value suitable for the widget's field.
+
+        The widget must return a value that can be legally assigned to
+        its bound field or otherwise raise ``WidgetInputError``.
+
+        The return value is not affected by `setRenderedValue()`.
+        """
+
+    def hasInput():
+        """
+        Returns ``True`` if the widget has input.
+
+        Input is used by the widget to calculate an 'input value', which is
+        a value that can be legally assigned to a field.
+
+        Note that the widget may return ``True``, indicating it has input, but
+        still be unable to return a value from `getInputValue`. Use
+        `hasValidInput` to determine whether or not `getInputValue` will return
+        a valid value.
+
+        A widget that does not have input should generally not be used
+        to update its bound field.  Values set using
+        `setRenderedValue()` do not count as user input.
+
+        A widget that has been rendered into a form which has been
+        submitted must report that it has input.  If the form
+        containing the widget has not been submitted, the widget
+        shall report that it has no input.
+        """
+
+
+class IDAVWidget(IBaseDAVWidget):
+    """
+    For use in rendering dav properties.
+    """
+
+    def render():
+        """
+        Render the property to a XML fragment, according to the relevent
+        specification.
+        """
+
+
+class IDAVErrorWidget(interface.Interface):
+    """
+    Widget used to render any errors that should be included in a multi-status
+    response.
+    """
+
+    status = interface.Attribute("""
+    The HTTP status code.
+    """)
+
+    errors = interface.Attribute("""
+    List of etree elements that is a precondition or a postcondition code.
+    """)
+
+    propstatdescription = interface.Attribute("""
+    Contains readable information about an error that occured and applies
+    to all properties contained within the current propstat XML element.
+    """)
+
+    responsedescription = interface.Attribute("""
+    Contains readable information about an error that occured and applies to
+    all resources contained within the current response XML element.
+    """)
+
+################################################################################
+#
+# Namespace management
+#
+################################################################################
+
+class IDAVProperty(interface.Interface):
+    """
+    Data structure that holds information about a live WebDAV property.
+    """
+
+    __name__ = schema.ASCII(
+        title = u"Name",
+        description = u"Local name of the WebDAV property.",
+        required = True)
+
+    namespace = schema.URI(
+        title = u"Namespace",
+        description = u"WebDAV namespace to which this property belongs.",
+        required = True)
+
+    field = schema.Object(
+        title = u"Field",
+        description = u"""Zope schema field that defines the data of the live
+                          WebDAV property.""",
+        schema = IField,
+        required = True)
+
+    custom_widget = schema.Object(
+        title = u"Custom Widget",
+        description = u"""Factory to use for widget construction. If None then
+                          a multi-adapter implementing IDAVWidget.""",
+        default = None,
+        schema = IIDAVWidget,
+        required = False)
+
+    custom_input_widget = schema.Object(
+        title = u"Custom Input Widget",
+        description = u"""Factory to use for widget construction. If None then
+                          a multi-adapter implementing IDAVInputWidget.""",
+        default = None,
+        schema = IIDAVInputWidget,
+        required = False)
+
+    restricted = schema.Bool(
+        title = u"Restricted property",
+        description = u"""If True then this property should not be included in
+                          the response to an allprop PROPFIND request.""",
+        default = False,
+        required = False)
+
+    iface = schema.Object(
+        title = u"Storage interface",
+        description = u"""Interface which declares how to find, store
+                          the property""",
+        schema = IInterface,
+        required = True)
+
+################################################################################
+#
+# Application specific interfaces.
+#
+################################################################################
+
+class IOpaquePropertyStorage(interface.Interface):
+    """
+    Declaration of an adapter that is used to store, remove and query all
+    dead properties on a resource.
+    """
+
+    def getAllProperties():
+        """
+        Return iterable of all IDAVProperty objects defined for the
+        current context.
+        """
+
+    def hasProperty(tag):
+        """
+        Return boolean indicating whether the named property exists has
+        a dead property for the current resource.
+        """
+
+    def getProperty(tag):
+        """
+        Return a IDAVProperty utility for the named property.
+        """
+
+    def setProperty(tag, value):
+        """
+        Set the value of the named property to value.
+        """
+
+    def removeProperty(tag):
+        """
+        Remove the named property from this property.
+        """
+
+
+class IDAVLockmanager(interface.Interface):
+    """
+    Helper adapter for manage locks in an independent manner. Different
+    Zope systems are going to have there own locking mechanisms for example
+    Zope3 and CPS have two conflicting locking mechanisms.
+    """
+
+    def islockable():
+        """
+        Return False when the current context can never be locked or locking
+        is not support in your setup.
+
+        For example in Zope3, zope.webdav.lockingutils.DAVLockmanager depends
+        on a persistent zope.locking.interfaces.ITokenUtility utility being
+        present and registered. If this utility can not be locked be then
+        we can never use this implementation of IDAVLockmanager.
+        """
+
+    def lock(scope, type, owner, duration, depth):
+        """
+        Lock the current context passing in all the possible information
+        neccessary for generating a lock token.
+
+        If the context can't be locked raise an exception that has a
+        corresponding IDAVErrorWidget implementation so that we can extract
+        what went wrong.
+
+        If `depth` has the value `infinity` and the context is a folder then
+        recurse through all the subitems locking them has you go.
+
+        Raise a AlreadyLockedError if some resource is already locked.
+        """
+
+    def getActivelock(request = None):
+        """
+        Return an implementation of zope.webdav.coreproperties.IActiveLock
+
+        If you are testing the lock manager then you don't need to pass
+        in the request - just don't access the lockroot attribute :-)
+        """
+
+    def refreshlock(timeout):
+        """
+        Refresh to lock token associated with this resource.
+        """
+
+    def unlock():
+        """
+        Unlock the current context.
+        """
+
+    def islocked():
+        """
+        Is the current context locked or not. Return True, otherwise False.
+        """


Property changes on: zope.webdav/trunk/src/zope/webdav/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/locking.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/locking.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/locking.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,277 @@
+##############################################################################
+# Copyright (c) 2006 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.
+##############################################################################
+"""WebDAV LOCK and UNLOCK methods.
+
+Request contains a lockinfo XML element.
+
+  <!ELEMENT lockinfo (lockscope, locktype, owner?)  >
+  <!ELEMENT lockscope (exclusive | shared) >
+  <!ELEMENT locktype (write) >
+  <!ELEMENT shared EMPTY >
+  <!ELEMENT exclusive EMPTY >
+  <!ELEMENT write EMPTY >
+  <!ELEMENT owner ANY >
+
+Response contains a lockdiscovery XML element.
+
+  <!ELEMENT lockdiscovery (activelock)* >
+  <!ELEMENT activelock (lockscope, locktype, depth, owner?, timeout?,
+            locktoken?, lockroot)>
+  <!ELEMENT depth (#PCDATA) >
+     "0" | "1" | "infinity"
+  <!ELEMENT timeout (#PCDATA) >
+    TimeType (defined in Section 10.7).
+  <!ELEMENT locktoken (href) >
+    locktoken uri
+  <!ELEMENT lockroot (href) >
+    The href element contains the root of the lock.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import datetime
+
+from zope import component
+from zope import interface
+
+import zope.webdav.interfaces
+import zope.webdav.properties
+from zope.webdav.coreproperties import IActiveLock
+from zope.webdav.ietree import IEtree
+import zope.webdav.utils
+
+MAXTIMEOUT = (2L ** 32) - 1
+DEFAULTTIMEOUT = 12 * 60L
+
+
+ at component.adapter(interface.Interface, zope.webdav.interfaces.IWebDAVMethod)
+ at interface.implementer(zope.webdav.interfaces.IWebDAVMethod)
+def LOCK(context, request):
+    """
+    If we can adapt the context to a lock manager then we should be able to
+    lock the resource.
+    """
+    lockmanager = zope.webdav.interfaces.IDAVLockmanager(context, None)
+    if lockmanager is None:
+        return None
+    if not lockmanager.islockable():
+        return None
+
+    return LOCKMethod(context, request)
+
+
+class LOCKMethod(object):
+    """
+    LOCK handler for all objects.
+    """
+    interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+    component.adapts(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def getDepth(self):
+        # default is infinity
+        return self.request.getHeader("depth", "infinity")
+
+    def getTimeout(self):
+        """
+        Return a datetime.timedelta object representing the duration of
+        the requested lock token.
+
+        XXX - this method is broken it needs to be able to parse
+        multiple timetypes.
+        """
+        timeoutheader = self.request.getHeader("timeout", "infinity")
+
+        timeoutheader = timeoutheader.strip().lower()
+        t = timeoutheader.split("-")
+
+        if len(t) == 2 and t[0].lower().lower() == "second":
+            th = t[1]
+        elif len(t) == 1 and t[0] == "infinity":
+            th = t[0]
+        else:
+            raise zope.webdav.interfaces.BadRequest(
+                self.request, message = u"Invalid TIMEOUT header")
+
+        if th == "infinite" or th == "infinity":
+            timeout = DEFAULTTIMEOUT
+        else:
+            try:
+                timeout = long(th)
+            except ValueError:
+                raise zope.webdav.interfaces.BadRequest(
+                    self.request, message = u"Invalid TIMEOUT header")
+
+        if timeout > MAXTIMEOUT:
+            timeout = DEFAULTTIMEOUT
+
+        return datetime.timedelta(seconds = timeout)
+
+    def LOCK(self):
+        # The Lock-Token header is not returned in the response for a
+        # successful refresh LOCK request.
+        refreshlock = False
+
+        if self.request.xmlDataSource is None:
+            errors = self.handleLockRefresh()
+            refreshlock = True
+        else: # Body => try to lock the resource
+            errors = self.handleLock()
+
+        if errors:
+            raise zope.webdav.interfaces.WebDAVErrors(self.context, errors)
+
+        etree = component.getUtility(IEtree)
+
+        davprop, adapter = zope.webdav.properties.getProperty(
+            self.context, self.request, "{DAV:}lockdiscovery")
+        davwidget = zope.webdav.properties.getWidget(
+            davprop, adapter, self.request)
+        propel = etree.Element(etree.QName("DAV:", "prop"))
+        propel.append(davwidget.render())
+
+        activelock = component.getMultiAdapter((self.context, self.request),
+                                               IActiveLock)
+
+        self.request.response.setStatus(200)
+        self.request.response.setHeader("Content-Type", "application/xml")
+        if not refreshlock:
+            self.request.response.setHeader("Lock-Token",
+                                            "<%s>" % activelock.locktoken[0])
+
+        return etree.tostring(propel)
+
+    def handleLockRefresh(self):
+        lockmanager = zope.webdav.interfaces.IDAVLockmanager(self.context)
+
+        if not lockmanager.islocked():
+            raise zope.webdav.interfaces.PreconditionFailed(
+                self.context, message = u"Context is not locked.")
+
+        locktoken = lockmanager.getActivelock().locktoken[0]
+        request_uri = self.request.getHeader("IF", "")
+        if not request_uri or \
+               request_uri[0] != "<" or request_uri[-1] != ">" or \
+               request_uri[1:-1] != locktoken:
+            raise zope.webdav.interfaces.PreconditionFailed(
+                self.context, message = u"Lock-Token doesn't match request uri")
+
+        timeout = self.getTimeout()
+        lockmanager.refreshlock(timeout)
+
+    def handleLock(self):
+        errors = []
+
+        xmlsource = self.request.xmlDataSource
+        if xmlsource.tag != "{DAV:}lockinfo":
+            raise zope.webdav.interfaces.UnprocessableError(
+                self.context,
+                message = u"LOCK request body must be a `lockinfo' XML element")
+
+        depth = self.getDepth()
+        if depth not in ("0", "infinity"):
+            raise zope.webdav.interfaces.BadRequest(
+                self.request,
+                u"Invalid depth header. Must be either '0' or 'infinity'")
+
+        etree = component.getUtility(IEtree)
+
+        timeout = self.getTimeout()
+
+        lockscope = xmlsource.find("{DAV:}lockscope")
+        if not lockscope:
+            raise zope.webdav.interfaces.UnprocessableError(
+                self.context,
+                message = u"No `{DAV:}lockscope' XML element in request")
+        lockscope_str = zope.webdav.utils.parseEtreeTag(lockscope[0].tag)[1]
+
+        locktype = xmlsource.find("{DAV:}locktype")
+        if not locktype:
+            raise zope.webdav.interfaces.UnprocessableError(
+                self.context,
+                message = u"No `{DAV:}locktype' XML element in request")
+        locktype_str = zope.webdav.utils.parseEtreeTag(locktype[0].tag)[1]
+
+        owner = xmlsource.find("{DAV:}owner")
+        owner_str = etree.tostring(owner)
+
+        lockmanager = zope.webdav.interfaces.IDAVLockmanager(self.context)
+
+        roottoken = None
+        try:
+            lockmanager.lock(scope = lockscope_str,
+                             type = locktype_str,
+                             owner = owner_str,
+                             duration = timeout,
+                             depth = depth)
+        except zope.webdav.interfaces.AlreadyLocked, error:
+            errors.append(error)
+
+        return errors
+
+################################################################################
+#
+# UNLOCK method.
+#
+################################################################################
+
+ at component.adapter(interface.Interface, zope.webdav.interfaces.IWebDAVMethod)
+ at interface.implementer(zope.webdav.interfaces.IWebDAVMethod)
+def UNLOCK(context, request):
+    """
+    If we can adapt the context to a lock manager then we should be able to
+    unlock the resource.
+    """
+    lockmanager = zope.webdav.interfaces.IDAVLockmanager(context, None)
+    if lockmanager is None:
+        return None
+    if not lockmanager.islockable():
+        return None
+
+    return UNLOCKMethod(context, request)
+
+
+class UNLOCKMethod(object):
+    """
+    UNLOCK handler for all objects.
+    """
+    interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+    component.adapts(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def UNLOCK(self):
+        locktoken = self.request.getHeader("lock-token", "")
+        if len(locktoken) > 1 and locktoken[0] == "<" and locktoken[-1] == ">":
+            locktoken = locktoken[1:-1]
+
+        if not locktoken:
+            raise zope.webdav.interfaces.BadRequest(
+                self.request, message = u"No lock-token header supplied")
+
+        lockmanager = zope.webdav.interfaces.IDAVLockmanager(self.context)
+        if not lockmanager.islocked() or \
+             lockmanager.getActivelock(self.request).locktoken[0] != locktoken:
+            raise zope.webdav.interfaces.ConflictError(
+                self.context, message = "object is locked or the lock isn't" \
+                                        "in the scope the passed.")
+
+        lockmanager.unlock()
+
+        self.request.response.setStatus(204)
+        return ""


Property changes on: zope.webdav/trunk/src/zope/webdav/locking.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/lockingutils.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/lockingutils.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/lockingutils.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,862 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Support for using zope.locking has a locking mechanism for WebDAV locking.
+
+Note that we can't use zope.locking.utility.TokenUtility has a global utility.
+This is because if a recursive lock request fails half through then the
+utility has already been modified and since it is not persistent
+transaction.abort doesn't unlock the pervious successful locks. Since the
+utility gets into an inconsistent state.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import persistent
+import time
+import random
+import datetime
+from BTrees.OOBTree import OOBTree
+from zope import component
+from zope import interface
+from zope.locking import tokens
+import zope.locking.interfaces
+from zope.security.proxy import removeSecurityProxy
+from zope.app.keyreference.interfaces import IKeyReference
+from zope.traversing.browser.absoluteurl import absoluteURL
+from zope.app.container.interfaces import IReadContainer
+
+from zope.webdav.coreproperties import ILockEntry, IDAVSupportedlock, \
+     IActiveLock
+import zope.webdav.interfaces
+
+
+INDIRECT_INDEX_KEY = 'zope.app.dav.lockingutils'
+
+_randGen = random.Random(time.time())
+
+class IIndirectToken(zope.locking.interfaces.IToken,
+                     zope.locking.interfaces.IEndable):
+    """
+    """
+
+    roottoken = interface.Attribute("""
+    Return the root lock token against which this token is locked.
+    """)
+
+
+class IndirectToken(persistent.Persistent):
+    """
+
+    Most of these tests have being copied from the README.txt file in
+    zope.locking
+
+    Some initial setup including creating some demo content.
+
+      >>> from zope.locking import utility, utils
+      >>> util = utility.TokenUtility()
+      >>> component.getGlobalSiteManager().registerUtility(
+      ...    util, zope.locking.interfaces.ITokenUtility)
+
+    Setup some content to test on.
+
+      >>> demofolder = DemoFolder(None, 'demofolderroot')
+      >>> demofolder['demo1'] = Demo()
+      >>> demofolder['demofolder1'] = DemoFolder()
+      >>> demofolder['demofolder1']['demo'] = Demo()
+
+    Lock the root folder with an exclusive lock.
+
+      >>> lockroot = tokens.ExclusiveLock(demofolder, 'michael')
+      >>> res = util.register(lockroot)
+
+    Now indirectly all the descended objects of the root folder against the
+    exclusive lock token we used to lock this folder with.
+
+      >>> lock1 = IndirectToken(demofolder['demo1'], lockroot)
+      >>> lock2 = IndirectToken(demofolder['demofolder1'], lockroot)
+      >>> lock3 = IndirectToken(demofolder['demofolder1']['demo'], lockroot)
+      >>> res1 = util.register(lock1)
+      >>> lock1 is util.get(demofolder['demo1'])
+      True
+      >>> res2 = util.register(lock2)
+      >>> lock2 is util.get(demofolder['demofolder1'])
+      True
+      >>> res3 = util.register(lock3)
+      >>> lock3 is util.get(demofolder['demofolder1']['demo'])
+      True
+
+    Make sure that the lockroot contains an index of all the toekns locked
+    against in its annotations
+
+      >>> len(lockroot.annotations[INDIRECT_INDEX_KEY])
+      3
+
+    Check that the IEndable properties are None
+
+      >>> res1.expiration == lockroot.expiration == None
+      True
+      >>> res1.duration == lockroot.duration == None
+      True
+      >>> res1.duration == lockroot.remaining_duration == None
+      True
+      >>> res1.started == lockroot.started
+      True
+      >>> lockroot.started is not None
+      True
+
+    All the indirect locktokens and the lookroot share the same annotations
+
+      >>> lockroot.annotations[u'webdav'] = u'test webdav indirect locking'
+      >>> res1.annotations[u'webdav']
+      u'test webdav indirect locking'
+
+    All the lock tokens have the same principals
+
+      >>> list(res1.principal_ids)
+      ['michael']
+      >>> list(lockroot.principal_ids)
+      ['michael']
+
+    None of the locks have ended yet, and they share the same utility.
+
+      >>> res1.ended is None
+      True
+      >>> lockroot.ended is None
+      True
+      >>> lockroot.utility is res1.utility
+      True
+
+    Expire the lock root
+
+      >>> now = utils.now()
+      >>> res3.end()
+
+    Now all the descendent objects of the lockroot and the lockroot itself
+    are unlocked.
+
+      >>> util.get(demofolder) is None
+      True
+      >>> util.get(demofolder['demo1']) is None
+      True
+      >>> util.get(demofolder['demofolder1']['demo']) is None
+      True
+
+    Also all the tokens has ended after now.
+
+      >>> lock1.ended is not None
+      True
+      >>> lock2.ended > now
+      True
+      >>> lock1.ended is lock2.ended
+      True
+      >>> lock3.ended is lockroot.ended
+      True
+
+    Test the event subscribers.
+
+      >>> ev = events[-1]
+      >>> zope.locking.interfaces.ITokenEndedEvent.providedBy(ev)
+      True
+      >>> len(lockroot.annotations[INDIRECT_INDEX_KEY])
+      3
+      >>> removeEndedTokens(ev)
+      >>> len(lockroot.annotations[INDIRECT_INDEX_KEY])
+      0
+
+    Test all the endable attributes
+
+      >>> import datetime
+      >>> one = datetime.timedelta(hours = 1)
+      >>> two = datetime.timedelta(hours = 2)
+      >>> three = datetime.timedelta(hours = 3)
+      >>> four = datetime.timedelta(hours = 4)
+      >>> lockroot = tokens.ExclusiveLock(demofolder, 'john', three)
+      >>> dummy = util.register(lockroot)
+      >>> indirect1 = IndirectToken(demofolder['demo1'], lockroot)
+      >>> dummy = util.register(indirect1)
+      >>> indirect1.duration
+      datetime.timedelta(0, 10800)
+      >>> lockroot.duration == indirect1.duration
+      True
+      >>> indirect1.ended is None
+      True
+      >>> indirect1.expiration == indirect1.started + indirect1.duration
+      True
+
+    Now try to 
+
+      >>> indirect1.expiration = indirect1.started + one
+      >>> indirect1.expiration == indirect1.started + one
+      True
+      >>> indirect1.expiration == lockroot.expiration
+      True
+      >>> indirect1.duration == one
+      True
+
+    Now test changing the duration attribute
+
+      >>> indirect1.duration = four
+      >>> indirect1.duration == lockroot.duration
+      True
+      >>> indirect1.duration
+      datetime.timedelta(0, 14400)
+
+    Now check the remain_duration code
+
+      >>> import pytz
+      >>> def hackNow():
+      ...     return (datetime.datetime.now(pytz.utc) +
+      ...             datetime.timedelta(hours=2))
+      ...
+      >>> import zope.locking.utils
+      >>> oldNow = zope.locking.utils.now
+      >>> zope.locking.utils.now = hackNow # make code think it's 2 hours later
+      >>> indirect1.duration
+      datetime.timedelta(0, 14400)
+      >>> two >= indirect1.remaining_duration >= one
+      True
+      >>> indirect1.remaining_duration -= one
+      >>> one >= indirect1.remaining_duration >= datetime.timedelta()
+      True
+      >>> three + datetime.timedelta(minutes = 1) >= indirect1.duration >= three
+      True
+
+    Since we modified the remaining_duration attribute a IExpirationChagedEvent
+    should have being fired.
+      
+      >>> ev = events[-1]
+      >>> from zope.interface.verify import verifyObject
+      >>> from zope.locking.interfaces import IExpirationChangedEvent
+      >>> verifyObject(IExpirationChangedEvent, ev)
+      True
+      >>> ev.object is lockroot
+      True
+
+    Now pretend that it is a day later, the indirect token and the lock root
+    will have timed out sliently.
+
+      >>> def hackNow():
+      ...     return (
+      ...         datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1))
+      ...
+      >>> zope.locking.utils.now = hackNow # make code think it is a day later
+      >>> indirect1.ended == indirect1.expiration
+      True
+      >>> lockroot.ended == indirect1.ended
+      True
+      >>> util.get(demofolder['demo1']) is None
+      True
+      >>> util.get(demofolder['demo1'], util) is util
+      True
+      >>> indirect1.remaining_duration == datetime.timedelta()
+      True
+      >>> indirect1.end()
+      Traceback (most recent call last):
+      ...
+      EndedError
+
+    Once a lock has ended, the timeout can no longer be changed.
+
+      >>> indirect1.duration = datetime.timedelta(days=2)
+      Traceback (most recent call last):
+      ...
+      EndedError
+
+    Now undo our hack.
+
+      >>> zope.locking.utils.now = oldNow # undo the hack
+      >>> indirect1.end() # really end the token
+      >>> util.get(demofolder) is None
+      True
+
+    Now test the simple SharedLock with an indirect token.
+
+      >>> lockroot = tokens.SharedLock(demofolder, ('john', 'mary'))
+      >>> dummy = util.register(lockroot)
+      >>> sharedindirect = IndirectToken(demofolder['demo1'], lockroot)
+      >>> dummy = util.register(sharedindirect)
+      >>> sorted(sharedindirect.principal_ids)
+      ['john', 'mary']
+      >>> sharedindirect.add(('jane',))
+      >>> sorted(lockroot.principal_ids)
+      ['jane', 'john', 'mary']
+      >>> sorted(sharedindirect.principal_ids)
+      ['jane', 'john', 'mary']
+      >>> sharedindirect.remove(('mary',))
+      >>> sorted(sharedindirect.principal_ids)
+      ['jane', 'john']
+      >>> sorted(lockroot.principal_ids)
+      ['jane', 'john']
+      >>> lockroot.remove(('jane',))
+      >>> sorted(sharedindirect.principal_ids)
+      ['john']
+      >>> sorted(lockroot.principal_ids)
+      ['john']
+      >>> sharedindirect.remove(('john',))
+      >>> util.get(demofolder) is None
+      True
+      >>> util.get(demofolder['demo1']) is None
+      True
+
+    Test using the shared lock token methods on a non shared lock
+
+      >>> lockroot = tokens.ExclusiveLock(demofolder, 'john')
+      >>> dummy = util.register(lockroot)
+      >>> indirect1 = IndirectToken(demofolder['demo1'], lockroot)
+      >>> dummy = util.register(indirect1)
+      >>> dummy is indirect1
+      True
+      >>> dummy.add('john')
+      Traceback (most recent call last):
+      ...
+      TypeError: can't add a principal to a non-shared token
+      >>> dummy.remove('michael')
+      Traceback (most recent call last):
+      ...
+      TypeError: can't add a principal to a non-shared token
+
+    Setup with wrong utility.
+
+      >>> util2 = utility.TokenUtility()
+      >>> roottoken = tokens.ExclusiveLock(demofolder, 'michael2')
+      >>> roottoken = util2.register(roottoken)
+      >>> roottoken.utility == util2
+      True
+
+      >>> indirecttoken = IndirectToken(demofolder['demo1'], roottoken)
+      >>> indirecttoken = util2.register(indirecttoken)
+      >>> indirecttoken.utility is util2
+      True
+      >>> indirecttoken.utility = util
+      Traceback (most recent call last):
+      ...
+      ValueError: cannot reset utility
+      >>> indirecttoken = IndirectToken(demofolder['demo1'], roottoken)
+      >>> indirecttoken.utility = util
+      Traceback (most recent call last):
+      ...
+      ValueError: Indirect tokens must be registered withsame utility has the root token
+
+    Cleanup test.
+
+      >>> component.getGlobalSiteManager().unregisterUtility(
+      ...    util, zope.locking.interfaces.ITokenUtility)
+      True
+
+    """
+    interface.implements(IIndirectToken)
+
+    def __init__(self, target, token):
+        self.context = self.__parent__ = target
+        self.roottoken = token
+
+    _utility = None
+    @apply
+    def utility():
+        # IAbstractToken - this is the only hook I can find since
+        # it represents the lock utility in charge of this lock.
+        def get(self):
+            return self._utility
+        def set(self, value):
+            if self._utility is not None:
+                if value is not self._utility:
+                    raise ValueError("cannot reset utility")
+            else:
+                assert zope.locking.interfaces.ITokenUtility.providedBy(value)
+                root = self.roottoken
+                if root.utility != value:
+                    raise ValueError("Indirect tokens must be registered with" \
+                                     "same utility has the root token")
+                index = root.annotations.get(INDIRECT_INDEX_KEY, None)
+                if index is None:
+                    index = root.annotations[INDIRECT_INDEX_KEY] = \
+                            tokens.AnnotationsMapping()
+                    index.__parent__ = root
+                key_ref = IKeyReference(self.context)
+                assert index.get(key_ref, None) is None, \
+                       "context is already locked"
+                index[key_ref] = self
+                self._utility = value
+        return property(get, set)
+
+    @property
+    def principal_ids(self):
+        # IAbstractToken
+        return self.roottoken.principal_ids
+
+    @property
+    def started(self):
+        # IAbstractToken
+        return self.roottoken.started
+
+    @property
+    def annotations(self):
+        # See IToken
+        return self.roottoken.annotations
+
+    def add(self, principal_ids):
+        # ISharedLock
+        if not zope.locking.interfaces.ISharedLock.providedBy(self.roottoken):
+            raise TypeError, "can't add a principal to a non-shared token"
+        return self.roottoken.add(principal_ids)
+
+    def remove(self, principal_ids):
+        # ISharedLock
+        if not zope.locking.interfaces.ISharedLock.providedBy(self.roottoken):
+            raise TypeError, "can't add a principal to a non-shared token"
+        return self.roottoken.remove(principal_ids)
+
+    @property
+    def ended(self):
+        # IEndable
+        return self.roottoken.ended
+
+    @apply
+    def expiration(): # XXX - needs testing
+        # IEndable
+        def get(self):
+            return self.roottoken.expiration
+        def set(self, value):
+            self.roottoken.expiration = value
+        return property(get, set)
+
+    @apply
+    def duration(): # XXX - needs testing
+        # IEndable
+        def get(self):
+            return self.roottoken.duration
+        def set(self, value):
+            self.roottoken.duration = value
+        return property(get, set)
+
+    @apply
+    def remaining_duration():
+        # IEndable
+        def get(self):
+            return self.roottoken.remaining_duration
+        def set(self, value):
+            self.roottoken.remaining_duration = value
+        return property(get, set)
+
+    def end(self):
+        # IEndable
+        return self.roottoken.end()
+
+
+def removeEndedTokens(event):
+    """subscriber handler for ITokenEndedEvent"""
+    assert zope.locking.interfaces.ITokenEndedEvent.providedBy(event)
+    roottoken = event.object
+    assert not IIndirectToken.providedBy(roottoken)
+    index = roottoken.annotations.get(INDIRECT_INDEX_KEY, {})
+    # read the whole index in memory so that we correctly loop over all the
+    # items in this list.
+    indexItems = list(index.items())
+    for key_ref, token in indexItems:
+        # token has ended so it should be removed via the register method
+        roottoken.utility.register(token)
+        del index[key_ref]
+
+# TODO - need subscriber incase a user tries to add a object has a
+# descendent to the lock object.
+
+################################################################################
+#
+# zope.locking adapters.
+#
+################################################################################
+
+class ExclusiveLockEntry(object):
+    interface.implements(ILockEntry)
+
+    lockscope = [u"exclusive"]
+    locktype = [u"write"]
+
+
+class SharedLockEntry(object):
+    interface.implements(ILockEntry)
+
+    lockscope = [u"shared"]
+    locktype = [u"write"]
+
+
+ at component.adapter(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+ at interface.implementer(IDAVSupportedlock)
+def DAVSupportedlock(context, request):
+    utility = component.queryUtility(zope.locking.interfaces.ITokenUtility)
+    if utility is None:
+        return None
+    return DAVSupportedlockAdapter(context, request)
+
+
+class DAVSupportedlockAdapter(object):
+    """
+      >>> slock = DAVSupportedlockAdapter(None, None)
+      >>> exclusive, shared = slock.supportedlock
+
+      >>> exclusive.lockscope
+      [u'exclusive']
+      >>> exclusive.locktype
+      [u'write']
+
+      >>> shared.lockscope
+      [u'shared']
+      >>> shared.locktype
+      [u'write']
+
+    """
+    interface.implements(IDAVSupportedlock)
+    component.adapts(interface.Interface,
+                     zope.webdav.interfaces.IWebDAVRequest)
+
+    def __init__(self, context, request):
+        pass
+
+    @property
+    def supportedlock(self):
+        return [ExclusiveLockEntry(), SharedLockEntry()]
+
+
+WEBDAV_LOCK_KEY = "zope.webdav.lockingutils.info"
+
+ at component.adapter(interface.Interface, zope.webdav.interfaces.IWebDAVMethod)
+ at interface.implementer(IActiveLock)
+def DAVActiveLock(context, request):
+    """
+    The activelock property only exists whenever the resource is locked.
+    """
+    try:
+        token = zope.locking.interfaces.ITokenBroker(context).get()
+    except TypeError:
+        token = None
+    if token is None:
+        return None
+    return DAVActiveLockAdapter(token, context, request)
+
+
+class DAVActiveLockAdapter(object):
+    component.adapts(interface.Interface,
+                     zope.webdav.interfaces.IWebDAVRequest)
+    interface.implements(IActiveLock)
+
+    def __init__(self, token, context, request):
+        self.context = self.__parent__ = context
+        self.token = token
+        self.request = request
+
+    @property
+    def lockscope(self):
+        if IIndirectToken.providedBy(self.token):
+            roottoken = self.token.roottoken
+        else:
+            roottoken = self.token
+
+        if zope.locking.interfaces.IExclusiveLock.providedBy(roottoken):
+            return [u"exclusive"]
+        elif zope.locking.interfaces.ISharedLock.providedBy(roottoken):
+            return [u"shared"]
+
+        raise ValueError("Unknow lock token used for locking")
+
+    @property
+    def locktype(self):
+        return [u"write"]
+
+    @property
+    def depth(self):
+        return self.token.annotations[WEBDAV_LOCK_KEY]["depth"]
+
+    @property
+    def owner(self):
+        return self.token.annotations[WEBDAV_LOCK_KEY]["owner"]
+
+    @property
+    def timeout(self):
+        return u"Second-%d" % self.token.remaining_duration.seconds
+
+    @property
+    def locktoken(self):
+        return [self.token.annotations[WEBDAV_LOCK_KEY]["token"]]
+
+    @property
+    def lockroot(self):
+        if IIndirectToken.providedBy(self.token):
+            root = self.token.roottoken.context
+        else:
+            root = self.token.context
+
+        return absoluteURL(root, self.request)
+
+
+ at component.adapter(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+ at interface.implementer(zope.webdav.coreproperties.IDAVLockdiscovery)
+def DAVLockdiscovery(context, request):
+    utility = component.queryUtility(zope.locking.interfaces.ITokenUtility)
+    if utility is None:
+        return None
+    return DAVLockdiscoveryAdapter(context, request)
+
+
+class DAVLockdiscoveryAdapter(object):
+    interface.implements(zope.webdav.coreproperties.IDAVLockdiscovery)
+    component.adapts(interface.Interface,
+                     zope.webdav.interfaces.IWebDAVRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    @property
+    def lockdiscovery(self):
+        adapter = component.queryMultiAdapter((self.context, self.request),
+                                              IActiveLock, default = None)
+        if adapter is None:
+            return None
+        return [adapter]
+
+
+class DAVLockmanager(object):
+    """
+
+      >>> from zope.interface.verify import verifyObject
+      >>> from zope.locking import utility, utils
+      >>> from zope.locking.adapters import TokenBroker
+
+      >>> file = Demo()
+
+    Before we register a ITokenUtility utility make sure that the DAVLockmanager
+    is not lockable.
+
+      >>> adapter = DAVLockmanager(file)
+      >>> adapter.islockable()
+      False
+
+    Now create and register a ITokenUtility utility.
+
+      >>> util = utility.TokenUtility()
+      >>> component.getGlobalSiteManager().registerUtility(
+      ...    util, zope.locking.interfaces.ITokenUtility)
+      >>> component.getGlobalSiteManager().registerAdapter(
+      ...    TokenBroker, (interface.Interface,),
+      ...    zope.locking.interfaces.ITokenBroker)
+
+      >>> import pytz
+      >>> def hackNow():
+      ...     return datetime.datetime(2006, 7, 25, 23, 49, 51)
+      >>> oldNow = utils.now
+      >>> utils.now = hackNow
+
+    Test the DAVLockmanager implements the descired interface.
+
+      >>> adapter = DAVLockmanager(file)
+      >>> verifyObject(zope.webdav.interfaces.IDAVLockmanager, adapter)
+      True
+
+    The adapter should also be lockable.
+
+      >>> adapter.islockable()
+      True
+
+    Lock with an exclusive lock token.
+
+      >>> roottoken = adapter.lock(u'exclusive', u'write',
+      ...    u'Michael', datetime.timedelta(seconds = 3600), '0')
+      >>> util.get(file) == roottoken
+      True
+      >>> zope.locking.interfaces.IExclusiveLock.providedBy(roottoken)
+      True
+
+      >>> adapter.islocked()
+      True
+
+      >>> activelock = adapter.getActivelock()
+      >>> activelock.lockscope
+      [u'exclusive']
+      >>> activelock.locktype
+      [u'write']
+      >>> activelock.depth
+      '0'
+      >>> activelock.timeout
+      u'Second-3600'
+      >>> activelock.lockroot
+      '/dummy'
+      >>> activelock.owner
+      u'Michael'
+
+      >>> adapter.refreshlock(datetime.timedelta(seconds = 7200))
+      >>> adapter.getActivelock().timeout
+      u'Second-7200'
+
+      >>> adapter.unlock()
+      >>> util.get(file) is None
+      True
+      >>> adapter.islocked()
+      False
+      >>> adapter.getActivelock() is None
+      True
+
+    Shared locking support.
+
+      >>> roottoken = adapter.lock(u'shared', u'write', u'Michael',
+      ...    datetime.timedelta(seconds = 3600), '0')
+      >>> util.get(file) == roottoken
+      True
+      >>> zope.locking.interfaces.ISharedLock.providedBy(roottoken)
+      True
+
+      >>> activelock = adapter.getActivelock()
+      >>> activelock.lockscope
+      [u'shared']
+      >>> activelock.locktoken #doctest:+ELLIPSIS
+      ['opaquelocktoken:...
+
+      >>> adapter.unlock()
+
+    Recursive lock suport.
+
+      >>> demofolder = DemoFolder()
+      >>> demofolder['demo'] = file
+
+      >>> adapter = DAVLockmanager(demofolder)
+      >>> roottoken = adapter.lock(u'exclusive', u'write', u'MichaelK',
+      ...    datetime.timedelta(seconds = 3600), 'infinity')
+
+      >>> demotoken = util.get(file)
+      >>> IIndirectToken.providedBy(demotoken)
+      True
+
+      >>> activelock = adapter.getActivelock()
+      >>> activelock.lockroot
+      '/dummy/'
+      >>> DAVLockmanager(file).getActivelock().lockroot
+      '/dummy/'
+      >>> absoluteURL(file, None)
+      '/dummy/dummy'
+      >>> activelock.lockscope
+      [u'exclusive']
+
+    Already locked support.
+
+      >>> adapter.lock(u'exclusive', u'write', u'Michael',
+      ...    datetime.timedelta(seconds = 100), 'infinity') #doctest:+ELLIPSIS
+      Traceback (most recent call last):
+      ...
+      AlreadyLocked...
+      >>> adapter.islocked()
+      True
+
+      >>> adapter.unlock()
+
+    Some error conditions.
+
+      >>> adapter.lock(u'exclusive', u'notwrite', u'Michael',
+      ...    datetime.timedelta(seconds = 100), 'infinity') # doctest:+ELLIPSIS
+      Traceback (most recent call last):
+      ...
+      UnprocessableError: ...
+
+      >>> adapter.lock(u'notexclusive', u'write', u'Michael',
+      ...    datetime.timedelta(seconds = 100), 'infinity') # doctest:+ELLIPSIS
+      Traceback (most recent call last):
+      ...
+      UnprocessableError: ...
+
+      >>> component.getGlobalSiteManager().unregisterUtility(
+      ...    util, zope.locking.interfaces.ITokenUtility)
+      True
+      >>> component.getGlobalSiteManager().unregisterAdapter(
+      ...    TokenBroker, (interface.Interface,),
+      ...    zope.locking.interfaces.ITokenBroker)
+      True
+      >>> utils.now = oldNow
+
+    """
+    interface.implements(zope.webdav.interfaces.IDAVLockmanager)
+    component.adapts(interface.Interface)
+
+    def __init__(self, context):
+        self.context = self.__parent__ = context
+
+    def generateLocktoken(self):
+        return "opaquelocktoken:%s-%s-00105A989226:%.03f" % \
+               (_randGen.random(), _randGen.random(), time.time())
+
+    def islockable(self):
+        utility = component.queryUtility(zope.locking.interfaces.ITokenUtility,
+                                         context = self.context, default = None)
+        return utility is not None
+
+    def lock(self, scope, type, owner, duration, depth,
+             roottoken = None, context = None):
+        if type != u"write":
+            raise zope.webdav.interfaces.UnprocessableError(
+                self.context,
+                message = "Invalid locktype supplied to lock manager.")
+
+        if context is None:
+            context = self.context
+
+        tokenBroker = zope.locking.interfaces.ITokenBroker(context)
+        if tokenBroker.get():
+            raise zope.webdav.interfaces.AlreadyLocked(
+                context, message = u"Context or subitem is already locked.")
+
+        if roottoken is None:
+            if scope == u"exclusive":
+                roottoken = tokenBroker.lock(duration = duration)
+            elif scope == u"shared":
+                roottoken = tokenBroker.lockShared(duration = duration)
+            else:
+                raise zope.webdav.interfaces.UnprocessableError(
+                    self.context,
+                    message = u"Invalid lockscope supplied to the lock manager")
+
+            annots = roottoken.annotations.get(WEBDAV_LOCK_KEY, None)
+            if annots is None:
+                annots = roottoken.annotations[WEBDAV_LOCK_KEY] = OOBTree()
+            annots["owner"] = owner
+            annots["token"] = self.generateLocktoken()
+            annots["depth"] = depth
+        else:
+            indirecttoken = IndirectToken(context, roottoken)
+            ## XXX - using removeSecurityProxy - is this right, has
+            ## it seems wrong
+            removeSecurityProxy(roottoken).utility.register(indirecttoken)
+
+        if depth == "infinity" and IReadContainer.providedBy(context):
+            for subob in context.values():
+                self.lock(scope, type, owner, duration, depth,
+                          roottoken, subob)
+
+        return roottoken
+
+    def getActivelock(self, request = None):
+        if self.islocked():
+            token = zope.locking.interfaces.ITokenBroker(self.context).get()
+            return DAVActiveLockAdapter(token, self.context, request)
+        return None
+
+    def refreshlock(self, timeout):
+        token = zope.locking.interfaces.ITokenBroker(self.context).get()
+        token.duration = timeout
+
+    def unlock(self):
+        tokenBroker = zope.locking.interfaces.ITokenBroker(self.context)
+        token = tokenBroker.get()
+        token.end()
+
+    def islocked(self):
+        tokenBroker = zope.locking.interfaces.ITokenBroker(self.context)
+        return tokenBroker.get() is not None


Property changes on: zope.webdav/trunk/src/zope/webdav/lockingutils.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/mkcol.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/mkcol.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/mkcol.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,63 @@
+##############################################################################
+# 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.
+##############################################################################
+"""DAV method MKCOL
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import component
+from zope.filerepresentation.interfaces import IWriteDirectory
+from zope.filerepresentation.interfaces import IDirectoryFactory
+import zope.event
+from zope.lifecycleevent import ObjectCreatedEvent
+import zope.app.http.interfaces
+
+import zope.webdav.interfaces
+
+class NullResource(object):
+    """MKCOL handler for creating collections"""
+
+    # MKCOL is only supported on unmapped urls.
+    interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+    component.adapts(zope.app.http.interfaces.INullResource,
+                     zope.webdav.interfaces.IWebDAVRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def MKCOL(self):
+        request = self.request
+        data = request.bodyStream.read()
+        if len(data):
+            # We don't (yet) support a request body on MKCOL.
+            raise zope.webdav.interfaces.UnsupportedMediaType(
+                self.context,
+                message = u"A request body is not supported for a MKCOL method")
+
+        container = self.context.container
+        name = self.context.name
+
+        dir = IWriteDirectory(container, None)
+        if dir is None:
+            raise zope.webdav.interfaces.ForbiddenError(
+                self.context, message = u"")
+
+        factory = IDirectoryFactory(container)
+        newdir = factory(name)
+        zope.event.notify(ObjectCreatedEvent(newdir))
+        dir[name] = newdir
+
+        request.response.setStatus(201)
+        return ""


Property changes on: zope.webdav/trunk/src/zope/webdav/mkcol.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/properties.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/properties.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/properties.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,233 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Miscellanous helper methods for implementing the WebDAV data model. See
+datamodel.txt
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import component
+from zope import interface
+from zope import schema
+from zope.schema.interfaces import IField
+from zope.schema.fieldproperty import FieldProperty
+from zope.app.form.interfaces import MissingInputError
+
+from zope.webdav.interfaces import IDAVProperty, IDAVWidget, IDAVInputWidget
+from zope.webdav.interfaces import IOpaquePropertyStorage
+import zope.webdav.widgets
+from zope.webdav.ietree import IEtree
+import zope.webdav.utils
+
+class DAVProperty(object):
+    """
+
+      >>> from zope.interface.verify import verifyObject
+      >>> from zope.schema import getFields
+      >>> from zope.interface.interfaces import IInterface
+      >>> from coreproperties import IDAVResourcetype
+      >>> prop = DAVProperty('{DAV:}resourcetype', IDAVResourcetype)
+      >>> verifyObject(IDAVProperty, prop)
+      True
+      >>> prop.namespace
+      'DAV:'
+      >>> prop.__name__
+      'resourcetype'
+      >>> verifyObject(IInterface, prop.iface)
+      True
+      >>> prop.field in getFields(prop.iface).values()
+      True
+      >>> verifyObject(IField, prop.field)
+      True
+      >>> prop.custom_widget is None
+      True
+      >>> prop.restricted
+      False
+
+    """
+    interface.implements(IDAVProperty)
+
+    namespace = FieldProperty(IDAVProperty['namespace'])
+    __name__  = FieldProperty(IDAVProperty['__name__'])
+    ## XXX - If a developer writes his own field and passes it into
+    ## DAVProperty then it is next to impossible to get the to validate
+    ## correctly.
+    ## field     = FieldProperty(IDAVProperty['field'])
+    iface = FieldProperty(IDAVProperty['iface'])
+    custom_widget = FieldProperty(IDAVProperty['custom_widget'])
+    custom_input_widget = FieldProperty(IDAVProperty['custom_input_widget'])
+    restricted = FieldProperty(IDAVProperty['restricted'])
+
+    def __init__(self, tag, iface):
+        namespace, name = zope.webdav.utils.parseEtreeTag(tag)
+        self.namespace = namespace
+        self.__name__  = name
+        self.iface     = iface
+        self.field     = iface[name]
+        self.custom_widget = None
+        self.custom_input_widget = None
+        self.restricted    = False
+
+
+_opaque_namespace_key = "zope.webdav.properties.DAVOpaqueProperties"
+
+class DeadField(schema.Field):
+    pass
+
+
+class OpaqueWidget(zope.webdav.widgets.DAVWidget):
+
+    def render(self):
+        etree = component.getUtility(IEtree)
+        el = etree.fromstring(self._value)
+        return el
+
+
+class OpaqueInputWidget(zope.webdav.widgets.DAVInputWidget):
+
+    def getInputValue(self):
+        el = self.getProppatchElement()
+
+        etree = component.getUtility(IEtree)
+        # XXX - ascii seems a bit wrong here
+        return etree.tostring(el[0], 'ascii')
+
+
+class IOpaqueField(IField):
+
+    namespace = schema.BytesLine( # should this be schema.URI
+        title = u"Namespace",
+        description = u"Namespace to which the value belongs",
+        required = True)
+
+
+class OpaqueField(schema.Field):
+    interface.implements(IOpaqueField)
+
+    namespace = FieldProperty(IOpaqueField['namespace'])
+
+    def __init__(self, namespace = None, **kw):
+        super(OpaqueField, self).__init__(**kw)
+        self.namespace = namespace
+        self.tag = "{%s}%s" %(self.namespace, self.__name__)
+
+    def get(self, obj):
+        return obj.getProperty(self.tag)
+
+    def set(self, obj, value):
+        obj.setProperty(self.tag, value)
+
+
+class OpaqueProperty(object):
+    """
+      >>> from zope.interface.verify import verifyObject
+      >>> prop = OpaqueProperty('{examplens:}testprop')
+      >>> verifyObject(IDAVProperty, prop)
+      True
+    """
+    interface.implements(IDAVProperty)
+
+    def __init__(self, tag):
+        namespace, name = zope.webdav.utils.parseEtreeTag(tag)
+        self.__name__ = name
+        self.namespace = namespace
+        self.iface = IOpaquePropertyStorage
+        self.field = OpaqueField(
+            __name__ = name,
+            namespace = namespace,
+            title = u"",
+            description = u"")
+        self.custom_widget       = OpaqueWidget
+        self.custom_input_widget = OpaqueInputWidget
+        self.restricted = False
+
+
+def getAllProperties(context, request):
+    for name, prop in component.getUtilitiesFor(IDAVProperty):
+        adapter = component.queryMultiAdapter((context, request),
+                                              prop.iface,
+                                              default = None)
+        if adapter is None:
+            continue
+
+        yield prop, adapter
+
+    adapter = IOpaquePropertyStorage(context, None)
+    if adapter is None:
+        raise StopIteration
+
+    for tag in adapter.getAllProperties():
+        yield OpaqueProperty(tag), adapter
+
+
+def hasProperty(context, request, tag):
+    prop = component.queryUtility(IDAVProperty, name = tag, default = None)
+    if prop is None:
+        adapter = IOpaquePropertyStorage(context, None)
+        if adapter is not None and adapter.hasProperty(tag):
+            return True
+        return False
+
+    adapter = component.queryMultiAdapter((context, request), prop.iface,
+                                          default = None)
+    if adapter is None:
+        return False
+
+    return True
+
+
+def getProperty(context, request, tag, exists = False):
+    prop = component.queryUtility(IDAVProperty, name = tag, default = None)
+    if prop is None:
+        adapter = IOpaquePropertyStorage(context, None)
+        if adapter is None:
+            ## XXX - should we use the zope.publisher.interfaces.NotFound
+            ## exceptin here.
+            raise zope.webdav.interfaces.PropertyNotFound(context, tag, tag)
+
+        if exists and not adapter.hasProperty(tag):
+            ## XXX - should we use the zope.publisher.interfaces.NotFound
+            ## exceptin here.
+            raise zope.webdav.interfaces.PropertyNotFound(context, tag, tag)
+
+        return OpaqueProperty(tag), adapter
+
+    adapter = component.queryMultiAdapter((context, request), prop.iface,
+                                          default = None)
+    if adapter is None:
+        ## XXX - should we use the zope.publisher.interfaces.NotFound
+        ## exceptin here.
+        raise zope.webdav.interfaces.PropertyNotFound(context, tag, tag)
+
+    return prop, adapter
+
+
+def getWidget(prop, adapter, request, type = IDAVWidget):
+    """prop.field describes the data we want to render.
+    """
+    if type is IDAVWidget and prop.custom_widget is not None:
+        widget = prop.custom_widget(prop.field, request)
+    elif type is IDAVInputWidget and prop.custom_input_widget is not None:
+        widget = prop.custom_input_widget(prop.field, request)
+    else:
+        widget = component.getMultiAdapter((prop.field, request), type)
+
+    if IDAVWidget.providedBy(widget):
+        field = prop.field.bind(adapter)
+        widget.setRenderedValue(field.get(adapter))
+
+    widget.namespace = prop.namespace
+
+    return widget


Property changes on: zope.webdav/trunk/src/zope/webdav/properties.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/propfind.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/propfind.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/propfind.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,205 @@
+##############################################################################
+# Copyright (c) 2006 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.
+##############################################################################
+"""WebDAV method PROPFIND
+
+The propfind XML element conforms to the following DTD snippet
+
+  <!ELEMENT propfind ( propname | (allprop, include?) | prop ) >
+  <!ELEMENT propname EMPTY >
+  <!ELEMENT allprop EMPTY >
+  <!ELEMENT include ANY >
+  <!ELEMENT prop ANY >
+
+All the render*(ob, req, extra) know how to render the requested properties
+requested by the PROPFIND method.
+
+  renderPropnames(ob, req, ignore) - extra argument is ignored.
+
+  renderAllProperties(ob, req, include) - extra argument is a list of all
+                                          the properties that must be rendered.
+
+  renderSelectedProperties(ob, req, props) - extra argument is a list of all
+                                             the properties to render.
+
+And all these methods return a zope.webdav.utils.IResponse implementation.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import sys
+
+from zope import interface
+from zope import component
+from zope.app.container.interfaces import IReadContainer
+
+from ietree import IEtree
+import zope.webdav.utils
+import zope.webdav.interfaces
+import zope.webdav.properties
+
+DEFAULT_NS = "DAV:"
+
+class PROPFIND(object):
+    """
+    PROPFIND handler for all objects.
+
+    The PROPFIND method handles parsing of the XML body and then calls the
+    
+    """
+    interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+    component.adapts(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def getDepth(self):
+        # default is infinity.
+        return self.request.getHeader("depth", "infinity")
+
+    def PROPFIND(self):
+        if len(self.request.bodyStream.getCacheStream().read()) > 0 and \
+               self.request.content_type not in ("text/xml", "application/xml"):
+            raise zope.webdav.interfaces.BadRequest(
+                self.request,
+                message = u"PROPFIND requires a valid XML request")
+
+        depth = self.getDepth()
+        if depth not in ("0", "1", "infinity"):
+            raise zope.webdav.interfaces.BadRequest(
+                self.request, message = u"Invalid Depth header supplied")
+
+        propertiesFactory = None
+        extraArg = None
+
+        propfind = self.request.xmlDataSource
+        if propfind is not None:
+            if propfind.tag != "{DAV:}propfind":
+                raise zope.webdav.interfaces.UnprocessableError(
+                    self.context,
+                    message = u"Request is not a `propfind' XML element.")
+            properties = propfind[0]
+            if properties.tag == "{DAV:}propname":
+                propertiesFactory = self.renderPropnames
+            elif properties.tag == "{DAV:}allprop":
+                propertiesFactory = self.renderAllProperties
+                includes = propfind.findall("{DAV:}include")
+                if includes: # we have "DAV:include" properties
+                    extraArg = includes[0]
+            elif properties.tag == "{DAV:}prop":
+                if len(properties) == 0:
+                    ## XXX - does this code correspond to the protocol.
+                    propertiesFactory = self.renderAllProperties
+                else:
+                    propertiesFactory = self.renderSelectedProperties
+                    extraArg = properties
+            else:
+                raise zope.webdav.interfaces.UnprocessableError(
+                    self.context,
+                    message = u"Unknown propfind property element.")
+        else:
+            propertiesFactory = self.renderAllProperties
+
+        multistatus = zope.webdav.utils.MultiStatus()
+        responses = self.handlePropfindResource(
+            self.context, self.request, depth, propertiesFactory, extraArg)
+        multistatus.responses.extend(responses)
+
+        etree = component.getUtility(IEtree)
+
+        self.request.response.setStatus(207)
+        self.request.response.setHeader("content-type", "application/xml")
+        ## Is UTF-8 encoding ok here or is there a better way of doing this.
+        return etree.tostring(multistatus(), encoding = "utf-8")
+
+    def handlePropfindResource(self, ob, req, depth, \
+                               propertiesFactory, extraArg):
+        """
+        Recursive method that collects all the `response' XML elements for
+        the current PROPFIND request.
+
+        `propertiesFactory' is the method that is used to generated the
+        `response' XML element for one resource. It takes the resource,
+        request and `extraArg' used to pass in specific information about
+        the properties we want to return.
+        """
+        responses = [propertiesFactory(ob, req, extraArg)]
+
+        if depth in ("1", "infinity") and IReadContainer.providedBy(ob):
+            subdepth = (depth == "1") and "0" or "infinity"
+
+            for subob in ob.values():
+                responses.extend(self.handlePropfindResource(
+                    subob, req, subdepth, propertiesFactory, extraArg))
+
+        return responses
+
+    def renderPropnames(self, ob, req, ignore):
+        response = zope.webdav.utils.Response(
+            zope.webdav.utils.getObjectURL(ob, req))
+
+        for davprop, adapter in \
+                zope.webdav.properties.getAllProperties(ob, req):
+            davwidget = zope.webdav.properties.getWidget(davprop, adapter, req)
+            response.addProperty(200, davwidget.renderName())
+
+        return response
+
+    def renderAllProperties(self, ob, req, include):
+        response = zope.webdav.utils.Response(
+            zope.webdav.utils.getObjectURL(ob, req))
+
+        for davprop, adapter in \
+                zope.webdav.properties.getAllProperties(ob, req):
+            if davprop.restricted:
+                if include is None or \
+                       include.find("{%s}%s" %(davprop.namespace,
+                                               davprop.__name__)) is None:
+                    continue
+
+            davwidget = zope.webdav.properties.getWidget(davprop, adapter, req)
+            response.addProperty(200, davwidget.render())
+
+        return response
+
+    def renderSelectedProperties(self, ob, req, props):
+        response = zope.webdav.utils.Response(
+            zope.webdav.utils.getObjectURL(ob, req))
+
+        etree = component.getUtility(IEtree)
+
+        for prop in props:
+            try:
+                davprop, adapter = zope.webdav.properties.getProperty(
+                    ob, req, prop.tag, exists = True)
+                davwidget = zope.webdav.properties.getWidget(
+                    davprop, adapter, req)
+                propstat = response.getPropstat(200)
+                propstat.properties.append(davwidget.render())
+            except Exception, error:
+                exc_info = sys.exc_info()
+
+                error_view = component.queryMultiAdapter(
+                    (error, req), zope.webdav.interfaces.IDAVErrorWidget)
+                if error_view is None:
+                    ## XXX - needs testing
+                    raise exc_info[0], exc_info[1], exc_info[2]
+
+                propstat = response.getPropstat(error_view.status)
+                ## XXX - needs testing
+                propstat.responsedescription += error_view.propstatdescription
+                response.responsedescription += error_view.responsedescription
+
+                propstat.properties.append(etree.Element(prop.tag))
+
+        return response


Property changes on: zope.webdav/trunk/src/zope/webdav/propfind.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/proppatch.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/proppatch.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/proppatch.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,147 @@
+##############################################################################
+# Copyright (c) 2006 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.
+##############################################################################
+"""WebDAV PROPPATCH method.
+
+  <!ELEMENT propertyupdate (remove | set)+ >
+  <!ELEMENT remove (prop) >
+  <!ELEMENT set (prop) >
+  <!ELEMENT prop ANY >
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import component
+
+import zope.webdav.utils
+import zope.webdav.interfaces
+import zope.webdav.properties
+from zope.webdav.ietree import IEtree
+
+
+class PROPPATCH(object):
+    """PROPPATCH handler for all objects"""
+    interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+    component.adapts(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def PROPPATCH(self):
+        if self.request.content_type not in ("text/xml", "application/xml"):
+            raise zope.webdav.interfaces.BadRequest(
+                self.request,
+                message = "All PROPPATCH requests needs a XML body")
+
+        xmldata = self.request.xmlDataSource
+        if xmldata.tag != "{DAV:}propertyupdate":
+            raise zope.webdav.interfaces.UnprocessableError(
+                self.context,
+                message = u"PROPPATCH request body must be a "
+                           "`propertyupdate' XML element.")
+
+        etree = component.getUtility(IEtree)
+
+        # isError - an error occurred during the processing of the request.
+        isError = False
+        # propErrors - list of (property tag, error). error is None if no
+        #              error occurred in setting / removing the property.
+        propErrors = []
+        # properties - list of all the properties that we handled correctly.
+        properties = []
+        for update in xmldata:
+            if update.tag not in ("{DAV:}set", "{DAV:}remove"):
+                continue
+
+            props = update.findall("{DAV:}prop")
+            if not props:
+                continue
+
+            props = props[0]
+
+            for prop in props:
+                try:
+                    if update.tag == "{DAV:}set":
+                        self.handleSet(prop)
+                    else:
+                        self.handleRemove(prop)
+                except Exception, error:
+                    isError = True
+                    propErrors.append((prop.tag, error))
+                else:
+                    properties.append(prop.tag)
+
+        if isError:
+            errors = zope.webdav.interfaces.WebDAVPropstatErrors(self.context)
+
+            for prop, error in propErrors:
+                errors[prop] = error
+
+            for proptag in properties:
+                errors[proptag] = zope.webdav.interfaces.FailedDependency(
+                    self.context, proptag)
+
+            raise errors # this kills the current transaction.
+
+        url = zope.webdav.utils.getObjectURL(self.context, self.request)
+        response = zope.webdav.utils.Response(url)
+        propstat = response.getPropstat(200)
+
+        for proptag in properties:
+            propstat.properties.append(etree.Element(proptag))
+
+        multistatus = zope.webdav.utils.MultiStatus()
+        multistatus.responses.append(response)
+
+        self.request.response.setStatus(207)
+        self.request.response.setHeader("content-type", "application/xml")
+        return etree.tostring(multistatus())
+
+    def handleSet(self, prop):
+        davprop, adapter = zope.webdav.properties.getProperty(
+            self.context, self.request, prop.tag)
+
+        widget = zope.webdav.properties.getWidget(
+            davprop, adapter, self.request,
+            type = zope.webdav.interfaces.IDAVInputWidget)
+
+        field = davprop.field.bind(adapter)
+
+        if field.readonly:
+            raise zope.webdav.interfaces.ForbiddenError(
+                self.context, prop.tag, message = u"readonly field")
+
+        value = widget.getInputValue()
+        field.validate(value)
+
+        if field.get(adapter) != value:
+            field.set(adapter, value)
+
+    def handleRemove(self, prop):
+        davprop = component.queryUtility(
+            zope.webdav.interfaces.IDAVProperty, prop.tag, None)
+
+        if davprop is not None:
+            raise zope.webdav.interfaces.ConflictError(
+                self.context, prop.tag,
+                message = u"cannot remove a live property")
+
+        deadproperties = zope.webdav.interfaces.IOpaquePropertyStorage(
+            self.context, None)
+
+        if deadproperties is None or not deadproperties.hasProperty(prop.tag):
+            raise zope.webdav.interfaces.ConflictError(
+                self.context, prop.tag, message = u"property doesn't exist")
+
+        deadproperties.removeProperty(prop.tag)


Property changes on: zope.webdav/trunk/src/zope/webdav/proppatch.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/publisher.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/publisher.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/publisher.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,92 @@
+##############################################################################
+# Copyright (c) 2006 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.
+##############################################################################
+"""WebDAV publishing objects including the request and response.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import time
+import sys
+from xml.parsers.expat import ExpatError
+try:
+    from lxml.etree import XMLSyntaxError
+except ImportError:
+    # no lxml - so create a dummy excpetion
+    class XMLSyntaxError(Exception):
+        pass
+
+from zope import component
+from zope.interface import implements
+from zope.publisher.http import HTTPResponse, HTTPRequest
+from zope.app.publication.http import HTTPPublication
+from zope.app.publication.interfaces import IRequestPublicationFactory
+
+from ietree import IEtree
+import interfaces
+
+
+class WebDAVResponse(HTTPResponse):
+    implements(interfaces.IWebDAVResponse)
+
+
+class WebDAVRequest(HTTPRequest):
+    implements(interfaces.IWebDAVRequest)
+
+    __slot__ = (
+        'xmlDataSource', # holds xml.dom representation of the input stream
+                         # if it is XML otherwise it is None.
+        'content_type',  # 
+        )
+
+    def __init__(self, body_instream, environ, reponse = None,
+                 positional = None, outstream = None):
+        super(WebDAVRequest, self).__init__(body_instream, environ)
+
+        self.xmlDataSource = None
+        self.content_type = None
+
+    def processInputs(self):
+        """See IPublisherRequest."""
+        content_type = self.getHeader('content-type', '')
+        content_type_params = None
+        if ';' in content_type:
+            parts = content_type.split(';', 1)
+            content_type = parts[0].strip().lower()
+            content_type_params = parts[1].strip()
+
+        self.content_type = content_type
+
+        if content_type in ("text/xml", "application/xml") and \
+               self.getHeader("content-length", 0) > 0:
+            etree = component.getUtility(IEtree)
+            try:
+                self.xmlDataSource = etree.parse(self.bodyStream).getroot()
+            except (ExpatError, XMLSyntaxError):
+                raise interfaces.BadRequest(
+                    self, u"Invalid xml data passed")
+
+    def _createResponse(self):
+        """Create a specific WebDAV response object."""
+        return WebDAVResponse()
+
+
+class WebDAVRequestFactory(object):
+    implements(IRequestPublicationFactory)
+
+    def canHandle(self, environment):
+        return True
+
+    def __call__(self):
+        request_class = component.queryUtility(
+            interfaces.IWebDAVRequestFactory, default = WebDAVRequest)
+        return request_class, HTTPPublication


Property changes on: zope.webdav/trunk/src/zope/webdav/publisher.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/testing.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/testing.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/testing.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,144 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Zope Element Tree Support
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import zope.component
+import zope.app.component
+from zope.configuration import xmlconfig
+from zope.app.testing import setup
+
+from ietree import IEtree
+import zope.webdav
+
+#
+# Setup for Zope etree. 
+#
+
+def etreeSetup(test = None):
+    setup.placelessSetUp()
+    context = xmlconfig.file("meta.zcml", package = zope.app.component)
+    xmlconfig.file("ietree-configure.zcml", package = zope.webdav,
+                   context = context)
+
+    etreeEtree = zope.component.getUtility(IEtree)
+
+    if test is not None:
+        test.globs["etree"] = etreeEtree
+        test.globs["assertXMLEqual"] = assertXMLEqual
+    return etreeEtree
+
+def etreeTearDown(test = None):
+    if test is not None:
+        del test.globs["etree"]
+        del test.globs["assertXMLEqual"]
+    etreeEtree = zope.component.getUtility(IEtree)
+    zope.component.getGlobalSiteManager().unregisterUtility(etreeEtree)
+
+    setup.placelessTearDown()
+
+#
+# Handy methods for testing if two xml fragmenets are equal.
+#
+
+def _assertTextEqual(got, expected):
+    """
+      >>> _assertTextEqual(None, "\\n")
+      True
+
+      >>> _assertTextEqual("\\n", "\\n")
+      True
+
+      >>> _assertTextEqual("test", "test")
+      True
+
+    """
+    tgot = got and got.strip()
+    texpected = expected and expected.strip()
+
+    error_msg = "'%r != %r' have different element content." %(
+        got, expected)
+
+    if not tgot:
+        assert not texpected, error_msg
+        return True
+
+    if not texpected:
+        assert not tgot, error_msg
+        return True
+
+    assert isinstance(tgot, (str, unicode)), error_msg
+    assert isinstance(texpected, (str, unicode)), error_msg
+
+    assert tgot == texpected, error_msg
+
+    return True
+
+def _assertXMLElementEqual(got, expected):
+    etree = zope.component.getUtility(IEtree)
+
+    assert got.tag == expected.tag, \
+           "'%r != %r' different tag name." %(got.tag, expected.tag)
+    assert len(got) == len(expected), \
+           "'%d != %d' different number of subchildren on %r." %(
+               len(got), len(expected), got.tag)
+    _assertTextEqual(got.text, expected.text)
+
+    for index in range(0, len(got)):
+        _assertXMLElementEqual(got[index], expected[index])
+
+
+def assertXMLEqual(got, expected):
+    """
+      >>> etree = etreeSetup()
+      >>> assertXMLEqual('<test>xml</test>', '<test>xml</test>')
+
+      >>> assertXMLEqual('<test>xml</test>', '<test>xml1</test>')
+      Traceback (most recent call last):
+      ...
+      AssertionError: ''xml' != 'xml1'' have different element content.
+
+      >>> assertXMLEqual('<test><subtest>Test</subtest></test>',
+      ...                '<test>Test</test>')
+      Traceback (most recent call last):
+      ...
+      AssertionError: '1 != 0' different number of subchildren on 'test'.
+
+      >>> assertXMLEqual('<test1/>', '<test2/>')
+      Traceback (most recent call last):
+      ...
+      AssertionError: ''test1' != 'test2'' different tag name.
+
+      >>> assertXMLEqual('<a><b><c /></b></a>', '<a><b><c/></b></a>')
+      >>> etreeTearDown()
+
+    """
+    etree = zope.component.getUtility(IEtree)
+
+    if isinstance(got, (str, unicode)):
+        got = etree.fromstring(got)
+    if isinstance(expected, (str, unicode)):
+        expected = etree.fromstring(expected)
+
+    if getattr(got, "getroot", None) is not None:
+        # XXX - is this all neccessary.
+        got = got.getroot()
+
+        assert getattr(expected, "getroot", None) is not None
+        expected = expected.getroot()
+
+    _assertXMLElementEqual(got, expected)


Property changes on: zope.webdav/trunk/src/zope/webdav/testing.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/tests/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/__init__.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/__init__.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,17 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Collection of tests for zope.webdav
+
+$Id$
+"""


Property changes on: zope.webdav/trunk/src/zope/webdav/tests/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/tests/test_copymove.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_copymove.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_copymove.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,450 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 WebDAV COPY and MOVE methods.
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+
+from zope import interface
+from zope import component
+from zope.copypastemove.interfaces import IObjectCopier, IObjectMover
+from zope.location.traversing import LocationPhysicallyLocatable
+from zope.traversing.adapters import Traverser, DefaultTraversable
+from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.app.publication.http import MethodNotAllowed
+from zope.traversing.interfaces import IContainmentRoot
+
+import zope.webdav.publisher
+from zope.webdav.copymove import COPY, MOVE
+
+import test_locking
+
+class TestRequest(zope.webdav.publisher.WebDAVRequest):
+
+    def __init__(self, environ = {}):
+        env = environ.copy()
+        env.setdefault("HTTP_HOST", "localhost")
+        super(TestRequest, self).__init__(StringIO(""), env)
+
+        self.processInputs()
+
+
+def baseSetUp():
+    gsm = component.getGlobalSiteManager()
+    gsm.registerAdapter(LocationPhysicallyLocatable,
+                        (test_locking.IResource,))
+    gsm.registerAdapter(LocationPhysicallyLocatable,
+                        (test_locking.ICollectionResource,))
+    gsm.registerAdapter(Traverser, (test_locking.IResource,))
+    gsm.registerAdapter(Traverser, (test_locking.ICollectionResource,))
+    gsm.registerAdapter(DefaultTraversable, (test_locking.IResource,))
+    gsm.registerAdapter(DefaultTraversable,
+                        (test_locking.ICollectionResource,))
+
+
+def baseTearDown():
+    gsm = component.getGlobalSiteManager()
+    gsm.unregisterAdapter(LocationPhysicallyLocatable,
+                          (test_locking.IResource,))
+    gsm.unregisterAdapter(LocationPhysicallyLocatable,
+                          (test_locking.ICollectionResource,))
+    gsm.unregisterAdapter(Traverser, (test_locking.IResource,))
+    gsm.unregisterAdapter(Traverser, (test_locking.ICollectionResource,))
+    gsm.unregisterAdapter(DefaultTraversable, (test_locking.IResource,))
+    gsm.unregisterAdapter(DefaultTraversable,
+                          (test_locking.ICollectionResource,))
+
+
+class COPYMOVEParseHeadersTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.root = test_locking.RootCollectionResource()
+
+        baseSetUp()
+
+    def tearDown(self):
+        del self.root
+
+        baseTearDown()
+
+    def test_no_overwrite(self):
+        request = TestRequest()
+        copy = COPY(None, request)
+
+        self.assertEqual(copy.getOverwrite(), True)
+
+    def test_T_overwrite(self):
+        request = TestRequest(environ = {"OVERWRITE": "t"})
+        copy = COPY(None, request)
+
+        self.assertEqual(copy.getOverwrite(), True)
+
+    def test_t_overwrite(self):
+        request = TestRequest(environ = {"OVERWRITE": "T"})
+        copy = COPY(None, request)
+
+        self.assertEqual(copy.getOverwrite(), True)
+
+    def test_F_overwrite(self):
+        request = TestRequest(environ = {"OVERWRITE": "F"})
+        copy = COPY(None, request)
+
+        self.assertEqual(copy.getOverwrite(), False)
+
+    def test_f_overwrite(self):
+        request = TestRequest(environ = {"OVERWRITE": "f"})
+        copy = COPY(None, request)
+
+        self.assertEqual(copy.getOverwrite(), False)
+
+    def test_bad_overwrite(self):
+        request = TestRequest(environ = {"OVERWRITE": "x"})
+        copy = COPY(None, request)
+
+        self.assertRaises(zope.webdav.interfaces.BadRequest, copy.getOverwrite)
+
+    def test_default_destination_path(self):
+        request = TestRequest()
+        copy = COPY(None, request)
+
+        self.assertRaises(
+            zope.webdav.interfaces.BadRequest, copy.getDestinationPath)
+
+    def test_destination_path(self):
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/testpath"})
+        copy = COPY(None, request)
+
+        self.assertEqual(copy.getDestinationPath(), "/testpath")
+
+    def test_destination_path_slash(self):
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/testpath/"})
+        copy = COPY(None, request)
+
+        self.assertEqual(copy.getDestinationPath(), "/testpath")
+
+    def test_getDestinationPath_wrong_server(self):
+        request = TestRequest(
+            environ = {"DESTINATION": "http://www.server.com/testpath"})
+        copy = COPY(None, request)
+
+        self.assertRaises(zope.webdav.interfaces.BadGateway,
+                          copy.getDestinationPath)
+
+    def test_getDestinationNameAndParentObject(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/testpath"})
+
+        copy = COPY(resource, request)
+        destname, destob, parent = copy.getDestinationNameAndParentObject()
+        self.assertEqual(destname, "testpath")
+        self.assertEqual(destob, None)
+        self.assertEqual(parent, self.root)
+
+    def test_getDestinationNameAndParentObject_destob_overwrite(self):
+        destresource = self.root["destresource"] = test_locking.Resource()
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/destresource",
+                       "OVERWRITE": "T"})
+
+        copy = COPY(resource, request)
+        destname, destob, parent = copy.getDestinationNameAndParentObject()
+        self.assertEqual(destname, "destresource")
+        self.assertEqual(destob, destresource)
+        self.assert_("destresource" not in self.root)
+        self.assertEqual(parent, self.root)
+
+    def test_getDestinationNameAndParentObject_destob_overwrite_failed(self):
+        destresource = self.root["destresource"] = test_locking.Resource()
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/destresource",
+                       "OVERWRITE": "F"})
+
+        copy = COPY(resource, request)
+        self.assertRaises(zope.webdav.interfaces.PreconditionFailed,
+                          copy.getDestinationNameAndParentObject)
+        self.assert_("destresource" in self.root)
+
+    def test_getDestinationNameAndParentObject_noparent(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/noparent/testpath"})
+
+        copy = COPY(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ConflictError,
+                          copy.getDestinationNameAndParentObject)
+
+    def test_getDestinationNameAndParentObject_destob_sameob(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/resource",
+                       "OVERWRITE": "T"})
+
+        copy = COPY(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ForbiddenError,
+                          copy.getDestinationNameAndParentObject)
+
+    def test_nocopier(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+        copy = COPY(resource, request)
+        self.assertRaises(MethodNotAllowed, copy.COPY)
+
+    def test_nomovier(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+        copy = MOVE(resource, request)
+        self.assertRaises(MethodNotAllowed, copy.MOVE)
+
+
+class DummyResourceURL(object):
+    interface.implements(IAbsoluteURL)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    def __str__(self):
+        if getattr(self.context, "__parent__", None) is not None:
+            path = DummyResourceURL(self.context.__parent__, None)()
+        elif IContainmentRoot.providedBy(self.context):
+            return ""
+        else:
+            path = ""
+
+        if getattr(self.context, "__name__", None) is not None:
+            path += "/" + self.context.__name__
+        elif test_locking.IResource.providedBy(self.context):
+            path += "/resource"
+        elif test_locking.ICollectionResource.providedBy(self.context):
+            path += "/collection"
+        else:
+            raise ValueError("unknown context type")
+
+        return path
+
+    __call__ = __str__
+
+
+class Copier(object):
+    interface.implements(IObjectCopier)
+
+    iscopyable = True
+    canCopyableTo = True
+
+    def __init__(self, context):
+        self.context = context
+
+    def copyable(self):
+        return self.iscopyable
+
+    def copyTo(self, target, new_name):
+        target[new_name] = self.context
+
+        return new_name
+
+    def copyableTo(self, parent, destname):
+        return self.canCopyableTo
+
+
+class COPYObjectTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.root = test_locking.RootCollectionResource()
+
+        baseSetUp()
+
+        Copier.iscopyable = True
+        Copier.canCopyableTo = True
+        gsm = component.getGlobalSiteManager()
+        gsm.registerAdapter(Copier, (test_locking.IResource,))
+        gsm.registerAdapter(DummyResourceURL,
+                            (test_locking.IResource,
+                             zope.webdav.interfaces.IWebDAVRequest))
+
+    def tearDown(self):
+        del self.root
+
+        baseTearDown()
+
+        gsm = component.getGlobalSiteManager()
+        gsm.unregisterAdapter(Copier, (test_locking.IResource,))
+        gsm.unregisterAdapter(DummyResourceURL,
+                              (test_locking.IResource,
+                               zope.webdav.interfaces.IWebDAVRequest))
+
+    def test_copy(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+        copy = COPY(resource, request)
+        result = copy.COPY()
+
+        self.assertEqual(request.response.getStatus(), 201)
+        self.assertEqual(request.response.getHeader("Location"),
+                         "/copy_of_resource")
+        self.assertEqual(result, "")
+        self.assertEqual(self.root["copy_of_resource"] is resource, True)
+        self.assertEqual(self.root["resource"] is resource, True)
+
+    def test_copy_overwrite(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        resource2 = self.root["resource2"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/resource2",
+                       "OVERWRITE": "T"})
+
+        copy = COPY(resource, request)
+        result = copy.COPY()
+
+        self.assertEqual(request.response.getStatus(), 204)
+        self.assertEqual(result, "")
+        self.assertEqual(self.root["resource"] is resource, True)
+        self.assertEqual(self.root["resource2"] is resource, True)
+
+    def test_copy_not_copyable(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+        Copier.iscopyable = False
+
+        copy = COPY(resource, request)
+        self.assertRaises(MethodNotAllowed, copy.COPY)
+
+    def test_copy_not_copyableto(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+        Copier.canCopyableTo = False
+
+        copy = COPY(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ConflictError, copy.COPY)
+
+
+class Movier(object):
+    interface.implements(IObjectMover)
+
+    isMoveable = True
+    isMoveableTo = True
+
+    def __init__(self, context):
+        self.context = context
+
+    def moveTo(self, target, new_name):
+        del self.context.__parent__[self.context.__name__]
+        target[new_name] = self.context
+
+        return new_name
+
+    def moveable(self):
+        return self.isMoveable
+
+    def moveableTo(self, target, name = None):
+        return self.isMoveableTo
+
+
+class MOVEObjectTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.root = test_locking.RootCollectionResource()
+
+        baseSetUp()
+
+        Movier.isMoveable = True
+        Movier.isMoveableTo = True
+        gsm = component.getGlobalSiteManager()
+        gsm.registerAdapter(Movier, (test_locking.IResource,))
+        gsm.registerAdapter(DummyResourceURL,
+                            (test_locking.IResource,
+                             zope.webdav.interfaces.IWebDAVRequest))
+
+    def tearDown(self):
+        del self.root
+
+        baseTearDown()
+
+        gsm = component.getGlobalSiteManager()
+        gsm.unregisterAdapter(Movier, (test_locking.IResource,))
+        gsm.unregisterAdapter(DummyResourceURL,
+                              (test_locking.IResource,
+                               zope.webdav.interfaces.IWebDAVRequest))
+
+    def test_move(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+        move = MOVE(resource, request)
+        result = move.MOVE()
+
+        self.assertEqual(request.response.getStatus(), 201)
+        self.assertEqual(request.response.getHeader("Location"),
+                         "/copy_of_resource")
+        self.assertEqual("resource" not in self.root, True)
+        self.assertEqual(self.root["copy_of_resource"], resource)
+
+    def test_move_overwrite(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        resource2 = self.root["resource2"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/resource2",
+                       "OVERWRITE": "T"})
+
+        move = MOVE(resource, request)
+        result = move.MOVE()
+
+        self.assertEqual(request.response.getStatus(), 204)
+        self.assertEqual("resource" not in self.root, True)
+        self.assertEqual(self.root["resource2"] is resource, True)
+
+    def test_move_not_moveable(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+        Movier.isMoveable = False
+
+        move = MOVE(resource, request)
+        self.assertRaises(MethodNotAllowed, move.MOVE)
+
+    def test_move_not_moveableTo(self):
+        resource = self.root["resource"] = test_locking.Resource()
+        request = TestRequest(
+            environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+        Movier.isMoveableTo = False
+
+        move = MOVE(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ConflictError, move.MOVE)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(COPYMOVEParseHeadersTestCase),
+        unittest.makeSuite(COPYObjectTestCase),
+        unittest.makeSuite(MOVEObjectTestCase),
+        ))


Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_copymove.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,207 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Run all doctests contained within zope.webdav.
+
+$Id$
+"""
+import unittest
+import UserDict
+
+from zope.testing import doctest
+
+import zope.event
+from zope import component
+from zope import interface
+from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.app.container.interfaces import IContained, IContainer
+from zope.app.testing import placelesssetup
+import zope.app.keyreference.interfaces
+from zope.component.interfaces import IComponentLookup
+from zope.app.component.site import SiteManagerAdapter
+from zope.security.testing import Principal, Participation
+from zope.security.management import newInteraction, endInteraction, \
+     queryInteraction
+from zope.traversing.browser.interfaces import IAbsoluteURL
+
+from zope.webdav.testing import etreeSetup, etreeTearDown
+
+
+class IDemo(IContained):
+    "a demonstration interface for a demonstration class"
+
+
+class IDemoFolder(IContained, IContainer):
+    "a demostration interface for a demostration folder class"
+
+
+class Demo(object):
+    interface.implements(IDemo, IAttributeAnnotatable)
+
+    __parent__ = __name__ = None
+
+
+class DemoFolder(UserDict.UserDict):
+    interface.implements(IDemoFolder)
+
+    __parent__ = __name__ = None
+
+    def __init__(self, parent = None, name = u''):
+        UserDict.UserDict.__init__(self)
+        self.__parent__ = parent
+        self.__name__   = name
+
+    def __setitem__(self, key, value):
+        value.__name__ = key
+        value.__parent__ = self
+        self.data[key] = value
+
+
+class DemoKeyReference(object):
+    _class_counter = 0
+    interface.implements(zope.app.keyreference.interfaces.IKeyReference)
+
+    def __init__(self, context):
+        self.context = context
+        class_ = type(self)
+        self._id = getattr(context, "__demo_key_reference__", None)
+        if self._id is None:
+            self._id = class_._class_counter
+            context.__demo_key_reference__ = self._id
+            class_._class_counter += 1
+
+    key_type_id = "zope.app.dav.lockingutils.DemoKeyReference"
+
+    def __call__(self):
+        return self.context
+
+    def __hash__(self):
+        return (self.key_type_id, self._id)
+
+    def __cmp__(self, other):
+        if self.key_type_id == other.key_type_id:
+            return cmp(self._id, other._id)
+        return cmp(self.key_type_id, other.key_type_id)
+
+
+class DemoAbsoluteURL(object):
+    interface.implements(IAbsoluteURL)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    def __str__(self):
+        ob = self.context
+        url = ""
+        while ob is not None:
+            url += "/dummy"
+            ob = ob.__parent__
+        if IDemoFolder.providedBy(self.context):
+            url += "/"
+        return url
+
+    __call__ = __str__
+
+
+def contentSetup(test):
+    test.globs["Demo"] = Demo
+    test.globs["DemoFolder"] = DemoFolder
+
+
+def contentTeardown(test):
+    del test.globs["Demo"]
+    del test.globs["DemoFolder"]
+
+
+def lockingSetUp(test):
+    placelesssetup.setUp(test)
+
+    # create principal
+    participation = Participation(Principal('michael'))
+    if queryInteraction() is not None:
+        queryInteraction().add(participation)
+    else:
+        newInteraction(participation)
+
+    events = test.globs["events"] = []
+    zope.event.subscribers.append(events.append)
+
+    component.getGlobalSiteManager().registerAdapter(
+        DemoKeyReference, (IDemo,),
+        zope.app.keyreference.interfaces.IKeyReference)
+    component.getGlobalSiteManager().registerAdapter(
+        DemoKeyReference, (IDemoFolder,),
+        zope.app.keyreference.interfaces.IKeyReference)
+    component.getGlobalSiteManager().registerAdapter(
+        SiteManagerAdapter, (interface.Interface,), IComponentLookup)
+    component.getGlobalSiteManager().registerAdapter(
+        DemoAbsoluteURL,
+        (IDemo, interface.Interface),
+        IAbsoluteURL)
+    component.getGlobalSiteManager().registerAdapter(
+        DemoAbsoluteURL,
+        (IDemoFolder, interface.Interface),
+        IAbsoluteURL)
+
+    # expose these classes to the test
+    test.globs["Demo"] = Demo
+    test.globs["DemoFolder"] = DemoFolder
+
+
+def lockingTearDown(test):
+    placelesssetup.tearDown(test)
+
+    events = test.globs.pop("events")
+    assert zope.event.subscribers.pop().__self__ is events
+    del events[:] # being paranoid
+
+    del test.globs["Demo"]
+    del test.globs["DemoFolder"]
+
+    component.getGlobalSiteManager().unregisterAdapter(
+        DemoKeyReference, (IDemo,),
+        zope.app.keyreference.interfaces.IKeyReference)
+    component.getGlobalSiteManager().unregisterAdapter(
+        DemoKeyReference, (IDemoFolder,),
+        zope.app.keyreference.interfaces.IKeyReference)
+    component.getGlobalSiteManager().unregisterAdapter(
+        SiteManagerAdapter, (interface.Interface,), IComponentLookup)
+    component.getGlobalSiteManager().unregisterAdapter(
+        DemoAbsoluteURL,
+        (IDemo, interface.Interface), IAbsoluteURL)
+    component.getGlobalSiteManager().unregisterAdapter(
+        DemoAbsoluteURL,
+        (IDemoFolder, interface.Interface),
+        IAbsoluteURL)
+
+    endInteraction()
+
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocTestSuite("zope.webdav.properties",
+                             setUp = contentSetup, tearDown = contentTeardown),
+        doctest.DocTestSuite("zope.webdav.utils",
+                             setUp = etreeSetup, tearDown = etreeTearDown),
+##         doctest.DocTestSuite("zope.webdav.zetree", optionflags = \
+##                              doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE),
+        doctest.DocTestSuite("zope.webdav.testing",
+                             optionflags = doctest.ELLIPSIS + \
+                                           doctest.NORMALIZE_WHITESPACE),
+        doctest.DocTestSuite("zope.webdav.coreproperties"),
+        doctest.DocFileSuite("datamodel.txt", package = "zope.webdav"),
+        doctest.DocTestSuite("zope.webdav.lockingutils",
+                             setUp = lockingSetUp, tearDown = lockingTearDown),
+        doctest.DocTestSuite("zope.webdav.deadproperties"),
+        doctest.DocTestSuite("zope.webdav.adapters"),
+        ))


Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/tests/test_inputwidgets.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_inputwidgets.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_inputwidgets.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,250 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 WebDAV widget framework.
+
+$Id$
+"""
+
+import unittest
+import datetime
+from cStringIO import StringIO
+
+from zope import component
+from zope import schema
+from zope import interface
+from zope.schema.interfaces import RequiredMissing
+from zope.interface.verify import verifyObject
+from zope.app.form.interfaces import ConversionError, MissingInputError
+from zope.datetime import tzinfo
+
+from zope.webdav import widgets
+from zope.webdav.interfaces import IDAVInputWidget
+from zope.webdav.ietree import IEtree
+from zope.webdav.testing import etreeSetup, etreeTearDown
+
+from test_widgets import TestWebDAVRequest
+
+class _WebDAVWidgetTest(unittest.TestCase):
+
+    namespace = u'testns:'
+    name = u'foo'
+    missing_name = u'someotherproperty'
+
+    field_content    = u'' # field value assigned to the demo content.
+    rendered_content = None # string value of the some value in ...
+    bad_rendered_content = None # if not None try to parse this data.
+
+    _FieldFactory  = None
+    _WidgetFactory = None
+
+    def tearDown(self):
+        etreeTearDown()
+        del self.etree
+
+    def setUp(self):
+        etreeSetup()
+        self.etree = component.getUtility(IEtree)
+
+    def setUpContent(self, desc = u'', title = u'Foo Title', element = None):
+        ## setup the field first to stop some really weird errors
+        foofield = self._FieldFactory(
+            title = title,
+            description = desc)
+        class IDemoContent(interface.Interface):
+            foo = foofield
+
+        class DemoContent(object):
+            interface.implements(IDemoContent)
+
+        self.content = DemoContent()
+        field = IDemoContent['foo']
+        self.field = field.bind(self.content)
+        self.setUpWidget(element)
+
+    def setUpWidget(self, element = None):
+        request = TestWebDAVRequest(element)
+        self.widget = self._WidgetFactory(self.field, request)
+        self.widget.namespace = self.namespace
+
+
+class WebDAVBaseInputWidgetTest(_WebDAVWidgetTest):
+
+    _FieldFactory = schema.Text
+    _WidgetFactory = widgets.DAVInputWidget
+
+    def setUp(self):
+        super(WebDAVBaseInputWidgetTest, self).setUp()
+        self.setUpContent()
+
+    def test_dontuseDAVWidgetToFieldValue(self):
+        self.assertRaises(NotImplementedError, self.widget.toFieldValue,
+                          self.etree.Element(self.etree.QName(self.namespace,
+                                                              self.name)))
+
+
+class WebDAVInputWidgetTest(_WebDAVWidgetTest):
+
+    _FieldFactory = schema.Text
+    _WidgetFactory = widgets.TextDAVInputWidget
+
+    def setUp(self):
+        super(WebDAVInputWidgetTest, self).setUp()
+
+        self.element = self.etree.Element(
+            self.etree.QName(self.namespace, self.name))
+        if self.rendered_content is not None:
+            self.element.text = self.rendered_content
+        self.setUpContent(element = self.element)
+
+    def test_interface(self):
+        self.assertEqual(verifyObject(IDAVInputWidget, self.widget), True)
+
+    def test_hasInput(self):
+        self.assertEqual(self.widget.hasInput(), True)
+
+    def test_convert(self):
+        self.assertEqual(self.widget.toFieldValue(self.element),
+                         self.field_content)
+
+    def test_getInputValue(self):
+        value = self.widget.getInputValue()
+        self.assertEqual(value, self.field_content)
+        self.field.validate(value) # this will raise an exception if false
+
+    def test_noInput(self):
+        element = self.etree.Element(
+            self.etree.QName(self.namespace, self.missing_name))
+        element.text = self.rendered_content
+        request = TestWebDAVRequest(element)
+        widget  = self._WidgetFactory(self.field, request)
+        widget.namespace = self.namespace
+        self.assertEqual(widget.hasInput(), False)
+
+    def test_getInputValue_NoInput(self):
+        element = self.etree.Element(
+            self.etree.QName(self.namespace, self.missing_name))
+        element.text = self.rendered_content
+        request = TestWebDAVRequest(element)
+        widget  = self._WidgetFactory(self.field, request)
+        widget.namespace = self.namespace
+        self.assertRaises(MissingInputError, widget.getInputValue)
+
+    def _test_badinput(self, bad_rendered_content = None):
+        # Only run this test if the self.bad_rendered_content is not None.
+        # The test case must expility call this method.
+        if bad_rendered_content is None:
+            bad_rendered_content = self.bad_rendered_content
+        element = self.etree.Element(self.etree.QName(self.namespace, self.name))
+        element.text = bad_rendered_content
+        request = TestWebDAVRequest(element)
+        widget = self._WidgetFactory(self.field, request)
+        widget.namespace = self.namespace
+        self.assertRaises(ConversionError, widget.getInputValue)
+
+
+class TextWebDAVInputWidgetTest(WebDAVInputWidgetTest):
+
+    _FieldFactory  = schema.Text
+    _WidgetFactory = widgets.TextDAVInputWidget
+
+    field_content = u"Foo Value"
+    rendered_content = u"Foo Value"
+
+    def test_noinput(self):
+        element = self.etree.Element(self.etree.QName(self.namespace, self.name))
+        request = TestWebDAVRequest(element)
+        widget = self._WidgetFactory(self.field, request)
+        widget.namespace = self.namespace
+        value = widget.getInputValue()
+        self.field.validate(value)
+
+
+class IntWebDAVInputWidgetTest(WebDAVInputWidgetTest):
+
+    _FieldFactory  = schema.Int
+    _WidgetFactory = widgets.IntDAVInputWidget
+
+    field_content = 10
+    rendered_content = u"10"
+    bad_rendered_content = u"X10"
+
+    def test_badinput(self):
+        super(IntWebDAVInputWidgetTest, self)._test_badinput()
+
+    def test_noinput(self):
+        element = self.etree.Element(self.etree.QName(self.namespace, self.name))
+        request = TestWebDAVRequest(element)
+        widget = self._WidgetFactory(self.field, request)
+        widget.namespace = self.namespace
+        self.assertEquals(widget.hasInput(), True)
+        value = widget.getInputValue()
+        self.assertEquals(value, None)
+        self.assertRaises(RequiredMissing, self.field.validate, value)
+
+
+class FloatWebDAVInputWidgetTest(WebDAVInputWidgetTest):
+
+    _FieldFactory  = schema.Float
+    _WidgetFactory = widgets.FloatDAVInputWidget
+
+    field_content = 10.4
+    rendered_content = u"10.4"
+    bad_rendered_content = u"X10.4"
+
+    def test_badinput(self):
+        super(FloatWebDAVInputWidgetTest, self)._test_badinput()
+
+    def test_noinput(self):
+        element = self.etree.Element(
+            self.etree.QName(self.namespace, self.name))
+        request = TestWebDAVRequest(element)
+        widget = self._WidgetFactory(self.field, request)
+        widget.namespace = self.namespace
+        self.assertEquals(widget.hasInput(), True)
+        value = widget.getInputValue()
+        self.assertEquals(value, None)
+        self.assertRaises(RequiredMissing, self.field.validate, value)
+
+
+class DatetimeWebDAVInputWidgetTest(WebDAVInputWidgetTest):
+
+    _FieldFactory  = schema.Datetime
+    _WidgetFactory = widgets.DatetimeDAVInputWidget
+
+    rendered_content = u'Wed, 24 May 2006 00:00:58 +0100'
+    field_content = datetime.datetime(2006, 5, 24, 0, 0, 58,
+                                      tzinfo = tzinfo(60))
+
+    def test_badinput(self):
+        super(DatetimeWebDAVInputWidgetTest, self)._test_badinput(
+            u'NODAY, 24 May 2006 00:00:58 +0100')
+
+
+class DateWebDAVInputWidgetTest(DatetimeWebDAVInputWidgetTest):
+
+    _FieldFactory  = schema.Date
+    _WidgetFactory = widgets.DateDAVInputWidget
+
+    field_content = datetime.date(2006, 5, 24)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(WebDAVBaseInputWidgetTest),
+        unittest.makeSuite(TextWebDAVInputWidgetTest),
+        unittest.makeSuite(IntWebDAVInputWidgetTest),
+        unittest.makeSuite(FloatWebDAVInputWidgetTest),
+        unittest.makeSuite(DatetimeWebDAVInputWidgetTest),
+        unittest.makeSuite(DateWebDAVInputWidgetTest),
+        ))


Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_inputwidgets.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/tests/test_locking.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_locking.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_locking.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,688 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 WebDAV propfind method.
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+import UserDict
+import datetime
+import random
+import time
+
+from zope import interface
+from zope import schema
+import zope.schema.interfaces
+from zope import component
+from zope.security.proxy import removeSecurityProxy
+from zope.traversing.api import getPath, traverse, getRoot
+from zope.traversing.interfaces import IContainmentRoot
+from zope.location.traversing import LocationPhysicallyLocatable
+from zope.app.container.interfaces import IReadContainer
+from zope.webdav.locking import DEFAULTTIMEOUT, MAXTIMEOUT
+from zope.webdav.locking import UNLOCKMethod, LOCKMethod
+from zope.webdav.testing import etreeSetup, etreeTearDown, assertXMLEqual
+import zope.webdav.publisher
+import zope.webdav.interfaces
+
+_randGen = random.Random(time.time())
+
+class TestWebDAVRequest(zope.webdav.publisher.WebDAVRequest):
+
+    def __init__(self, lockinfo = {}, body = "", environ = {}):
+        env = environ.copy()
+
+        if body:
+            env.setdefault("CONTENT_TYPE", "text/xml")
+        env.setdefault("CONTENT_LENGTH", len(body))
+
+        super(TestWebDAVRequest, self).__init__(StringIO(body), env)
+
+        self.processInputs()
+
+
+class LOCKINGHeaders(unittest.TestCase):
+
+    def test_depth_empty(self):
+        lock = LOCKMethod(None, TestWebDAVRequest())
+        self.assertEqual(lock.getDepth(), "infinity")
+
+    def test_depth_zero(self):
+        lock = LOCKMethod(None, TestWebDAVRequest(environ = {"DEPTH": "0"}))
+        self.assertEqual(lock.getDepth(), "0")
+
+    def test_timeout_default(self):
+        lock = LOCKMethod(None, TestWebDAVRequest(environ = {}))
+        self.assertEqual(lock.getTimeout(),
+                         datetime.timedelta(seconds = DEFAULTTIMEOUT))
+
+    def test_timeout_infinity(self):
+        request = TestWebDAVRequest(environ = {"TIMEOUT": "infinity"})
+        lock = LOCKMethod(None, request)
+        self.assertEqual(lock.getTimeout(),
+                         datetime.timedelta(seconds = DEFAULTTIMEOUT))
+
+    def test_timeout_second_500(self):
+        request = TestWebDAVRequest(environ = {"TIMEOUT": "Second-500"})
+        lock = LOCKMethod(None, request)
+        self.assertEqual(lock.getTimeout(),
+                         datetime.timedelta(seconds = 500))
+
+    def test_invalid_second(self):
+        request = TestWebDAVRequest(environ = {"TIMEOUT": "XXX-500"})
+        lock = LOCKMethod(None, request)
+        self.assertRaises(zope.webdav.interfaces.BadRequest, lock.getTimeout)
+
+    def test_invalid_second_value(self):
+        request = TestWebDAVRequest(environ = {"TIMEOUT": "Second-500x"})
+        lock = LOCKMethod(None, request)
+        self.assertRaises(zope.webdav.interfaces.BadRequest, lock.getTimeout)
+
+    def test_big_second(self):
+        request = TestWebDAVRequest(
+            environ = {"TIMEOUT": "Second-%d" %(MAXTIMEOUT + 100)})
+        lock = LOCKMethod(None, request)
+        self.assertEqual(lock.getTimeout(),
+                         datetime.timedelta(seconds = DEFAULTTIMEOUT))
+
+
+class DAVLockmanager(object):
+    interface.implements(zope.webdav.interfaces.IDAVLockmanager)
+
+    def __init__(self, context):
+        self.context = context
+
+    def generateLocktoken(self):
+        return "opaquelocktoken:%s-%s-00105A989226:%.03f" % \
+               (_randGen.random(), _randGen.random(), time.time())
+
+    def lock(self, scope, type, owner, duration, depth,
+             context = None):
+        if context is None:
+            if self.islockedObject(self.context):
+                raise zope.webdav.interfaces.AlreadyLocked(self.context)
+
+            ob = removeSecurityProxy(self.context)
+            setattr(ob, "_is_locked", {"scope": scope,
+                                       "type": type,
+                                       "owner": owner,
+                                       "duration": duration,
+                                       "depth": depth,
+                                       "token": self.generateLocktoken(),
+                                       "indirectlylocked": [],
+                                       "lockroot": getPath(self.context)})
+        else:
+            if self.islockedObject(context):
+                raise zope.webdav.interfaces.AlreadyLocked(context)
+
+            ob = removeSecurityProxy(context)
+            setattr(ob, "_is_indirectly_locked",
+                    {"lockroot": getPath(self.context),
+                     "rootinfo": removeSecurityProxy(self.context)._is_locked,
+                     })
+            root = removeSecurityProxy(self.context)
+            root._is_locked["indirectlylocked"].append(getPath(ob))
+
+        if context is None:
+            context = self.context
+
+        if depth == "infinity" and IReadContainer.providedBy(context):
+            for subob in context.values():
+                self.lock(scope, type, owner, duration, depth, subob)
+
+    def getActivelock(self, request = None):
+        return getActiveLock(self.context, request)
+
+    def refreshlock(self, timeout):
+        if not self.islockedObject(self.context):
+            raise zope.webdav.interfaces.ConflictError(
+                self.context, u"The context is not locked")
+
+        ob = removeSecurityProxy(self.context)
+        root = getRoot(self.context)
+
+        if getattr(ob, "_is_indirectly_locked", None) is not None:
+            lockroot = traverse(root, ob._is_indirectly_locked["lockroot"])
+        else:
+            lockroot = ob
+
+        removeSecurityProxy(lockroot)._is_locked["duration"] = timeout
+
+    def unlock(self):
+        if not self.islockedObject(self.context):
+            raise Exception("object is not locked")
+
+        ob = removeSecurityProxy(self.context)
+        root = getRoot(self.context)
+
+        if getattr(ob, "_is_indirectly_locked", None) is not None:
+            lockroot = traverse(root, ob._is_indirectly_locked["lockroot"])
+        else:
+            lockroot = ob
+
+        lockroot = removeSecurityProxy(lockroot)
+        for path in lockroot._is_locked["indirectlylocked"]:
+            ob = removeSecurityProxy(traverse(root, path))
+            delattr(ob, "_is_indirectly_locked")
+        delattr(lockroot, "_is_locked")
+
+    def islocked(self):
+        return self.islockedObject(self.context)
+
+    def islockedObject(self, context):
+        ob = removeSecurityProxy(context)
+        return getattr(ob, "_is_locked", None) is not None or \
+               getattr(ob, "_is_indirectly_locked", None) is not None
+
+
+ at interface.implementer(zope.webdav.coreproperties.IActiveLock)
+def getActiveLock(context, request):
+    ob = removeSecurityProxy(context)
+
+    data = getattr(ob, "_is_indirectly_locked", None)
+    if data is not None:
+        root = traverse(getRoot(context), data["lockroot"])
+        root = removeSecurityProxy(root)
+        return ActiveLock(root._is_locked)
+
+    data = getattr(ob, "_is_locked", None)
+    if data is not None:
+        return ActiveLock(data)
+
+    return None
+
+
+class ActiveLock(object):
+    interface.implements(zope.webdav.coreproperties.IActiveLock)
+
+    def __init__(self, data):
+        self.data = data
+
+    @property
+    def lockscope(self):
+        return [u"exclusive"]
+
+    @property
+    def locktype(self):
+        return [u"write"]
+
+    @property
+    def depth(self):
+        return self.data["depth"]
+
+    @property
+    def owner(self):
+        return self.data["owner"]
+
+    @property
+    def timeout(self):
+        seconds = self.data["duration"]
+        if not isinstance(seconds, int):
+            seconds = seconds.seconds
+        return u"Second-%d" % seconds
+
+    @property
+    def locktoken(self):
+        return [self.data["token"]]
+
+    @property
+    def lockroot(self):
+        return "http://localhost%s" % self.data["lockroot"]
+
+
+class Lockdiscovery(object):
+    interface.implements(zope.webdav.coreproperties.IDAVLockdiscovery)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    @property
+    def lockdiscovery(self):
+        activelock = getActiveLock(self.context, self.request)
+        if activelock is not None:
+            return [activelock]
+        return None
+
+
+class IResource(interface.Interface):
+
+    text = schema.TextLine(
+        title = u"Example Text Property")
+
+    intprop = schema.Int(
+        title = u"Example Int Property")
+
+
+class Resource(object):
+    interface.implements(IResource)
+
+    def __init__(self, text = u"", intprop = 0):
+        self.text = text
+        self.intprop = intprop
+
+
+class ICollectionResource(IReadContainer):
+
+    title = schema.TextLine(
+        title = u"Title",
+        description = u"Title of resource")
+
+
+class CollectionResource(UserDict.UserDict):
+    interface.implements(ICollectionResource)
+
+    title = None
+
+    def __setitem__(self, key, val):
+        val.__parent__ = self
+        val.__name__ = key
+
+        self.data[key] = val
+
+class RootCollectionResource(CollectionResource):
+    interface.implements(IContainmentRoot)
+
+
+class LOCKTestCase(unittest.TestCase):
+
+    def setUp(self):
+        etreeSetup()
+
+        self.root = RootCollectionResource()
+
+        gsm = component.getGlobalSiteManager()
+        gsm.registerAdapter(DAVLockmanager, (IResource,))
+        gsm.registerAdapter(DAVLockmanager, (ICollectionResource,))
+        gsm.registerAdapter(LocationPhysicallyLocatable, (IResource,))
+        gsm.registerAdapter(LocationPhysicallyLocatable, (ICollectionResource,))
+
+        gsm.registerAdapter(Lockdiscovery,
+                            (IResource, zope.webdav.interfaces.IWebDAVRequest))
+        gsm.registerUtility(zope.webdav.coreproperties.lockdiscovery,
+                            name = "{DAV:}lockdiscovery")
+        gsm.registerAdapter(getActiveLock,
+                            (IResource, zope.webdav.interfaces.IWebDAVRequest))
+
+        gsm.registerAdapter(zope.webdav.widgets.TextDAVWidget,
+                            (zope.schema.interfaces.IText,
+                             zope.webdav.interfaces.IWebDAVRequest))
+        gsm.registerAdapter(zope.webdav.widgets.TextDAVWidget,
+                            (zope.schema.interfaces.IURI,
+                             zope.webdav.interfaces.IWebDAVRequest))
+        gsm.registerAdapter(zope.webdav.properties.OpaqueWidget,
+                            (zope.webdav.properties.DeadField,
+                             zope.webdav.interfaces.IWebDAVRequest))
+        gsm.registerAdapter(zope.webdav.widgets.ListDAVWidget,
+                            (zope.schema.interfaces.IList,
+                             zope.webdav.interfaces.IWebDAVRequest))
+        gsm.registerAdapter(zope.webdav.widgets.ObjectDAVWidget,
+                            (zope.schema.interfaces.IObject,
+                             zope.webdav.interfaces.IWebDAVRequest))
+
+    def tearDown(self):
+        etreeTearDown()
+
+        gsm = component.getGlobalSiteManager()
+        gsm.unregisterAdapter(DAVLockmanager, (IResource,))
+        gsm.unregisterAdapter(DAVLockmanager, (ICollectionResource,))
+        gsm.unregisterAdapter(LocationPhysicallyLocatable, (IResource,))
+        gsm.unregisterAdapter(
+            LocationPhysicallyLocatable, (ICollectionResource,))
+
+        del self.root
+
+    def test_handleLock_notlockinfo(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:notlockinfo xmlns:D="DAV:">
+  Not a lockinfo.
+</D:notlockinfo>"""
+        request = TestWebDAVRequest(body = body)
+
+        lock = LOCKMethod(None, request)
+        self.assertRaises(
+            zope.webdav.interfaces.UnprocessableError, lock.handleLock)
+
+    def test_handleLock_invalidDepth(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+  Not a lockinfo.
+</D:lockinfo>"""
+        request = TestWebDAVRequest(body = body, environ = {"DEPTH": "1"})
+
+        lock = LOCKMethod(None, request)
+        self.assertRaises(
+            zope.webdav.interfaces.BadRequest, lock.handleLock)
+
+    def test_handleLock_nolockscope(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+  Not a lockinfo.
+</D:lockinfo>"""
+        request = TestWebDAVRequest(body = body)
+
+        lock = LOCKMethod(None, request)
+        self.assertRaises(
+            zope.webdav.interfaces.UnprocessableError, lock.handleLock)
+
+    def test_handleLock_nolocktype(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+  <D:lockscope><D:exculsive/></D:lockscope>
+</D:lockinfo>"""
+        request = TestWebDAVRequest(body = body)
+
+        lock = LOCKMethod(None, request)
+        self.assertRaises(
+            zope.webdav.interfaces.UnprocessableError, lock.handleLock)
+
+    def test_handleLock(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+        request = TestWebDAVRequest(body = body)
+        resource = self.root["resource"] = Resource()
+
+        lock = LOCKMethod(resource, request)
+        errors = lock.handleLock()
+
+        lockmanager = DAVLockmanager(resource)
+        self.assertEqual(lockmanager.islocked(), True)
+
+    def test_handleLock_alreadLocked(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+        request = TestWebDAVRequest(body = body)
+        resource = self.root["resource"] = Resource()
+
+        lock = LOCKMethod(resource, request)
+        errors = lock.handleLock()
+        self.assertEqual(errors, [])
+
+        lockmanager = DAVLockmanager(resource)
+        self.assertEqual(lockmanager.islocked(), True)
+
+        lock = LOCKMethod(resource, request)
+        errors = lock.handleLock()
+        self.assertEqual(len(errors), 1)
+        self.assert_(zope.webdav.interfaces.IAlreadyLocked.providedBy(errors[0]))
+
+    def test_handleLockRefresh_notlocked(self):
+        request = TestWebDAVRequest()
+        resource = self.root["resource"] = Resource()
+
+        lock = LOCKMethod(resource, request)
+
+        self.assertRaises(zope.webdav.interfaces.PreconditionFailed,
+                          lock.handleLockRefresh)
+
+    def test_handleLockRefresh_noifheader(self):
+        request = TestWebDAVRequest()
+        resource = self.root["resource"] = Resource()
+
+        lock = LOCKMethod(resource, request)
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write", u"Michael",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+
+        self.assertRaises(zope.webdav.interfaces.PreconditionFailed,
+                          lock.handleLockRefresh)
+
+    def test_handleLockRefresh_ifbadheader(self):
+        request = TestWebDAVRequest(environ = {"IF": "<xxx>"})
+        resource = self.root["resource"] = Resource()
+
+        lock = LOCKMethod(resource, request)
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write", u"Michael",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+
+        self.assertRaises(zope.webdav.interfaces.PreconditionFailed,
+                          lock.handleLockRefresh)
+
+    def test_handleLockRefresh_ifbadheader2(self):
+        request = TestWebDAVRequest(environ = {"IF": "xxx"})
+        resource = self.root["resource"] = Resource()
+
+        lock = LOCKMethod(resource, request)
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write", u"Michael",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+
+        self.assertRaises(zope.webdav.interfaces.PreconditionFailed,
+                          lock.handleLockRefresh)
+
+    def test_handleLockRefresh_defaulttimeout(self):
+        request = TestWebDAVRequest(environ = {"IF": "xxx"})
+        resource = self.root["resource"] = Resource()
+
+        lock = LOCKMethod(resource, request)
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write", u"Michael",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+
+        self.assertEqual(lockmanager.getActivelock().timeout,
+                         u"Second-100")
+
+        locktoken = lockmanager.getActivelock().locktoken[0]
+        request = TestWebDAVRequest(environ = {"IF": "<%s>" % locktoken})
+        lock = LOCKMethod(resource, request)
+
+        lock.handleLockRefresh()
+
+        self.assertEqual(lockmanager.getActivelock().timeout,
+                         u"Second-720")
+
+    def test_handleLockRefresh_timeout(self):
+        request = TestWebDAVRequest(environ = {"IF": "xxx"})
+        resource = self.root["resource"] = Resource()
+
+        lock = LOCKMethod(resource, request)
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write", u"Michael",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+
+        self.assertEqual(lockmanager.getActivelock().timeout,
+                         u"Second-100")
+
+        locktoken = lockmanager.getActivelock().locktoken[0]
+        request = TestWebDAVRequest(environ = {"IF": "<%s>" % locktoken,
+                                               "TIMEOUT": "Second-500"})
+        lock = LOCKMethod(resource, request)
+
+        lock.handleLockRefresh()
+
+        self.assertEqual(lockmanager.getActivelock().timeout,
+                         u"Second-500")
+
+    def test_lock_alreadLocked(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+        request = TestWebDAVRequest(body = body)
+        resource = self.root["resource"] = Resource()
+
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write", u"Michael",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+
+        lock = LOCKMethod(resource, request)
+        self.assertRaises(zope.webdav.interfaces.WebDAVErrors, lock.LOCK)
+
+    def test_lock(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+  <D:lockscope><D:exclusive/></D:lockscope>
+  <D:locktype><D:write/></D:locktype>
+  <D:owner>
+    <D:href>http://example.org/~ejw/contact.html</D:href>
+  </D:owner>
+</D:lockinfo>"""
+        request = TestWebDAVRequest(body = body)
+        resource = self.root["resource"] = Resource()
+
+        lock = LOCKMethod(resource, request)
+        result = lock.LOCK()
+
+        locktoken = request.response.getHeader("lock-token")
+        lockmanager = DAVLockmanager(resource)
+        currentlocktoken = lockmanager.getActivelock().locktoken[0]
+        self.assert_(locktoken[0], "<")
+        self.assert_(locktoken[-1], ">")
+        self.assertEqual(currentlocktoken, locktoken[1:-1])
+
+        assertXMLEqual(result, """<ns0:prop xmlns:ns0="DAV:">
+<ns0:lockdiscovery xmlns:ns0="DAV:">
+  <ns0:activelock xmlns:ns0="DAV:">
+    <ns0:lockscope xmlns:ns0="DAV:"><ns0:exclusive xmlns:ns0="DAV:"/></ns0:lockscope>
+    <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+    <ns0:depth xmlns:ns0="DAV:">infinity</ns0:depth>
+    <ns0:owner xmlns:D="DAV:">
+      <ns0:href>http://example.org/~ejw/contact.html</ns0:href>
+    </ns0:owner>
+    <ns0:timeout xmlns:ns0="DAV:">Second-720</ns0:timeout>
+    <ns0:locktoken xmlns:ns0="DAV:">
+      <ns0:href xmlns:ns0="DAV:">%s</ns0:href>
+    </ns0:locktoken>
+    <ns0:lockroot xmlns:ns0="DAV:">http://localhost/resource</ns0:lockroot>
+  </ns0:activelock>
+</ns0:lockdiscovery></ns0:prop>""" % locktoken[1:-1])
+
+    def test_refreshlock_alreadLocked(self):
+        request = TestWebDAVRequest()
+        resource = self.root["resource"] = Resource()
+
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write", u"Michael",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+
+        lock = LOCKMethod(resource, request)
+        self.assertRaises(zope.webdav.interfaces.PreconditionFailed, lock.LOCK)
+
+    def test_refreshlock(self):
+        resource = self.root["resource"] = Resource()
+
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write",
+                         u"""<D:owner xmlns:D="DAV:">Michael</D:owner>""",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+        locktoken = lockmanager.getActivelock().locktoken[0]
+
+        request = TestWebDAVRequest(environ = {"IF": "<%s>" % locktoken})
+        lock = LOCKMethod(resource, request)
+        result = lock.LOCK()
+
+        assertXMLEqual(result, """<ns0:prop xmlns:ns0="DAV:">
+<ns0:lockdiscovery xmlns:ns0="DAV:">
+  <ns0:activelock xmlns:ns0="DAV:">
+    <ns0:lockscope xmlns:ns0="DAV:"><ns0:exclusive xmlns:ns0="DAV:"/></ns0:lockscope>
+    <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+    <ns0:depth xmlns:ns0="DAV:">0</ns0:depth>
+    <ns0:owner xmlns:D="DAV:">Michael</ns0:owner>
+    <ns0:timeout xmlns:ns0="DAV:">Second-720</ns0:timeout>
+    <ns0:locktoken xmlns:ns0="DAV:">
+      <ns0:href xmlns:ns0="DAV:">%s</ns0:href>
+    </ns0:locktoken>
+    <ns0:lockroot xmlns:ns0="DAV:">http://localhost/resource</ns0:lockroot>
+  </ns0:activelock>
+</ns0:lockdiscovery></ns0:prop>""" % locktoken)
+
+    def test_unlock_nolocktoken(self):
+        resource = self.root["resource"] = Resource()
+
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write",
+                         u"""<D:owner xmlns:D="DAV:">Michael</D:owner>""",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+
+        unlock = UNLOCKMethod(resource, TestWebDAVRequest())
+        self.assertRaises(zope.webdav.interfaces.BadRequest, unlock.UNLOCK)
+
+    def test_unlock_badlocktoken(self):
+        resource = self.root["resource"] = Resource()
+
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write",
+                         u"""<D:owner xmlns:D="DAV:">Michael</D:owner>""",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+
+        request = TestWebDAVRequest(environ = {"LOCK_TOKEN": "XXX"})
+        unlock = UNLOCKMethod(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ConflictError, unlock.UNLOCK)
+
+    def test_unlock_badlocktoken2(self):
+        resource = self.root["resource"] = Resource()
+
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write",
+                         u"""<D:owner xmlns:D="DAV:">Michael</D:owner>""",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+
+        request = TestWebDAVRequest(environ = {"LOCK_TOKEN": "<XXX>"})
+        unlock = UNLOCKMethod(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ConflictError, unlock.UNLOCK)
+
+    def test_unlock(self):
+        resource = self.root["resource"] = Resource()
+
+        lockmanager = DAVLockmanager(resource)
+        lockmanager.lock(u"exclusive", u"write",
+                         u"""<D:owner xmlns:D="DAV:">Michael</D:owner>""",
+                         duration = datetime.timedelta(seconds = 100),
+                         depth = "0")
+        locktoken = lockmanager.getActivelock().locktoken[0]
+
+        request = TestWebDAVRequest(
+            environ = {"LOCK_TOKEN": "<%s>" % locktoken})
+        unlock = UNLOCKMethod(resource, request)
+        result = unlock.UNLOCK()
+
+        self.assertEqual(result, "")
+        self.assertEqual(request.response.getStatus(), 204)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(LOCKINGHeaders),
+        unittest.makeSuite(LOCKTestCase),
+        ))


Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_locking.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,570 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 WebDAV propfind method.
+
+It is easier to do this has a unit test has we have complete control over
+what properties are defined or not.
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+import UserDict
+
+from zope import interface
+from zope import component
+from zope import schema
+import zope.schema.interfaces
+from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.app.container.interfaces import IReadContainer
+
+import zope.webdav.properties
+import zope.webdav.publisher
+import zope.webdav.widgets
+import zope.webdav.exceptions
+import zope.webdav.coreproperties
+from zope.webdav.propfind import PROPFIND
+from zope.webdav.testing import etreeSetup, etreeTearDown, assertXMLEqual
+
+class TestRequest(zope.webdav.publisher.WebDAVRequest):
+
+    def __init__(self, properties = None, environ = {}):
+        if properties is not None:
+            body = """<?xml version="1.0" encoding="utf-8" ?>
+<propfind xmlns:D="DAV:" xmlns="DAV:">
+  %s
+</propfind>
+""" % properties
+        else:
+            body = ""
+
+        env = environ.copy()
+        env.setdefault("REQUEST_METHOD", "PROPFIND")
+        env.setdefault("CONTENT_TYPE", "text/xml")
+        env.setdefault("CONTENT_LENGTH", len(body))
+
+        super(TestRequest, self).__init__(StringIO(body), env)
+
+        # call processInputs now since we are in a unit test.
+        self.processInputs()
+
+
+class PROPFINDBodyParsed(PROPFIND):
+
+    propertiesFactory = extraArg = depth = None
+
+    def handlePropfindResource(self, ob, req,
+                               depth, propertiesFactory, extraArg):
+        self.propertiesFactory = propertiesFactory
+        self.extraArg = extraArg
+        self.depth = depth
+
+        return []
+
+
+class PROPFINDBodyTestCase(unittest.TestCase):
+    # Using PROPFINDBodyParsed test that the correct method and arguements
+    # get set up.
+
+    def setUp(self):
+        etreeSetup()
+
+    def tearDown(self):
+        etreeTearDown()
+
+    def checkPropfind(self, properties = None, environ = {}):
+        request = TestRequest(properties = properties, environ = environ)
+        propfind = PROPFINDBodyParsed(None, request)
+        propfind.PROPFIND()
+
+        return propfind
+
+    def test_notxml(self):
+        self.assertRaises(zope.webdav.interfaces.BadRequest, self.checkPropfind,
+            "<propname />", {"CONTENT_TYPE": "text/plain"})
+
+    def test_bad_depthheader(self):
+        self.assertRaises(zope.webdav.interfaces.BadRequest, self.checkPropfind,
+            "<propname />", {"DEPTH": "2"})
+
+    def test_depth_header(self):
+        propf = self.checkPropfind("<propname />", {"DEPTH": "0"})
+        self.assertEqual(propf.depth, "0")
+        propf = self.checkPropfind("<propname />", {"DEPTH": "1"})
+        self.assertEqual(propf.depth, "1")
+        propf = self.checkPropfind("<propname />", {"DEPTH": "infinity"})
+        self.assertEqual(propf.depth, "infinity")
+
+    def test_xml_propname(self):
+        propf = self.checkPropfind("<propname />")
+        self.assertEqual(propf.propertiesFactory, propf.renderPropnames)
+        self.assertEqual(propf.extraArg, None)
+
+    def test_xml_allprop(self):
+        propf = self.checkPropfind("<allprop />")
+        self.assertEqual(propf.propertiesFactory, propf.renderAllProperties)
+        self.assertEqual(propf.extraArg, None)
+
+    def test_xml_allprop_with_include(self):
+        includes = """<include xmlns="DAV:"><davproperty /></include>"""
+        propf = self.checkPropfind("<allprop/>%s" % includes)
+        self.assertEqual(propf.propertiesFactory, propf.renderAllProperties)
+        assertXMLEqual(propf.extraArg, includes)
+
+    def test_xml_emptyprop(self):
+        propf = self.checkPropfind("<prop />")
+        self.assertEqual(propf.propertiesFactory, propf.renderAllProperties)
+        self.assertEqual(propf.extraArg, None)
+
+    def test_xml_someprops(self):
+        props = """<prop xmlns="DAV:"><someprop/></prop>"""
+        propf = self.checkPropfind(props)
+        self.assertEqual(propf.propertiesFactory,
+                         propf.renderSelectedProperties)
+        assertXMLEqual(propf.extraArg, props)
+
+    def test_emptybody(self):
+        propf = self.checkPropfind()
+        self.assertEqual(propf.propertiesFactory, propf.renderAllProperties)
+        self.assertEqual(propf.extraArg, None)
+
+    def test_xml_nopropfind_element(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<nopropfind xmlns:D="DAV:" xmlns="DAV:">
+  invalid xml
+</nopropfind>
+        """
+        env = {"CONTENT_TYPE": "text/xml",
+               "CONTENT_LENGTH": len(body)}
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(body), env)
+        request.processInputs()
+
+        propf = PROPFINDBodyParsed(None, request)
+        self.assertRaises(zope.webdav.interfaces.UnprocessableError,
+                          propf.PROPFIND)
+
+    def test_xml_propfind_bad_content(self):
+        self.assertRaises(zope.webdav.interfaces.UnprocessableError,
+                          self.checkPropfind, properties = "<noproperties />")
+
+
+class IExamplePropertyStorage(interface.Interface):
+
+    exampleintprop = schema.Int(
+        title = u"Example Integer Property")
+
+    exampletextprop = schema.Text(
+        title = u"Example Text Property")
+
+class IExtraPropertyStorage(interface.Interface):
+
+    extratextprop = schema.Text(
+        title = u"Property with no storage")
+
+exampleIntProperty = zope.webdav.properties.DAVProperty(
+    "{DAVtest:}exampleintprop", IExamplePropertyStorage)
+exampleTextProperty = zope.webdav.properties.DAVProperty(
+    "{DAVtest:}exampletextprop", IExamplePropertyStorage)
+extraTextProperty = zope.webdav.properties.DAVProperty(
+    "{DAVtest:}extratextprop", IExtraPropertyStorage)
+
+class ExamplePropertyStorage(object):
+    interface.implements(IExamplePropertyStorage)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def _getproperty(name, default = None):
+        def get(self):
+            return getattr(self.context, name, default)
+        def set(self, value):
+            setattr(self.context, name, value)
+        return property(get, set)
+
+    exampleintprop = _getproperty("intprop", default = 0)
+
+    exampletextprop = _getproperty("text", default = u"")
+
+
+class IResource(interface.Interface):
+
+    text = schema.TextLine(
+        title = u"Example Text Property")
+
+    intprop = schema.Int(
+        title = u"Example Int Property")
+
+
+class Resource(object):
+    interface.implements(IResource)
+
+    def __init__(self, text = u"", intprop = 0):
+        self.text = text
+        self.intprop = intprop
+
+
+class ICollection(IReadContainer):
+    pass
+
+
+class Collection(UserDict.UserDict):
+    interface.implements(ICollection)
+
+    def __setitem__(self, key, value):
+        self.data[key] = value
+        value.__parent__ = self
+        value.__name__ = key
+
+
+class DummyResourceURL(object):
+    interface.implements(IAbsoluteURL)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    def __str__(self):
+        if getattr(self.context, "__parent__", None) is not None:
+            path = DummyResourceURL(self.context.__parent__, None)()
+        else:
+            path = ""
+
+        if getattr(self.context, "__name__", None) is not None:
+            path += "/" + self.context.__name__
+        elif IResource.providedBy(self.context):
+            path += "/resource"
+        elif ICollection.providedBy(self.context):
+            path += "/collection"
+        else:
+            raise ValueError("unknown context type")
+
+        return path
+
+    __call__ = __str__
+
+
+def propfindSetUp():
+    etreeSetup()
+
+    gsm = component.getGlobalSiteManager()
+
+    gsm.registerUtility(exampleIntProperty,
+                        name = "{DAVtest:}exampleintprop",
+                        provided = zope.webdav.interfaces.IDAVProperty)
+    gsm.registerUtility(exampleTextProperty,
+                        name = "{DAVtest:}exampletextprop",
+                        provided = zope.webdav.interfaces.IDAVProperty)
+    exampleTextProperty.restricted = False
+    gsm.registerUtility(extraTextProperty,
+                        name = "{DAVtest:}extratextprop",
+                        provided = zope.webdav.interfaces.IDAVProperty)
+    gsm.registerUtility(zope.webdav.coreproperties.resourcetype,
+                        name = "{DAV:}resourcetype")
+
+    gsm.registerAdapter(ExamplePropertyStorage,
+                        (IResource, zope.webdav.interfaces.IWebDAVRequest),
+                        provided = IExamplePropertyStorage)
+    gsm.registerAdapter(zope.webdav.coreproperties.ResourceTypeAdapter)
+
+    gsm.registerAdapter(DummyResourceURL,
+                        (IResource, zope.webdav.interfaces.IWebDAVRequest))
+    gsm.registerAdapter(DummyResourceURL,
+                        (ICollection, zope.webdav.interfaces.IWebDAVRequest))
+
+    gsm.registerAdapter(zope.webdav.widgets.TextDAVWidget,
+                        (zope.schema.interfaces.IText,
+                         zope.webdav.interfaces.IWebDAVRequest))
+    gsm.registerAdapter(zope.webdav.widgets.IntDAVWidget,
+                        (zope.schema.interfaces.IInt,
+                         zope.webdav.interfaces.IWebDAVRequest))
+    gsm.registerAdapter(zope.webdav.widgets.ListDAVWidget,
+                        (zope.schema.interfaces.IList,
+                         zope.webdav.interfaces.IWebDAVRequest))
+
+    gsm.registerAdapter(zope.webdav.exceptions.PropertyNotFoundError,
+                        (zope.webdav.interfaces.IPropertyNotFound,
+                         zope.webdav.interfaces.IWebDAVRequest))
+
+def propfindTearDown():
+    etreeTearDown()
+
+    gsm = component.getGlobalSiteManager()
+
+    gsm.unregisterUtility(exampleIntProperty,
+                          name = "{DAVtest:}exampleintprop",
+                          provided = zope.webdav.interfaces.IDAVProperty)
+    gsm.unregisterUtility(exampleTextProperty,
+                          name = "{DAVtest:}exampletextprop",
+                          provided = zope.webdav.interfaces.IDAVProperty)
+    gsm.unregisterUtility(extraTextProperty,
+                          name = "{DAVtest:}extratextprop",
+                          provided = zope.webdav.interfaces.IDAVProperty)
+    gsm.unregisterUtility(zope.webdav.coreproperties.resourcetype,
+                          name = "{DAV:}resourcetype")
+
+    gsm.unregisterAdapter(ExamplePropertyStorage,
+                          (IResource, zope.webdav.interfaces.IWebDAVRequest),
+                          provided = IExamplePropertyStorage)
+    gsm.unregisterAdapter(zope.webdav.coreproperties.ResourceTypeAdapter)
+
+    gsm.unregisterAdapter(DummyResourceURL,
+                          (IResource, zope.webdav.interfaces.IWebDAVRequest))
+    gsm.unregisterAdapter(DummyResourceURL,
+                          (ICollection, zope.webdav.interfaces.IWebDAVRequest))
+
+    gsm.unregisterAdapter(zope.webdav.widgets.TextDAVWidget,
+                          (zope.schema.interfaces.IText,
+                           zope.webdav.interfaces.IWebDAVRequest))
+    gsm.unregisterAdapter(zope.webdav.widgets.IntDAVWidget,
+                          (zope.schema.interfaces.IInt,
+                           zope.webdav.interfaces.IWebDAVRequest))
+    gsm.unregisterAdapter(zope.webdav.exceptions.PropertyNotFoundError,
+                          (zope.webdav.interfaces.IPropertyNotFound,
+                           zope.webdav.interfaces.IWebDAVRequest))
+    gsm.unregisterAdapter(zope.webdav.widgets.ListDAVWidget,
+                          (zope.schema.interfaces.IList,
+                           zope.webdav.interfaces.IWebDAVRequest))
+
+
+class PROPFINDTestRender(unittest.TestCase):
+    # Test all the methods that render a resource into a `response' XML
+    # element. We are going to need to register the DAV widgets for
+    # text and int properties.
+
+    def setUp(self):
+        propfindSetUp()
+
+    def tearDown(self):
+        propfindTearDown()
+
+    def test_renderPropnames(self):
+        resource = Resource("some text", 10)
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+
+        propf = PROPFIND(None, None)
+        response = propf.renderPropnames(resource, request, None)
+        assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+  <ns0:prop xmlns:ns0="DAV:">
+    <ns01:exampletextprop xmlns:ns0="DAVtest:"/>
+    <ns01:exampleintprop xmlns:ns0="DAVtest:"/>
+    <ns0:resourcetype />
+  </ns0:prop>
+  <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+    def test_renderSelected(self):
+        resource = Resource("some text", 10)
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        propf = PROPFIND(None, None)
+
+        etree = component.getUtility(zope.webdav.ietree.IEtree)
+        props = etree.fromstring("""<prop xmlns="DAV:" xmlns:D="DAVtest:">
+<D:exampletextprop />
+<D:exampleintprop />
+</prop>""")
+        response = propf.renderSelectedProperties(resource, request, props)
+
+        assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+  <ns0:prop xmlns:ns0="DAV:">
+    <ns01:exampletextprop xmlns:ns0="DAVtest:">some text</ns01:exampletextprop>
+    <ns01:exampleintprop xmlns:ns0="DAVtest:">10</ns01:exampleintprop>
+  </ns0:prop>
+  <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+    def test_renderSelected_notfound(self):
+        resource = Resource("some text", 10)
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        propf = PROPFIND(None, None)
+
+        etree = component.getUtility(zope.webdav.ietree.IEtree)
+        props = etree.fromstring("""<prop xmlns="DAV:" xmlns:D="DAVtest:">
+<D:exampletextprop />
+<D:extratextprop />
+</prop>""")
+        response = propf.renderSelectedProperties(resource, request, props)
+
+        assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+  <ns0:prop xmlns:ns0="DAV:">
+    <ns01:exampletextprop xmlns:ns0="DAVtest:">some text</ns01:exampletextprop>
+  </ns0:prop>
+  <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+  <ns0:prop xmlns:ns0="DAV:">
+    <ns01:extratextprop xmlns:ns0="DAVtest:" />
+  </ns0:prop>
+  <ns0:status xmlns:ns0="DAV:">HTTP/1.1 404 Not Found</ns0:status>
+</ns0:propstat>
+</ns0:response>""")
+
+    def test_renderAllProperties(self):
+        resource = Resource("some text", 10)
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        propf = PROPFIND(None, None)
+
+        response = propf.renderAllProperties(resource, request, None)
+
+        assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+  <ns0:prop xmlns:ns0="DAV:">
+    <ns01:exampletextprop xmlns:ns0="DAVtest:">some text</ns01:exampletextprop>
+    <ns01:exampleintprop xmlns:ns0="DAVtest:">10</ns01:exampleintprop>
+    <ns0:resourcetype />
+  </ns0:prop>
+  <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+    def test_renderAllProperties_withInclude(self):
+        resource = Resource("some text", 10)
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        propf = PROPFIND(None, None)
+
+        etree = component.getUtility(zope.webdav.ietree.IEtree)
+        include = etree.fromstring("""<include xmlns="DAV:" xmlns:D="DAVtest:">
+<D:exampletextprop />
+</include>""")
+        response = propf.renderAllProperties(resource, request, include)
+
+        assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+  <ns0:prop xmlns:ns0="DAV:">
+    <ns01:exampletextprop xmlns:ns0="DAVtest:">some text</ns01:exampletextprop>
+    <ns01:exampleintprop xmlns:ns0="DAVtest:">10</ns01:exampleintprop>
+    <ns0:resourcetype />
+  </ns0:prop>
+  <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+    def test_renderAllProperties_withRestrictedProp(self):
+        resource = Resource("some text", 10)
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        propf = PROPFIND(None, None)
+
+        exampleTextProperty.restricted = True
+        response = propf.renderAllProperties(resource, request, None)
+
+        assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+  <ns0:prop xmlns:ns0="DAV:">
+    <ns01:exampleintprop xmlns:ns0="DAVtest:">10</ns01:exampleintprop>
+    <ns0:resourcetype />
+  </ns0:prop>
+  <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+    def test_renderAllProperties_withRestrictedProp_include(self):
+        resource = Resource("some text", 10)
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        propf = PROPFIND(None, None)
+
+        exampleTextProperty.restricted = True
+        etree = component.getUtility(zope.webdav.ietree.IEtree)
+        include = etree.fromstring("""<include xmlns="DAV:" xmlns:D="DAVtest:">
+<D:exampletextprop />
+</include>""")
+        response = propf.renderAllProperties(resource, request, include)
+
+        assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+  <ns0:prop xmlns:ns0="DAV:">
+    <ns01:exampletextprop xmlns:ns0="DAVtest:">some text</ns01:exampletextprop>
+    <ns01:exampleintprop xmlns:ns0="DAVtest:">10</ns01:exampleintprop>
+    <ns0:resourcetype />
+  </ns0:prop>
+  <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+
+class PROPFINDRecuseTest(unittest.TestCase):
+
+    def setUp(self):
+        propfindSetUp()
+
+    def tearDown(self):
+        propfindTearDown()
+
+    def test_handlePropfindResource(self):
+        collection = Collection()
+        collection["r1"] = Resource("some text - r1", 2)
+        collection["c"] = Collection()
+        collection["c"]["r2"] = Resource("some text - r2", 4)
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        request.processInputs()
+        propf = PROPFIND(collection, request)
+
+        result = propf.PROPFIND()
+        etree = component.getUtility(zope.webdav.ietree.IEtree)
+        etree.fromstring(result)
+
+        assertXMLEqual(result, """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response xmlns:ns0="DAV:">
+  <ns0:href xmlns:ns0="DAV:">/collection/</ns0:href>
+  <ns0:propstat xmlns:ns0="DAV:">
+    <ns0:prop xmlns:ns0="DAV:">
+      <ns0:resourcetype xmlns:ns0="DAV:"><ns0:collection xmlns:ns0="DAV:"/></ns0:resourcetype>
+    </ns0:prop>
+    <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+  </ns0:propstat>
+</ns0:response>
+<ns0:response xmlns:ns0="DAV:">
+  <ns0:href xmlns:ns0="DAV:">/collection/c/</ns0:href>
+  <ns0:propstat xmlns:ns0="DAV:">
+    <ns0:prop xmlns:ns0="DAV:">
+      <ns0:resourcetype xmlns:ns0="DAV:"><ns0:collection xmlns:ns0="DAV:"/></ns0:resourcetype>
+    </ns0:prop>
+    <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+  </ns0:propstat>
+</ns0:response>
+<ns0:response xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+  <ns0:href xmlns:ns0="DAV:">/collection/c/r2</ns0:href>
+  <ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+    <ns0:prop xmlns:ns0="DAV:">
+      <ns01:exampletextprop xmlns:ns0="DAVtest:">some text - r2</ns01:exampletextprop>
+      <ns01:exampleintprop xmlns:ns0="DAVtest:">4</ns01:exampleintprop>
+      <ns0:resourcetype xmlns:ns0="DAV:"/>
+    </ns0:prop>
+    <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+  </ns0:propstat>
+</ns0:response>
+<ns0:response xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+  <ns0:href xmlns:ns0="DAV:">/collection/r1</ns0:href>
+  <ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+    <ns0:prop xmlns:ns0="DAV:">
+      <ns01:exampletextprop xmlns:ns0="DAVtest:">some text - r1</ns01:exampletextprop>
+      <ns01:exampleintprop xmlns:ns0="DAVtest:">2</ns01:exampleintprop>
+      <ns0:resourcetype xmlns:ns0="DAV:"/>
+    </ns0:prop>
+    <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+  </ns0:propstat>
+</ns0:response></ns0:multistatus>
+        """)
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(PROPFINDBodyTestCase),
+        unittest.makeSuite(PROPFINDTestRender),
+        unittest.makeSuite(PROPFINDRecuseTest),
+        ))


Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,591 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 WebDAV propfind method.
+
+It is easier to do this has a unit test has we have complete control over
+what properties are defined or not.
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+
+from zope import interface
+from zope import component
+from zope import schema
+import zope.schema.interfaces
+from zope.traversing.browser.interfaces import IAbsoluteURL
+
+import zope.webdav.proppatch
+import zope.webdav.publisher
+import zope.webdav.interfaces
+from zope.webdav.ietree import IEtree
+from zope.webdav.testing import etreeSetup, etreeTearDown, assertXMLEqual
+
+class TestRequest(zope.webdav.publisher.WebDAVRequest):
+
+    def __init__(self, set_properties = None, remove_properties = None,
+                 environ = {}):
+        set_body = ""
+        if set_properties is not None:
+            set_body = "<set><prop>%s</prop></set>" % set_properties
+
+        remove_body = ""
+        if remove_properties is not None:
+            remove_body = "<remove><prop>%s</prop></remove>" % remove_properties
+
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+  %s %s
+</D:propertyupdate>
+        """ %(set_body, remove_body)
+        body = body.encode("utf-8")
+
+        env = environ.copy()
+        env.setdefault("REQUEST_METHOD", "PROPPATCH")
+        env.setdefault("CONTENT_TYPE", "text/xml")
+        env.setdefault("CONTENT_LENGTH", len(body))
+
+        super(TestRequest, self).__init__(StringIO(body), env)
+
+        # call processInputs now since we are in a unit test.
+        self.processInputs()
+
+
+class IResource(interface.Interface):
+
+    text = schema.TextLine(
+        title = u"Example Text Property")
+
+    intprop = schema.Int(
+        title = u"Example Int Property")
+
+
+class Resource(object):
+    interface.implements(IResource)
+
+    def __init__(self, text = u"", intprop = 0):
+        self.text = text
+        self.intprop = intprop
+
+
+class DummyResourceURL(object):
+    interface.implements(IAbsoluteURL)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    def __str__(self):
+        if getattr(self.context, "__parent__", None) is not None:
+            path = DummyResourceURL(self.context.__parent__, None)()
+        else:
+            path = ""
+
+        if getattr(self.context, "__name__", None) is not None:
+            path += "/" + self.context.__name__
+        elif IResource.providedBy(self.context):
+            path += "/resource"
+##         elif ICollection.providedBy(self.context):
+##             path += "/collection"
+        else:
+            raise ValueError("unknown context type")
+
+        return path
+
+    __call__ = __str__
+
+
+class PROPPATCHHandler(zope.webdav.proppatch.PROPPATCH):
+
+    def __init__(self, context, request):
+        super(PROPPATCHHandler, self).__init__(context, request)
+
+        self.setprops = []
+        self.removeprops = []
+
+    def handleSet(self, prop):
+        self.setprops.append(prop.tag)
+
+    def handleRemove(self, prop):
+        self.removeprops.append(prop.tag)
+
+
+class PROPPATCHXmlParsing(unittest.TestCase):
+
+    def setUp(self):
+        etreeSetup()
+
+        gsm = component.getGlobalSiteManager()
+
+        gsm.registerAdapter(DummyResourceURL,
+                            (IResource, zope.webdav.interfaces.IWebDAVRequest))
+
+    def tearDown(self):
+        etreeTearDown()
+
+        gsm = component.getGlobalSiteManager()
+
+        gsm.unregisterAdapter(DummyResourceURL,
+                              (IResource,
+                               zope.webdav.interfaces.IWebDAVRequest))
+
+    def test_noxml(self):
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        propp = PROPPATCHHandler(Resource(), request)
+        self.assertRaises(zope.webdav.interfaces.BadRequest, propp.PROPPATCH)
+
+    def test_notxml(self):
+        request = zope.webdav.publisher.WebDAVRequest(
+            StringIO("content"), {"CONTENT_TYPE": "text/plain",
+                                  "CONTENT_LENGTH": 7})
+        propp = PROPPATCHHandler(Resource(), request)
+        request.processInputs()
+        self.assertRaises(zope.webdav.interfaces.BadRequest, propp.PROPPATCH)
+
+    def test_notproppatch(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:notpropertyupdate xmlns:D="DAV:" xmlns="DAV:">
+  Not a propertyupdate element.
+</D:notpropertyupdate>
+        """
+
+        request = zope.webdav.publisher.WebDAVRequest(
+            StringIO(body), {"CONTENT_TYPE": "text/xml",
+                             "CONTENT_LENGTH": len(body)})
+        request.processInputs()
+
+        propp = PROPPATCHHandler(Resource(), request)
+        self.assertRaises(zope.webdav.interfaces.UnprocessableError,
+                          propp.PROPPATCH)
+
+    def test_not_set_element(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+  <notset><prop><displayname>Display name</displayname></prop></notset>
+</propertyupdate>
+        """
+
+        request = zope.webdav.publisher.WebDAVRequest(
+            StringIO(body), {"CONTENT_TYPE": "text/xml",
+                             "CONTENT_LENGTH": len(body)})
+        request.processInputs()
+
+        propp = PROPPATCHHandler(Resource(), request)
+        propp.PROPPATCH()
+
+        self.assertEqual(propp.setprops, [])
+        self.assertEqual(propp.removeprops, [])
+
+    def test_not_prop_element(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+  <set><notprop><displayname>Display name</displayname></notprop></set>
+</propertyupdate>
+        """
+
+        request = zope.webdav.publisher.WebDAVRequest(
+            StringIO(body), {"CONTENT_TYPE": "text/xml",
+                             "CONTENT_LENGTH": len(body)})
+        request.processInputs()
+
+        propp = PROPPATCHHandler(Resource(), request)
+        propp.PROPPATCH()
+
+        self.assertEqual(propp.setprops, [])
+        self.assertEqual(propp.removeprops, [])
+
+    def test_not_remove_element(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+  <notremove><prop><displayname>Display name</displayname></prop></notremove>
+</propertyupdate>
+        """
+
+        request = zope.webdav.publisher.WebDAVRequest(
+            StringIO(body), {"CONTENT_TYPE": "text/xml",
+                             "CONTENT_LENGTH": len(body)})
+        request.processInputs()
+
+        propp = PROPPATCHHandler(Resource(), request)
+        propp.PROPPATCH()
+
+        self.assertEqual(propp.setprops, [])
+        self.assertEqual(propp.removeprops, [])
+
+    def test_set_none_prop(self):
+        request = TestRequest()
+        propp = PROPPATCHHandler(Resource(), request)
+        propp.PROPPATCH()
+
+        self.assertEqual(propp.setprops, [])
+        self.assertEqual(propp.removeprops, [])
+
+    def test_set_one_prop(self):
+        request = TestRequest(
+            set_properties = "<displayname>Display name</displayname>")
+        propp = PROPPATCHHandler(Resource(), request)
+        propp.PROPPATCH()
+
+        self.assertEqual(propp.setprops, ["{DAV:}displayname"])
+        self.assertEqual(propp.removeprops, [])
+
+    def test_remove_one_prop(self):
+        request = TestRequest(
+            remove_properties = "<displayname>Display name</displayname>")
+        propp = PROPPATCHHandler(Resource(), request)
+        propp.PROPPATCH()
+
+        self.assertEqual(propp.setprops, [])
+        self.assertEqual(propp.removeprops, ["{DAV:}displayname"])
+
+    def test_multiset(self):
+        request = TestRequest(
+            set_properties = "<displayname>Display name</displayname><getcontenttype>text/plain</getcontenttype>")
+        propp = PROPPATCHHandler(Resource(), request)
+        propp.PROPPATCH()
+
+        self.assertEqual(propp.setprops, ["{DAV:}displayname",
+                                          "{DAV:}getcontenttype"])
+        self.assertEqual(propp.removeprops, [])
+
+    def test_multiremove(self):
+        request = TestRequest(
+            remove_properties = "<displayname>Display name</displayname><getcontenttype>text/plain</getcontenttype>")
+        propp = PROPPATCHHandler(Resource(), request)
+        propp.PROPPATCH()
+
+        self.assertEqual(propp.setprops, [])
+        self.assertEqual(propp.removeprops, ["{DAV:}displayname",
+                                             "{DAV:}getcontenttype"])
+
+    def test_set_remove_prop(self):
+        request = TestRequest(
+            remove_properties = "<displayname>Display name</displayname>",
+            set_properties = "<getcontenttype>text/plain</getcontenttype>")
+        propp = PROPPATCHHandler(Resource(), request)
+        propp.PROPPATCH()
+
+        self.assertEqual(propp.setprops, ["{DAV:}getcontenttype"])
+        self.assertEqual(propp.removeprops, ["{DAV:}displayname"])
+
+    def test_error_set_prop(self):
+        class PROPPATCHHandlerError(PROPPATCHHandler):
+            def handleSet(self, prop):
+                raise zope.webdav.interfaces.PropertyNotFound(
+                    self.context, "getcontenttype", u"property is missing")
+
+        request = TestRequest(
+            set_properties = "<getcontenttype>text/plain</getcontenttype>")
+        propp = PROPPATCHHandlerError(Resource(), request)
+        self.assertRaises(zope.webdav.interfaces.WebDAVPropstatErrors,
+                          propp.PROPPATCH)
+
+        self.assertEqual(propp.setprops, [])
+        self.assertEqual(propp.removeprops, [])
+
+    def test_error_set_prop_with_remove(self):
+        class PROPPATCHHandlerError(PROPPATCHHandler):
+            def handleSet(self, prop):
+                raise zope.webdav.interfaces.PropertyNotFound(
+                    self.context, "getcontenttype", u"property is missing")
+
+        request = TestRequest(
+            remove_properties = "<displayname>Test Name</displayname>",
+            set_properties = "<getcontenttype>text/plain</getcontenttype>")
+        propp = PROPPATCHHandlerError(Resource(), request)
+        self.assertRaises(zope.webdav.interfaces.WebDAVPropstatErrors,
+                          propp.PROPPATCH)
+
+        self.assertEqual(propp.setprops, [])
+        self.assertEqual(propp.removeprops, ['{DAV:}displayname'])
+
+    def test_response(self):
+        request = TestRequest(
+            remove_properties = "<displayname>Display name</displayname>",
+            set_properties = "<getcontenttype>text/plain</getcontenttype>")
+        propp = PROPPATCHHandler(Resource(), request)
+        result = propp.PROPPATCH()
+
+        assertXMLEqual(result, """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response xmlns:ns0="DAV:">
+  <ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+  <ns0:propstat xmlns:ns0="DAV:">
+    <ns0:prop xmlns:ns0="DAV:">
+      <ns0:getcontenttype xmlns:ns0="DAV:"/>
+      <ns0:displayname xmlns:ns0="DAV:"/>
+    </ns0:prop>
+    <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+  </ns0:propstat>
+</ns0:response></ns0:multistatus>""")
+
+
+class IExamplePropertyStorage(interface.Interface):
+
+    exampleintprop = schema.Int(
+        title = u"Example Integer Property")
+
+    exampletextprop = schema.Text(
+        title = u"Example Text Property")
+
+class IExtraPropertyStorage(interface.Interface):
+
+    extratextprop = schema.Text(
+        title = u"Property with no storage")
+
+exampleIntProperty = zope.webdav.properties.DAVProperty(
+    "{DAVtest:}exampleintprop", IExamplePropertyStorage)
+exampleTextProperty = zope.webdav.properties.DAVProperty(
+    "{DAVtest:}exampletextprop", IExamplePropertyStorage)
+extraTextProperty = zope.webdav.properties.DAVProperty(
+    "{DAVtest:}extratextprop", IExtraPropertyStorage)
+
+class ExamplePropertyStorage(object):
+    interface.implements(IExamplePropertyStorage)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def _getproperty(name, default = None):
+        def get(self):
+            return getattr(self.context, name, default)
+        def set(self, value):
+            setattr(self.context, name, value)
+        return property(get, set)
+
+    exampleintprop = _getproperty("intprop", default = 0)
+
+    exampletextprop = _getproperty("text", default = u"")
+
+
+class PROPPATCHHandlePropertyModification(unittest.TestCase):
+
+    def setUp(self):
+        etreeSetup()
+
+        gsm = component.getGlobalSiteManager()
+
+        gsm.registerUtility(exampleIntProperty,
+                            name = "{DAVtest:}exampleintprop",
+                            provided = zope.webdav.interfaces.IDAVProperty)
+        gsm.registerUtility(exampleTextProperty,
+                            name = "{DAVtest:}exampletextprop",
+                            provided = zope.webdav.interfaces.IDAVProperty)
+        exampleTextProperty.field.readonly = False
+        gsm.registerUtility(extraTextProperty,
+                            name = "{DAVtest:}extratextprop",
+                            provided = zope.webdav.interfaces.IDAVProperty)
+
+        gsm.registerAdapter(ExamplePropertyStorage,
+                            (IResource, zope.webdav.interfaces.IWebDAVRequest),
+                            provided = IExamplePropertyStorage)
+
+        gsm.registerAdapter(zope.webdav.widgets.TextDAVInputWidget,
+                            (zope.schema.interfaces.IText,
+                             zope.webdav.interfaces.IWebDAVRequest))
+
+    def tearDown(self):
+        etreeTearDown()
+
+        gsm = component.getGlobalSiteManager()
+
+        gsm.unregisterUtility(exampleIntProperty,
+                              name = "{DAVtest:}exampleintprop",
+                              provided = zope.webdav.interfaces.IDAVProperty)
+        gsm.unregisterUtility(exampleTextProperty,
+                              name = "{DAVtest:}exampletextprop",
+                              provided = zope.webdav.interfaces.IDAVProperty)
+        gsm.unregisterUtility(extraTextProperty,
+                              name = "{DAVtest:}extratextprop",
+                              provided = zope.webdav.interfaces.IDAVProperty)
+
+        gsm.unregisterAdapter(ExamplePropertyStorage,
+                              (IResource,
+                               zope.webdav.interfaces.IWebDAVRequest),
+                              provided = IExamplePropertyStorage)
+
+        gsm.unregisterAdapter(zope.webdav.widgets.TextDAVInputWidget,
+                              (zope.schema.interfaces.IText,
+                               zope.webdav.interfaces.IWebDAVRequest))
+
+    def test_handleSetProperty(self):
+        etree = component.getUtility(IEtree)
+        propel = etree.Element("{DAVtest:}exampletextprop")
+        propel.text = "Example Text Prop"
+
+        request = TestRequest(
+            set_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+        resource = Resource("Text Prop", 10)
+
+        propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+        propp.handleSet(propel)
+
+        self.assertEqual(resource.text, "Example Text Prop")
+
+    def test_handleSet_forbidden_property(self):
+        etree = component.getUtility(IEtree)
+        propel = etree.Element("{DAVtest:}exampletextprop")
+        propel.text = "Example Text Prop"
+
+        exampleTextProperty.field.readonly = True
+
+        request = TestRequest(
+            set_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+        resource = Resource("Text Prop", 10)
+
+        propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ForbiddenError,
+                          propp.handleSet,
+                          propel)
+
+    def test_handleSet_property_notfound(self):
+        etree = component.getUtility(IEtree)
+        propel = etree.Element("{DAVtest:}exampletextpropmissing")
+        propel.text = "Example Text Prop"
+
+        request = TestRequest(
+            set_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+        resource = Resource("Text Prop", 10)
+
+        propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+        self.assertRaises(zope.webdav.interfaces.PropertyNotFound,
+                          propp.handleSet,
+                          propel)
+
+    def test_handleRemove_live_property(self):
+        etree = component.getUtility(IEtree)
+        propel = etree.Element("{DAVtest:}exampletextprop")
+        propel.text = "Example Text Prop"
+
+        request = TestRequest(
+            remove_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+        resource = Resource("Text Prop", 10)
+
+        propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ConflictError,
+                          propp.handleRemove,
+                          propel)
+
+    def test_handleRemove_no_dead_properties(self):
+        etree = component.getUtility(IEtree)
+        propel = etree.Element("{example:}exampledeadprop")
+        propel.text = "Example Text Prop"
+
+        request = TestRequest(
+            remove_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+        resource = Resource("Text Prop", 10)
+
+        propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ConflictError,
+                          propp.handleRemove,
+                          propel)
+
+
+class DEADProperties(object):
+    interface.implements(zope.webdav.interfaces.IOpaquePropertyStorage)
+
+    def __init__(self, context):
+        self.data = context.props = getattr(context, "props", {})
+
+    def getAllProperties(self):
+        for tag in self.data:
+            yield tag
+
+    def hasProperty(self, tag):
+        return tag in self.data
+
+    def getProperty(self, tag):
+        return self.data[tag]
+
+    def setProperty(self, tag, value):
+        self.data[tag] = value
+
+    def removeProperty(self, tag):
+        del self.data[tag]
+
+
+class PROPPATCHHandlePropertyRemoveDead(unittest.TestCase):
+
+    def setUp(self):
+        etreeSetup()
+
+        gsm = component.getGlobalSiteManager()
+
+        gsm.registerAdapter(DEADProperties, (IResource,))
+
+    def tearDown(self):
+        etreeTearDown()
+
+        gsm = component.getGlobalSiteManager()
+
+        gsm.unregisterAdapter(DEADProperties, (IResource,))
+
+    def test_remove_no_storage(self):
+        etree = component.getUtility(IEtree)
+        propel = etree.Element("{example:}exampledeadprop")
+        propel.text = "Example Text Prop"
+
+        request = TestRequest(
+            remove_properties = """<Dt:exampledeadprop xmlns:Dt="example:">Example Text Prop</Dt:exampledeadprop>""")
+        resource = Resource("Text Prop", 10)
+
+        propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ConflictError,
+                          propp.handleRemove,
+                          propel)
+
+    def test_remove_not_there(self):
+        etree = component.getUtility(IEtree)
+        propel = etree.Element("{example:}exampledeadprop")
+        propel.text = "Example Text Prop"
+
+        request = TestRequest(
+            remove_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+        resource = Resource("Text Prop", 10)
+
+        propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+        self.assertRaises(zope.webdav.interfaces.ConflictError,
+                          propp.handleRemove,
+                          propel)
+
+    def test_remove_prop(self):
+        etree = component.getUtility(IEtree)
+        propel = etree.Element("{example:}exampledeadprop")
+        propel.text = "Example Text Prop"
+
+        request = TestRequest(
+            remove_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+        resource = Resource("Text Prop", 10)
+
+        testprop = "{example:}exampledeadprop"
+
+        deadprops = DEADProperties(resource)
+        deadprops.setProperty(testprop, "Example Text Prop")
+        self.assertEqual(deadprops.hasProperty(testprop), True)
+        self.assertEqual(deadprops.getProperty(testprop), "Example Text Prop")
+
+        propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+
+        propp.handleRemove(propel)
+
+        self.assertEqual(deadprops.hasProperty(testprop), False)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(PROPPATCHXmlParsing),
+        unittest.makeSuite(PROPPATCHHandlePropertyModification),
+        unittest.makeSuite(PROPPATCHHandlePropertyRemoveDead),
+        ))


Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/tests/test_publisher.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_publisher.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_publisher.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,112 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 utility for interfacing with the zope.locking package from ZC
+
+$Id$
+"""
+
+import unittest
+import types
+from cStringIO import StringIO
+
+from zope.interface.verify import verifyObject
+
+from zope.webdav.publisher import WebDAVRequest
+from zope.webdav.interfaces import IWebDAVRequest, IWebDAVResponse, BadRequest
+
+def create_request(body = None, env = {}):
+    if isinstance(body, types.StringTypes):
+        body = StringIO(body)
+    elif body is None:
+        body = StringIO('')
+    return WebDAVRequest(body, env)
+
+
+class TestWebDAVPublisher(unittest.TestCase):
+
+    def setUp(self):
+        from zope.webdav.testing import etreeSetup
+        self.etree = etreeSetup()
+
+    def tearDown(self):
+        from zope.webdav.testing import etreeTearDown
+        etreeTearDown()
+
+    def test_noinput(self):
+        request = create_request()
+        self.assert_(verifyObject(IWebDAVRequest, request))
+        self.assertEqual(request.content_type, None)
+        self.assertEqual(request.xmlDataSource, None)
+
+    def test_textinput(self):
+        body = "This is some text"
+        request = create_request(body, {"CONTENT_TYPE": "text/plain",
+                                        "CONTENT_LENGTH": len(body)})
+        request.processInputs()
+        self.assertEqual(request.content_type, "text/plain")
+        self.assertEqual(request.xmlDataSource, None)
+
+    def test_unicodeInput(self):
+        body = "This is some text"
+        request = create_request(body,
+                                 {"CONTENT_TYPE": "text/plain;charset=cp1252",
+                                  "CONTENT_LENGTH": len(body)})
+        request.processInputs()
+        self.assertEqual(request.content_type, "text/plain")
+        self.assertEqual(request.xmlDataSource, None)
+
+    def test_textxml(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+        <somedoc>This is some xml document</somedoc>
+        """
+        request = create_request(body, {"CONTENT_TYPE": "text/xml",
+                                        "CONTENT_LENGTH": len(body)})
+        request.processInputs()
+
+        self.assertEqual(request.content_type, "text/xml")
+        self.assert_(request.xmlDataSource is not None)
+
+    def test_applicationxml(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+        <somedoc>This is some xml document</somedoc>
+        """
+        request = create_request(body, {"CONTENT_TYPE": "application/xml",
+                                        "CONTENT_LENGTH": len(body)})
+        request.processInputs()
+
+        self.assertEqual(request.content_type, "application/xml")
+        self.assert_(request.xmlDataSource is not None)
+
+    def test_xml_nobody(self):
+        request = create_request("", {"CONTENT_TYPE": "text/xml"})
+        request.processInputs()
+        self.assertEqual(request.xmlDataSource, None)
+
+    def test_response(self):
+        request = create_request()
+        self.assert_(verifyObject(IWebDAVResponse, request.response))
+
+    def test_invalidxml(self):
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+        <somedoc>Bad End Tag</anotherdoc>
+        """
+        request = create_request(body, {"CONTENT_TYPE": "application/xml",
+                                        "CONTENT_LENGTH": len(body)})
+        self.assertRaises(BadRequest, request.processInputs)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(TestWebDAVPublisher),
+        ))


Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_publisher.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,336 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 WebDAV widget framework.
+
+$Id$
+"""
+
+import unittest
+import datetime
+from cStringIO import StringIO
+
+from zope import schema
+from zope import component
+from zope.schema.interfaces import ITextLine
+from zope.interface import Interface, implements
+from zope.interface.verify import verifyObject
+from zope.datetime import tzinfo
+
+from zope.webdav import widgets
+import zope.webdav.interfaces
+from zope.webdav.publisher import WebDAVRequest
+from zope.webdav.testing import etreeSetup, etreeTearDown, assertXMLEqual
+
+
+class TestWebDAVRequest(WebDAVRequest):
+    """."""
+    def __init__(self, elem = None):
+        if elem is not None:
+            body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propertyupdate xmlns:D="DAV:">
+  <D:set>
+    <D:prop />
+  </D:set>
+</D:propertyupdate>
+"""
+            f = StringIO(body)
+        else:
+            f = StringIO("")
+
+        super(TestWebDAVRequest, self).__init__(
+            f, {"CONTENT_TYPE": "text/xml",
+                "CONTENT_LENGTH": len(f.getvalue()),
+                })
+
+        # processInputs to test request
+        self.processInputs()
+
+        # if elem is given insert it into the proppatch request.
+        if elem is not None:
+            self.xmlDataSource[0][0].append(elem)
+
+
+class _WebDAVWidgetTest(unittest.TestCase):
+
+    namespace = u"testns:"
+    name = u"foo"
+    missing_name = u"someotherproperty"
+
+    field_content = u"" # field value assigned to the demo content.
+    rendered_content = u"" # string value of the some value in ...
+
+    _FieldFactory = None
+    _WidgetFactory = None
+
+    def tearDown(self):
+        etreeTearDown()
+
+    def setUp(self):
+        self.etree = etreeSetup()
+
+    def setUpContent(self, desc = u'', title = u'Foo Title', element = None):
+        ## setup the field first to stop some really weird errors
+        foofield = self._FieldFactory(
+            __name__ = self.name,
+            title = title,
+            description = desc)
+        class IDemoContent(Interface):
+            foo = foofield
+
+        class DemoContent(object):
+            implements(IDemoContent)
+
+        self.content = DemoContent()
+        field = IDemoContent['foo']
+        self.field = field.bind(self.content)
+        self.setUpWidget(element)
+
+    def setUpWidget(self, element = None):
+        request = TestWebDAVRequest(element)
+        self.widget = self._WidgetFactory(self.field, request)
+        self.widget.namespace = self.namespace
+
+
+class WebDAVBaseWidgetTest(_WebDAVWidgetTest):
+
+    _FieldFactory = schema.Text
+    _WidgetFactory = widgets.DAVWidget
+
+    def setUp(self):
+        super(WebDAVBaseWidgetTest, self).setUp()
+        self.setUpContent()
+
+    def test_dontuseDAVWidgetRender(self):
+        self.assertRaises(NotImplementedError, self.widget.render)
+
+    def test_dontuseDAVWidgetToDAVValue(self):
+        self.assertRaises(NotImplementedError, self.widget.toDAVValue, u"test")
+
+
+class WebDAVWidgetTest(_WebDAVWidgetTest):
+
+    _FieldFactory = schema.Text
+    _WidgetFactory = widgets.TextDAVWidget
+
+    def test_interface(self):
+        self.assertEqual(
+            verifyObject(zope.webdav.interfaces.IDAVWidget, self.widget), True)
+
+    def test_render(self):
+        self.widget.setRenderedValue(self.field_content)
+        self.content.foo = self.field_content
+        element = self.widget.render()
+        assertXMLEqual(self.etree.tostring(element),
+            '<ns0:foo xmlns:ns0="testns:">%s</ns0:foo>' % self.rendered_content)
+
+    def test_nofieldValue(self):
+        request = TestWebDAVRequest()
+        widget = self._WidgetFactory(self.field, request)
+        widget.namespace = self.namespace
+        element = widget.render()
+        assertXMLEqual(self.etree.tostring(element),
+                         '<ns0:foo xmlns:ns0="testns:" />')
+
+
+class TextWebDAVWidgetTest(WebDAVWidgetTest):
+
+    _FieldFactory  = schema.Text
+    _WidgetFactory = widgets.TextDAVWidget
+
+    field_content = u'Foo Value'
+    rendered_content = u'Foo Value'
+
+    def setUp(self):
+        super(TextWebDAVWidgetTest, self).setUp()
+        self.setUpContent()
+
+
+class IntWebDAVWidgetTest(WebDAVWidgetTest):
+
+    _FieldFactory  = schema.Int
+    _WidgetFactory = widgets.IntDAVWidget
+
+    field_content = 10
+    rendered_content = u'10'
+
+    def setUp(self):
+        super(IntWebDAVWidgetTest, self).setUp()
+        self.setUpContent()
+
+
+class FloatWebDAVWidgetTest(WebDAVWidgetTest):
+
+    _FieldFactory  = schema.Float
+    _WidgetFactory = widgets.IntDAVWidget
+
+    field_content = 10.0
+    rendered_content = u"10.0"
+
+    def setUp(self):
+        super(FloatWebDAVWidgetTest, self).setUp()
+        self.setUpContent()
+
+
+class DatetimeWebDAVWidgetTest(WebDAVWidgetTest):
+
+    _FieldFactory  = schema.Datetime
+    _WidgetFactory = widgets.DatetimeDAVWidget
+
+    rendered_content = u"Wed, 24 May 2006 00:00:58 +0100"
+    field_content = datetime.datetime(2006, 5, 24, 0, 0, 58,
+                                      tzinfo = tzinfo(60))
+
+    def setUp(self):
+        super(DatetimeWebDAVWidgetTest, self).setUp()
+        self.setUpContent()
+
+
+class DateWebDAVWidgetTest(DatetimeWebDAVWidgetTest):
+
+    _FieldFactory  = schema.Date
+    _WidgetFactory = widgets.DatetimeDAVWidget
+
+    field_content = datetime.date(2006, 5, 24)
+    rendered_content = u"Wed, 24 May 2006 00:00:00"
+
+
+class ISO8601DatetimeWebDAVWidgetTest(DatetimeWebDAVWidgetTest):
+
+    _FieldFactory  = schema.Datetime
+    _WidgetFactory = widgets.ISO8601DatetimeDAVWidget
+
+    rendered_content = u"2006-05-24T00:00:58Z"
+
+
+class ISO8601DateWebDAVWidgetTest(DatetimeWebDAVWidgetTest):
+
+    _FieldFactory  = schema.Date
+    _WidgetFactory = widgets.ISO8601DatetimeDAVWidget
+
+    field_content = datetime.date(2006, 5, 24)
+    rendered_content = u"2006-05-24T00:00:00Z"
+
+
+class ListWebDAVWidgetTest(WebDAVWidgetTest):
+
+    _FieldFactory  = schema.List
+    _WidgetFactory = widgets.ListDAVWidget
+
+    field_content = [u'collection']
+    rendered_content = '<ns0:collection />'
+
+    def setUp(self):
+        super(ListWebDAVWidgetTest, self).setUp()
+        self.setUpContent()
+
+
+class ListTextWebDAVWidgetTest(WebDAVWidgetTest):
+
+    _FieldFactory = schema.List
+    _WidgetFactory = widgets.ListDAVWidget
+
+    rendered_content = "<ns0:name>firstitem</ns0:name><ns0:name>seconditem</ns0:name>"
+
+    def setUp(self):
+        self.etree = etreeSetup()
+        component.getGlobalSiteManager().registerAdapter(
+            widgets.TextDAVWidget,
+            (ITextLine, zope.webdav.interfaces.IWebDAVRequest))
+
+        foofield = schema.List(__name__ = self.name,
+                               title = u"Foo Title",
+                               description = u"Foo field",
+                               value_type = schema.TextLine(
+                                   __name__ = "name",
+                                   title = u"Foo Title",
+                                   description = u"Foo field"))
+
+        class IDemoContent(Interface):
+            foo = foofield
+
+        class DemoContent(object):
+            implements(IDemoContent)
+
+        self.field_content = [u"firstitem", u"seconditem"]
+        self.content = DemoContent()
+        field = IDemoContent['foo']
+        self.field = field.bind(self.content)
+        self.setUpWidget()
+
+    def tearDown(self):
+        component.getGlobalSiteManager().unregisterAdapter(
+            widgets.TextDAVWidget,
+            (ITextLine, zope.webdav.interfaces.IWebDAVRequest))
+        super(ListTextWebDAVWidgetTest, self).tearDown()
+
+
+class ObjectDAVWidgetTest(WebDAVWidgetTest):
+
+    _WidgetFactory = widgets.ObjectDAVWidget
+
+    rendered_content = '<ns0:name>Michael Kerrin</ns0:name>'
+
+    def setUp(self):
+        self.etree = etreeSetup()
+
+        class ISimpleInterface(Interface):
+            name = schema.TextLine(
+                title = u"Named subproperty",
+                description = u"")
+
+        class SimpleObject(object):
+            name = u"Michael Kerrin"
+
+        foofield = schema.Object(__name__ = self.name,
+                                 title = u"Foo Title",
+                                 description = u"Foo field",
+                                 schema = ISimpleInterface)
+        class IDemoContent(Interface):
+            foo = foofield
+
+        class DemoContent(object):
+            implements(IDemoContent)
+
+        self.field_content = SimpleObject()
+        self.content = DemoContent()
+        field = IDemoContent['foo']
+        self.field = field.bind(self.content)
+        self.setUpWidget()
+
+        component.getGlobalSiteManager().registerAdapter(
+            widgets.TextDAVWidget,
+            (ITextLine,
+             zope.webdav.interfaces.IWebDAVRequest))
+
+    def tearDown(self):
+        component.getGlobalSiteManager().unregisterAdapter(
+            widgets.TextDAVWidget,
+            (ITextLine, zope.webdav.interfaces.IWebDAVRequest))
+        super(ObjectDAVWidgetTest, self).tearDown()
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(WebDAVBaseWidgetTest),
+        unittest.makeSuite(TextWebDAVWidgetTest),
+        unittest.makeSuite(IntWebDAVWidgetTest),
+        unittest.makeSuite(FloatWebDAVWidgetTest),
+        unittest.makeSuite(DatetimeWebDAVWidgetTest),
+        unittest.makeSuite(DateWebDAVWidgetTest),
+        unittest.makeSuite(ISO8601DatetimeWebDAVWidgetTest),
+        unittest.makeSuite(ISO8601DateWebDAVWidgetTest),
+        unittest.makeSuite(ListWebDAVWidgetTest),
+        unittest.makeSuite(ListTextWebDAVWidgetTest),
+        unittest.makeSuite(ObjectDAVWidgetTest),
+        ))


Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/tests/test_zetree.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_zetree.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_zetree.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,169 @@
+##############################################################################
+#
+# Copyright (c) 2006 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 ElementTree support within WebDAV. These aren't really tests but
+more of an assertion that I spelt things, like variable names correctly. By
+ust calling the methods here I have managed to find a bunch of bugs :-)
+
+Otherwise I just assume that underlying engine does its job correctly.
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+
+from zope.interface.verify import verifyObject
+import zope.webdav.zetree
+from zope.webdav.ietree import IEtree
+
+
+class BaseEtreeTestCase(unittest.TestCase):
+
+    def test_interface(self):
+        self.assertEqual(verifyObject(IEtree, self.etree), True)
+
+    def test_comment(self):
+        comment = self.etree.Comment(u"some text")
+
+    def test_etree(self):
+        etree = self.etree.ElementTree()
+
+    def test_XML(self):
+        xml = self.etree.XML(u"<p>some text</p>")
+
+    def test_fromstring(self):
+        xml = self.etree.fromstring(u"<p>some text</p>")
+
+    def test_element(self):
+        elem = self.etree.Element(u"testtag")
+
+    def test_iselement(self):
+        elem = self.etree.Element(u"testtag")
+        iselem = self.etree.iselement(elem)
+        self.assert_(iselem, "Not an element")
+
+    def test_parse(self):
+        f = StringIO("<b>Test Source String</b>")
+        self.etree.parse(f)
+
+    def test_qname(self):
+        qname = self.etree.QName("http://example.namespace.org", "test")
+
+    def test_tostring(self):
+        elem = self.etree.Element(u"testtag")
+        string = self.etree.tostring(elem, "ascii")
+        self.assert_(isinstance(string, str), "Not a string")
+
+    def test_treeBuilder(self):
+        self.assertRaises(NotImplementedError, self.etree.TreeBuilder)
+
+    def test_subelement(self):
+        elem = self.etree.Element(u"testtag")
+        subel = self.etree.SubElement(elem, "foo")
+
+    def test_PI(self):
+        pi = self.etree.PI("sometarget")
+
+    def test_processinginstructions(self):
+        pi = self.etree.ProcessingInstruction("sometarget")
+
+    def test_xmltreebulider(self):
+        builder = self.etree.XMLTreeBuilder()
+
+
+class OrigElementTreeTestCase(BaseEtreeTestCase):
+
+    def setUp(self):
+        self.etree = zope.webdav.zetree.EtreeEtree()
+
+    def tearDown(self):
+        del self.etree
+
+
+class LXMLElementTreeTestCase(BaseEtreeTestCase):
+
+    def setUp(self):
+        self.etree = zope.webdav.zetree.LxmlEtree()
+
+    def tearDown(self):
+        del self.etree
+
+    def test_PI(self):
+        self.assertRaises(NotImplementedError, self.etree.PI, "sometarget")
+
+    def test_processinginstructions(self):
+        self.assertRaises(NotImplementedError,
+                          self.etree.ProcessingInstruction, "sometarget")
+
+    def test_xmltreebulider(self):
+        self.assertRaises(NotImplementedError, self.etree.XMLTreeBuilder)
+
+    def test_namespaces(self):
+        # When we have a element whoes namespace declaration is declared
+        # in a parent element lxml doesn't print out the namespace
+        # declaration by default.
+        multinselemstr = """<D:prop xmlns:D="DAV:"><D:owner><H:href xmlns:H="examplens">http://example.org</H:href></D:owner></D:prop>"""
+        multinselem = self.etree.fromstring(multinselemstr)
+        self.assertEqual(self.etree.tostring(multinselem[0]),
+                         """<D:owner xmlns:D="DAV:"><H:href xmlns:H="examplens">http://example.org</H:href></D:owner>""")
+
+
+class Python25ElementTreeTestCase(BaseEtreeTestCase):
+
+    def setUp(self):
+        self.etree = zope.webdav.zetree.EtreePy25()
+
+    def tearDown(self):
+        del self.etree
+
+
+class NoElementTreePresentTestCase(unittest.TestCase):
+    # If no element tree engine exists then run this test case. Which will
+    # mark the current instance has broken.
+
+    def test_warn(self):
+        self.fail("""
+        WARNING: zope.webdav needs elementtree installed in order to run.
+        """)
+
+def test_suite():
+    suite = unittest.TestSuite()
+
+    # Only run the tests for each elementtree that is installed.
+    foundetree = False
+    try:
+        import elementtree
+        suite.addTest(unittest.makeSuite(OrigElementTreeTestCase))
+        foundetree = True
+    except ImportError:
+        pass
+
+    try:
+        import lxml.etree
+        suite.addTest(unittest.makeSuite(LXMLElementTreeTestCase))
+        foundetree = True
+    except ImportError:
+        pass
+
+    try:
+        import xml.etree
+        suite.addTest(unittest.makeSuite(Python25ElementTreeTestCase))
+        foundetree = True
+    except ImportError:
+        pass
+
+    if not foundetree:
+        suite.addTest(unittest.makeSuite(NoElementTreePresentTestCase))
+
+    return suite


Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_zetree.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/utils.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/utils.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/utils.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,469 @@
+##############################################################################
+# Copyright (c) 2006 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.
+##############################################################################
+"""A collection of useful classes used for generating common XML fragments
+for use within zope.webdav
+
+MultiStatus
+
+  <!ELEMENT multistatus (response*, responsedescription?)  >
+  <!ELEMENT response (href, ((href*, status)|(propstat+)),
+            error?, responsedescription? , location?) >
+  <!ELEMENT propstat (prop, status, error?, responsedescription?) >
+  <!ELEMENT prop ANY >
+  <!ELEMENT href (#PCDATA)>
+  <!ELEMENT status (#PCDATA) >
+  <!ELEMENT responsedescription (#PCDATA) >
+  <!ELEMENT error ANY >
+  <!ELEMENT location (href)>
+
+Also contains some usefully methods like
+
++ getObjectURL
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import types
+
+from zope import component
+from zope import interface
+from zope.publisher.http import status_reasons
+from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.app.container.interfaces import IReadContainer
+from ietree import IEtree
+
+class IPropstat(interface.Interface):
+    """Helper interface to render a response XML element. 
+    """
+
+    properties = interface.Attribute("""List of etree elements that make up
+    the prop element.
+    """)
+
+    status = interface.Attribute("""Integer status code of all the properties
+    belonging to this propstat XML element.
+    """)
+
+    error = interface.Attribute("""List of etree elements describing the
+    error condition of the properties within this propstat XML element.
+    """)
+
+    responsedescription = interface.Attribute("""String containing readable
+    information about the status of all the properties belonging to this
+    propstat element.
+    """)
+
+    def __call__():
+        """Render this propstat object to a etree XML element.
+        """
+
+class IResponse(interface.Interface):
+    """Helper object to render a response XML element.
+    """
+
+    href = interface.Attribute("""String representation of the HTTP URL
+    pointing to the resource that this response element represents.
+    """)
+
+    status = interface.Attribute("""Optional integer status code for this
+    response element.
+    """)
+
+    error = interface.Attribute("""List of etree elements describing the
+    error conditions of all the properties contained within an instance of
+    this response XML element.
+    """)
+
+    responsedescription = interface.Attribute("""String containing readable
+    information about this response relative to the request or result.
+    """)
+
+    location = interface.Attribute("""The "Location" HTTP header for use with
+    some status codes like 201 and 300. This value should be set if any of
+    status codes contained within this response XML element are required to
+    have a HTTP "Location" header.
+    """)
+
+    def addPropstats(status, propstat):
+        """Add a IPropstat instance to this response. This propstat will
+        rendered with a status of 'status'.
+        """
+
+    def addProperty(status, element):
+        """Add a etree.Element object to this response XML element. The property
+        will be added within the propstat element that registered under the
+        status code.
+        """
+
+    def __call__():
+        """Render this response object to an etree element.
+        """
+
+
+class IMultiStatus(interface.Interface):
+
+    responses = interface.Attribute("""List of IResponse objects that make
+    all the responses contained within this multistatus XML element.
+    """)
+
+    responsedescription = interface.Attribute("""String containing a readable
+    informatino about the status of all the responses belonging to this
+    multistatus element.
+    """)
+
+    def __call__():
+        """Render this multistatus object to an etree element.
+        """
+
+################################################################################
+#
+# Some helper methods. Which includes:
+#
+#  - makeelement(namespace, tagname, text_or_el = None)
+#
+#  - makedavelement(tagname, text_or_el = None)
+#
+#  - makestatuselement(status)
+#
+#  - parseEtreeTag(tag)
+#
+################################################################################
+
+def makeelement(namespace, tagname, text_or_el = None):
+    etree = component.getUtility(IEtree)
+    el = etree.Element(etree.QName(namespace, tagname))
+    if isinstance(text_or_el, (str, unicode)):
+        el.text = text_or_el
+    elif text_or_el is not None:
+        el.append(text_or_el)
+
+    return el
+
+
+def makedavelement(tagname, text_or_el = None):
+    """
+      >>> assertXMLEqual(etree.tostring(makedavelement('foo')),
+      ...    '<ns0:foo xmlns:ns0="DAV:" />')
+      >>> assertXMLEqual(etree.tostring(makedavelement('foo', 'foo content')),
+      ...    '<ns0:foo xmlns:ns0="DAV:">foo content</ns0:foo>')
+    """
+    return makeelement('DAV:', tagname, text_or_el)
+
+
+def makestatuselement(status):
+    """
+      >>> etree.tostring(makestatuselement(200))
+      '<ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>'
+    """
+    if isinstance(status, types.IntType):
+        status = 'HTTP/1.1 %d %s' %(status, status_reasons[status])
+
+    return makedavelement('status', status)
+
+
+def parseEtreeTag(tag):
+    """Return namespace, tagname pair.
+
+      >>> parseEtreeTag('{DAV:}prop')
+      ['DAV:', 'prop']
+
+      >>> parseEtreeTag('prop')
+      (None, 'prop')
+
+    """
+    if tag[0] == '{':
+        return tag[1:].split('}')
+    return None, tag
+
+################################################################################
+#
+# 
+#
+################################################################################
+
+class Propstat(object):
+    """Simple propstat xml handler.
+
+      >>> from zope.interface.verify import verifyObject
+      >>> pstat = Propstat()
+      >>> verifyObject(IPropstat, pstat)
+      True
+      >>> pstat.status = 200
+
+      >>> pstat.properties.append(makedavelement(u'testprop', u'Test Property'))
+      >>> assertXMLEqual(etree.tostring(pstat()),
+      ...    '<ns0:propstat xmlns:ns0="DAV:"><ns0:prop><ns0:testprop>Test Property</ns0:testprop></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat>')
+
+      >>> pstat.properties.append(makedavelement(u'test2', u'Second Test'))
+      >>> assertXMLEqual(etree.tostring(pstat()),
+      ...    '<ns0:propstat xmlns:ns0="DAV:"><ns0:prop><ns0:testprop>Test Property</ns0:testprop><ns0:test2>Second Test</ns0:test2></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat>')
+
+      >>> pstat.responsedescription = u'This is ok'
+      >>> assertXMLEqual(etree.tostring(pstat()),
+      ...    '<ns0:propstat xmlns:ns0="DAV:"><ns0:prop><ns0:testprop>Test Property</ns0:testprop><ns0:test2>Second Test</ns0:test2></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status><ns0:responsedescription>This is ok</ns0:responsedescription></ns0:propstat>')
+
+      >>> pstat.error = [makedavelement(u'precondition-error')]
+      >>> assertXMLEqual(etree.tostring(pstat()),
+      ...     '<ns0:propstat xmlns:ns0="DAV:"><ns0:prop><ns0:testprop>Test Property</ns0:testprop><ns0:test2>Second Test</ns0:test2></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status><ns0:error><ns0:precondition-error /></ns0:error><ns0:responsedescription>This is ok</ns0:responsedescription></ns0:propstat>')
+
+    The status must be set.
+
+      >>> pstat = Propstat()
+      >>> pstat()
+      Traceback (most recent call last):
+      ...
+      ValueError: Must set status before rendering a propstat.
+
+    """
+    interface.implements(IPropstat)
+
+    def __init__(self):
+        # etree.Element
+        self.properties = []
+        # int or string
+        self.status = None
+
+        # etree.Element
+        self.error = []
+        # text
+        self.responsedescription = ""
+
+    def __call__(self):
+        if self.status is None:
+            raise ValueError("Must set status before rendering a propstat.")
+
+        propstatel = makedavelement('propstat')
+        propel = makedavelement('prop')
+        propstatel.append(propel)
+
+        for prop in self.properties:
+            propel.append(prop)
+
+        propstatel.append(makestatuselement(self.status))
+
+        for error in self.error:
+            propstatel.append(makedavelement('error', error))
+
+        if self.responsedescription:
+            propstatel.append(makedavelement('responsedescription',
+                                             self.responsedescription))
+
+        return propstatel
+
+
+class Response(object):
+    """WebDAV response XML element
+
+    We need a URL to initialize the Response object, /container is a good
+    choice.
+
+      >>> from zope.interface.verify import verifyObject
+      >>> response = Response('/container')
+      >>> verifyObject(IResponse, response)
+      True
+      >>> assertXMLEqual(etree.tostring(response()),
+      ...                '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href></ns0:response>')
+      >>> response.status = 200
+      >>> response.href.append('/container2')
+      >>> assertXMLEqual(etree.tostring(response()),
+      ...                '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:href>/container2</ns0:href><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:response>')
+
+    The response XML element can contain a number of Propstat elements
+    organized by status code.
+
+      >>> response = Response('/container')
+      >>> pstat1 = Propstat()
+      >>> pstat1.status = 200
+      >>> pstat1.properties.append(makedavelement(u'test1', u'test one'))
+      >>> response.addPropstats(200, pstat1)
+      >>> pstat2 = Propstat()
+      >>> pstat2.status = 404
+      >>> pstat2.properties.append(makedavelement(u'test2'))
+      >>> response.addPropstats(404, pstat2)
+      >>> assertXMLEqual(etree.tostring(response()),
+      ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat><ns0:propstat><ns0:prop><ns0:test2 /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat></ns0:response>')
+
+      >>> response.error = [makedavelement(u'precondition-failed')]
+      >>> assertXMLEqual(etree.tostring(response()),
+      ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat><ns0:propstat><ns0:prop><ns0:test2 /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat><ns0:error><ns0:precondition-failed /></ns0:error></ns0:response>')
+
+      >>> response.responsedescription = u'webdav description'
+      >>> assertXMLEqual(etree.tostring(response()),
+      ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat><ns0:propstat><ns0:prop><ns0:test2 /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat><ns0:error><ns0:precondition-failed /></ns0:error><ns0:responsedescription>webdav description</ns0:responsedescription></ns0:response>')
+
+      >>> response.location = '/container2'
+      >>> assertXMLEqual(etree.tostring(response()),
+      ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat><ns0:propstat><ns0:prop><ns0:test2 /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat><ns0:error><ns0:precondition-failed /></ns0:error><ns0:responsedescription>webdav description</ns0:responsedescription><ns0:location><ns0:href>/container2</ns0:href></ns0:location></ns0:response>')
+
+      >>> response = Response('/container1')
+      >>> response.href.append('/container2')
+      >>> response.addPropstats(200, Propstat())
+      >>> etree.tostring(response())
+      Traceback (most recent call last):
+      ...
+      ValueError: Response object is in an invalid state.
+
+      >>> response = Response('/container1')
+      >>> response.status = 200
+      >>> response.addPropstats(200, Propstat())
+      >>> etree.tostring(response())
+      Traceback (most recent call last):
+      ...
+      ValueError: Response object is in an invalid state.
+
+    Now the must handly method of all the addProperty mehtod:
+
+      >>> resp = Response('/container')
+      >>> resp.addProperty(200, makedavelement(u'testprop', u'Test Property'))
+      >>> assertXMLEqual(etree.tostring(resp()),
+      ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:testprop>Test Property</ns0:testprop></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat></ns0:response>')
+      >>> resp.addProperty(200, makedavelement(u'testprop2',
+      ...                                      u'Test Property Two'))
+      >>> assertXMLEqual(etree.tostring(resp()),
+      ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:testprop>Test Property</ns0:testprop><ns0:testprop2>Test Property Two</ns0:testprop2></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat></ns0:response>')
+      >>> resp.addProperty(404, makedavelement(u'missing'))
+      >>> assertXMLEqual(etree.tostring(resp()),
+      ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:testprop>Test Property</ns0:testprop><ns0:testprop2>Test Property Two</ns0:testprop2></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat><ns0:propstat><ns0:prop><ns0:missing /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat></ns0:response>')
+
+    """
+    interface.implements(IResponse)
+
+    def __init__(self, href):
+        self.href = [href]
+        self.status = None
+        self._propstats = {} # status -> list of propstat Propstat object
+
+        self.error = []
+        self.responsedescription = ""
+        self.location = None
+
+    def getPropstat(self, status):
+        if status not in self._propstats:
+            self._propstats[status] = Propstat()
+        return self._propstats[status]
+
+    def addPropstats(self, status, propstat): # use getPropstats instead.
+        self._propstats[status] = propstat
+
+    def addProperty(self, status, element):
+        try:
+            propstat = self._propstats[status]
+        except KeyError:
+            propstat = self._propstats[status] = Propstat()
+
+        propstat.properties.append(element)
+
+    def __call__(self):
+        if (len(self.href) > 1 or self.status is not None) and self._propstats:
+            raise ValueError, "Response object is in an invalid state."
+
+        respel = makedavelement('response')
+        respel.append(makedavelement('href', self.href[0]))
+
+        if self.status is not None:
+            for href in self.href[1:]:
+                respel.append(makedavelement('href', href))
+
+            respel.append(makestatuselement(self.status))
+        else:
+            for status, propstat in self._propstats.items():
+                propstat.status = status
+                respel.append(propstat())
+
+        for error in self.error:
+            respel.append(makedavelement('error', error))
+
+        if self.responsedescription:
+            respel.append(makedavelement('responsedescription',
+                                         self.responsedescription))
+
+        if self.location is not None:
+            respel.append(makedavelement('location',
+                                         makedavelement('href', self.location)))
+
+        return respel
+
+
+class MultiStatus(object):
+    """Multistatus element generation
+
+      >>> from zope.interface.verify import verifyObject
+      >>> ms = MultiStatus()
+      >>> verifyObject(IMultiStatus, ms)
+      True
+      >>> assertXMLEqual(etree.tostring(ms()),
+      ... '<ns0:multistatus xmlns:ns0="DAV:" />')
+
+      >>> ms.responsedescription = u'simple description'
+      >>> assertXMLEqual(etree.tostring(ms()),
+      ... '<ns0:multistatus xmlns:ns0="DAV:"><ns0:responsedescription>simple description</ns0:responsedescription></ns0:multistatus>')
+
+      >>> response = Response('/container')
+      >>> ms.responses.append(response)
+      >>> assertXMLEqual(etree.tostring(ms()),
+      ... '<ns0:multistatus xmlns:ns0="DAV:"><ns0:response><ns0:href>/container</ns0:href></ns0:response><ns0:responsedescription>simple description</ns0:responsedescription></ns0:multistatus>')
+
+      >>> pstat1 = Propstat()
+      >>> pstat1.status = 200
+      >>> pstat1.properties.append(makedavelement(u'test1', u'test one'))
+      >>> response.addPropstats(200, pstat1)
+      >>> assertXMLEqual(etree.tostring(ms()),
+      ... '<ns0:multistatus xmlns:ns0="DAV:"><ns0:response><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat></ns0:response><ns0:responsedescription>simple description</ns0:responsedescription></ns0:multistatus>')
+
+      >>> response2 = Response('/container2')
+      >>> pstat2 = Propstat()
+      >>> pstat2.status = 404
+      >>> pstat2.properties.append(makedavelement(u'test2'))
+      >>> response2.addPropstats(404, pstat2)
+      >>> ms.responses.append(response2)
+      >>> assertXMLEqual(etree.tostring(ms()),
+      ... '<ns0:multistatus xmlns:ns0="DAV:"><ns0:response><ns0:href>/container</ns0:href>   <ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat></ns0:response><ns0:response><ns0:href>/container2</ns0:href><ns0:propstat><ns0:prop><ns0:test2 /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat></ns0:response><ns0:responsedescription>simple description</ns0:responsedescription></ns0:multistatus>''')
+
+    """
+    interface.implements(IMultiStatus)
+
+    def __init__(self):
+        # list of Response objects
+        self.responses = []
+        # text
+        self.responsedescription = ""
+
+    def __call__(self):
+        etree = component.getUtility(IEtree)
+        el = etree.Element(etree.QName('DAV:', 'multistatus'))
+        for response in self.responses:
+            el.append(response())
+
+        if self.responsedescription:
+            el.append(makedavelement('responsedescription',
+                                     self.responsedescription))
+
+        return el
+
+################################################################################
+#
+# Some other miscellanous helpful methods
+#
+################################################################################
+
+def getObjectURL(ob, req):
+    """Return the URL for the object `ob`.
+
+    If the object is a container and the url doesn't end in slash '/' then
+    append a slash to the url.
+    """
+    url = component.getMultiAdapter((ob, req), IAbsoluteURL)()
+    if IReadContainer.providedBy(ob) and url[-1] != "/":
+        url += "/"
+
+    return url


Property changes on: zope.webdav/trunk/src/zope/webdav/utils.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/widgets.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/widgets.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/widgets.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,268 @@
+##############################################################################
+# Copyright (c) 2006 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.
+##############################################################################
+"""A collection of usefull classes and methods related to WebDAV.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import component
+from zope import interface
+from zope.schema import getFieldsInOrder
+
+from zope.webdav.ietree import IEtree
+import interfaces
+
+import zope.datetime
+from zope.app.form.interfaces import ConversionError, MissingInputError
+
+DEFAULT_NS = 'DAV:'
+
+
+class DAVWidget(object):
+    """Base class for rendering WebDAV properties through implementations like
+    PROPFIND and PROPPATCH.
+    """
+    interface.implements(interfaces.IDAVWidget)
+    interface.classProvides(interfaces.IIDAVWidget)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+        self.name = self.context.__name__
+        self.namespace = None
+
+        # value to render.
+        self._value = None
+
+    def setRenderedValue(self, value):
+        self._value = value
+
+    def toDAVValue(self, value):
+        """Override this method in the base class. This method should either
+        a string, a list or tuple
+        """
+        raise NotImplementedError, \
+              "please implemented this method in a subclass of DAVWidget."
+
+    def renderName(self):
+        etree = component.getUtility(IEtree)
+        return etree.Element(etree.QName(self.namespace, self.name))
+
+    def render(self):
+        etree = component.getUtility(IEtree)
+        el = etree.Element(etree.QName(self.namespace, self.name))
+
+        rendered_value = self.toDAVValue(self._value)
+        el.text = rendered_value
+
+        return el
+
+
+class TextDAVWidget(DAVWidget):
+    interface.classProvides(interfaces.IIDAVWidget)
+
+    def toDAVValue(self, value):
+        if value is None:
+            return None
+        return value
+
+
+class IntDAVWidget(DAVWidget):
+    interface.classProvides(interfaces.IIDAVWidget)
+
+    def toDAVValue(self, value):
+        if value is not None:
+            return str(value)
+        return None
+
+
+class DatetimeDAVWidget(DAVWidget):
+    """Same widget can be used for a date field also."""
+    interface.classProvides(interfaces.IIDAVWidget)
+
+    def toDAVValue(self, value):
+        # datetime object
+        if value is None:
+            return None
+
+        return value.strftime("%a, %d %b %Y %H:%M:%S %z").strip()
+
+
+class ISO8601DatetimeDAVWidget(DAVWidget):
+    """Same widget can be used for a date field also."""
+    interface.classProvides(interfaces.IIDAVWidget)
+
+    def toDAVValue(self, value):
+        if value is None:
+            return None
+
+        return value.strftime('%Y-%m-%dT%TZ')
+
+
+class ObjectDAVWidget(DAVWidget):
+    interface.classProvides(interfaces.IIDAVWidget)
+
+    def render(self):
+        etree = component.getUtility(IEtree)
+        el = etree.Element(etree.QName(self.namespace, self.name))
+
+        if self._value is None:
+            return el
+
+        interface = self.context.schema
+        for name, field in getFieldsInOrder(interface):
+            field = field.bind(self._value)
+
+            widget = component.getMultiAdapter((field, self.request),
+                                               interfaces.IDAVWidget)
+            widget.namespace = self.namespace
+            widget.setRenderedValue(field.get(self._value))
+            el.append(widget.render())
+
+        return el
+
+
+class ListDAVWidget(DAVWidget):
+    interface.classProvides(interfaces.IIDAVWidget)
+
+    def render(self):
+        etree = component.getUtility(IEtree)
+        el = etree.Element(etree.QName(self.namespace, self.name))
+
+        if self._value is None:
+            return el
+
+        value_type = self.context.value_type
+        if value_type is None:
+            for value in self._value:
+                el.append(etree.Element(etree.QName(self.namespace, value)))
+        else:
+            # value_type is not None so render each item in the sequence
+            # according to the widget register for this field.
+            for value in self._value:
+                widget = component.getMultiAdapter((value_type, self.request),
+                                                   interfaces.IDAVWidget)
+                widget.setRenderedValue(value)
+                widget.namespace = self.namespace
+                el.append(widget.render())
+        return el
+
+
+################################################################################
+#
+# Now for a collection of input widgets.
+#
+################################################################################
+
+
+class DAVInputWidget(object):
+    interface.implements(interfaces.IDAVInputWidget)
+    interface.classProvides(interfaces.IIDAVInputWidget)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+        self.name = self.context.__name__
+        self.namespace = None
+
+    def getProppatchElement(self):
+        """NOTE that the latest specification does NOT specify that a client
+        can't update a property only once during a PROPPATCH request -> this
+        method and implementation is meaningless.
+        """
+        return self.request.xmlDataSource.findall(
+            '{DAV:}set/{DAV:}prop/{%s}%s' % (self.namespace, self.name))[-1:]
+
+    def hasInput(self):
+        if self.getProppatchElement():
+            return True
+        return False
+
+    def toFieldValue(self, element):
+        raise NotImplementedError(
+            "Please implement the toFieldValue as a subclass of DAVInputWidget")
+
+    def getInputValue(self):
+        el = self.getProppatchElement()
+
+        # form input is required, otherwise raise an error
+        if not el:
+            raise MissingInputError(self.name, None, None)
+
+        # convert input to suitable value - may raise conversion error
+        value = self.toFieldValue(el[0])
+        return value
+
+
+class TextDAVInputWidget(DAVInputWidget):
+    interface.classProvides(interfaces.IIDAVInputWidget)
+
+    def toFieldValue(self, element):
+        value = element.text
+        if value is None:
+            return u"" # toFieldValue must validate against the
+        if not isinstance(value, unicode):
+            return value.decode("utf-8")
+        return value
+
+
+class IntDAVInputWidget(DAVInputWidget):
+    interface.classProvides(interfaces.IIDAVInputWidget)
+
+    def toFieldValue(self, element):
+        value = element.text
+        # XXX - should this be happening - has then the field doesn't validate
+        # in the default case against the corresponding field.
+        if not value:
+            return self.context.missing_value
+
+        try:
+            return int(value)
+        except ValueError, e:
+            raise ConversionError("Invalid int", e)
+
+
+class FloatDAVInputWidget(DAVInputWidget):
+    interface.classProvides(interfaces.IIDAVInputWidget)
+
+    def toFieldValue(self, element):
+        value = element.text
+        if not value:
+            # XXX - should this be the case?
+            return self.context.missing_value
+
+        try:
+            return float(value)
+        except ValueError, e:
+            raise ConversionError("Invalid float", e)
+
+
+class DatetimeDAVInputWidget(DAVInputWidget):
+    interface.classProvides(interfaces.IIDAVInputWidget)
+
+    def toFieldValue(self, element):
+        value = element.text
+        try:
+            return zope.datetime.parseDatetimetz(value)
+        except (zope.datetime.DateTimeError, ValueError, IndexError), e:
+            raise ConversionError("Invalid datetime date", e)
+
+
+class DateDAVInputWidget(DatetimeDAVInputWidget):
+    interface.classProvides(interfaces.IIDAVInputWidget)
+
+    def toFieldValue(self, element):
+        value = super(DateDAVInputWidget, self).toFieldValue(element)
+        return value.date()


Property changes on: zope.webdav/trunk/src/zope/webdav/widgets.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/z3-configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/z3-configure.zcml	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/z3-configure.zcml	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,88 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <!--
+      Zope3 webdav support - this should all be in a seperate Zope3 package.
+    -->
+
+  <adapter
+     factory=".adapters.DAVSchemaAdapter"
+     />
+
+  <adapter
+     factory=".adapters.DAVFileGetSchema"
+     />
+
+  <adapter
+     factory=".deadproperties.OpaqueProperties"
+     trusted="1"
+     />
+
+  <class class=".deadproperties.OpaqueProperties">
+    <require
+       permission="zope.Public"
+       attributes="getAllProperties hasProperty getProperty"
+       />
+
+    <require
+       permission="zope.ManageContent"
+       attributes="setProperty removeProperty"
+       />
+  </class>
+
+  <!--
+      Support for using zope.locking for locking support.
+    -->
+  <adapter
+     factory=".lockingutils.DAVSupportedlock"
+     />
+
+  <adapter
+     factory=".lockingutils.DAVLockdiscovery"
+     />
+
+  <adapter
+     factory=".lockingutils.DAVLockmanager"
+     trusted="1"
+     />
+
+  <class class=".lockingutils.DAVLockmanager">
+    <require
+       permission="zope.View"
+       attributes="getActivelock islocked islockable"
+       />
+
+    <require
+       permission="zope.ManageContent"
+       attributes="lock refreshlock unlock"
+       />
+  </class>
+
+  <adapter
+     factory=".lockingutils.DAVActiveLock"
+     for="zope.interface.Interface
+          zope.webdav.interfaces.IWebDAVRequest"
+     provides="zope.webdav.coreproperties.IActiveLock"
+     />
+
+  <class class=".lockingutils.DAVActiveLockAdapter">
+    <require
+       permission="zope.View"
+       interface="zope.webdav.coreproperties.IActiveLock"
+       />
+  </class>
+
+  <class class=".lockingutils.IndirectToken">
+    <require
+       permission="zope.View"
+       attributes="context utility principal_ids started annotations roottoken"
+       />
+
+    <require permission="zope.View"
+      attributes="ended expiration duration remaining_duration" />
+
+    <require permission="zope.Security"
+      attributes="end"
+      set_attributes="expiration duration remaining_duration" />
+  </class>
+
+</configure>


Property changes on: zope.webdav/trunk/src/zope/webdav/z3-configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/zetree.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/zetree.py	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/zetree.py	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,305 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Zope Element Tree Support
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import copy
+from zope.interface import implements
+
+from ietree import IEtree
+
+class BaseEtree(object):
+    def Comment(self, text = None):
+        return self.etree.Comment(text)
+
+    # XXX - not tested
+    def dump(self, elem):
+        return self.etree.dump(elem)
+
+    def Element(self, tag, attrib = {}, **extra):
+        return self.etree.Element(tag, attrib, **extra)
+
+    def ElementTree(self, element = None, file = None):
+        return self.etree.ElementTree(element, file)
+
+    def XML(self, text):
+        return self.etree.fromstring(text)
+
+    fromstring = XML
+
+    def iselement(self, element):
+        return self.etree.iselement(element)
+
+    # XXX - not tested
+    def iterparse(self, source, events = None):
+        return self.etree.iterparse(source, events)
+
+    def parse(self, source, parser = None):
+        return self.etree.parse(source, parser)
+
+    def PI(self, target, text = None):
+        raise NotImplementedError, "lxml doesn't implement PI"
+
+    ProcessingInstruction = PI
+
+    def QName(self, text_or_uri, tag = None):
+        return self.etree.QName(text_or_uri, tag)
+
+    def SubElement(self, parent, tag, attrib = {}, **extra):
+        return self.etree.SubElement(parent, tag, attrib, **extra)
+
+    def tostring(self, element, encoding = None):
+        return self.etree.tostring(element, encoding)
+
+    def TreeBuilder(self, element_factory = None):
+        raise NotImplementedError, "lxml doesn't implement TreeBuilder"
+
+    def XMLTreeBuilder(self, html = 0, target = None):
+        raise NotImplementedError, "lxml doesn't implement XMLTreeBuilder"
+
+
+class EtreeEtree(BaseEtree):
+    """
+    Support for ElementTree
+
+      >>> from cStringIO import StringIO
+      >>> from zope.interface.verify import verifyObject
+      >>> letree = EtreeEtree()
+      >>> verifyObject(IEtree, letree)
+      True
+
+      >>> letree.Comment(u'some text') #doctest:+ELLIPSIS
+      <Element <function Comment at ...
+
+      >>> letree.Element(u'testtag')
+      <Element...
+
+      >>> letree.ElementTree() #doctest:+ELLIPSIS
+      <elementtree.ElementTree.ElementTree instance at ...
+
+      >>> letree.XML(u'<p>some text</p>')
+      <Element p ...
+
+      >>> letree.fromstring(u'<p>some text</p>')
+      <Element p ...
+
+      >>> elem = letree.Element(u'testtag')
+      >>> letree.iselement(elem)
+      1
+
+      >>> f = StringIO('<b>Test Source String</b>')
+      >>> letree.parse(f) #doctest:+ELLIPSIS
+      <elementtree.ElementTree.ElementTree instance at ...
+
+      >>> letree.QName('http://example.namespace.org', 'test')#doctest:+ELLIPSIS
+      <elementtree.ElementTree.QName instance at...
+
+      >>> print letree.tostring(elem, 'ascii')
+      <?xml version='1.0' encoding='ascii'?>
+      <testtag />
+
+      >>> letree.TreeBuilder()
+      Traceback (most recent call last):
+      ...
+      NotImplementedError: lxml doesn't implement TreeBuilder
+
+      >>> subel = letree.SubElement(elem, 'foo')
+      >>> letree.tostring(elem)
+      '<testtag><foo /></testtag>'
+
+      >>> letree.PI('sometarget')  #doctest:+ELLIPSIS
+      <Element <function ProcessingInstruction at ...
+
+      >>> letree.ProcessingInstruction('sometarget') #doctest:+ELLIPSIS
+      <Element <function ProcessingInstruction at ...
+
+      >>> letree.XMLTreeBuilder()
+      <elementtree.ElementTree.XMLTreeBuilder instance at ...
+
+    """
+    implements(IEtree)
+
+    def __init__(self):
+        from elementtree import ElementTree
+        self.etree = ElementTree
+
+    def XMLTreeBuilder(self, html = 0, target = None):
+        return self.etree.XMLTreeBuilder(html, target)
+
+    def PI(self, target, text = None):
+        return self.etree.PI(target, text)
+
+    ProcessingInstruction = PI
+
+
+class EtreePy25(BaseEtree):
+    """
+    Support for ElementTree
+
+      >>> from cStringIO import StringIO
+      >>> from zope.interface.verify import verifyObject
+      >>> letree = EtreePy25()
+      >>> verifyObject(IEtree, letree)
+      True
+
+      >>> letree.Comment(u'some text') #doctest:+ELLIPSIS
+      <Element <function Comment at ...
+
+      >>> letree.Element(u'testtag')
+      <Element...
+
+      >>> letree.ElementTree() #doctest:+ELLIPSIS
+      <xml.etree.ElementTree.ElementTree instance at ...
+
+      >>> letree.XML(u'<p>some text</p>')
+      <Element p ...
+
+      >>> letree.fromstring(u'<p>some text</p>')
+      <Element p ...
+
+      >>> elem = letree.Element(u'testtag')
+      >>> letree.iselement(elem)
+      1
+
+      >>> f = StringIO('<b>Test Source String</b>')
+      >>> letree.parse(f) #doctest:+ELLIPSIS
+      <xml.etree.ElementTree.ElementTree instance at ...
+
+      >>> letree.QName('http://example.namespace.org', 'test')#doctest:+ELLIPSIS
+      <xml.etree.ElementTree.QName instance at...
+
+      >>> print letree.tostring(elem, 'ascii')
+      <?xml version='1.0' encoding='ascii'?>
+      <testtag />
+
+      >>> letree.TreeBuilder()
+      Traceback (most recent call last):
+      ...
+      NotImplementedError: lxml doesn't implement TreeBuilder
+
+      >>> subel = letree.SubElement(elem, 'foo')
+      >>> letree.tostring(elem)
+      '<testtag><foo /></testtag>'
+
+      >>> letree.PI('sometarget')  #doctest:+ELLIPSIS
+      <Element <function ProcessingInstruction at ...
+
+      >>> letree.ProcessingInstruction('sometarget') #doctest:+ELLIPSIS
+      <Element <function ProcessingInstruction at ...
+
+      >>> letree.XMLTreeBuilder()
+      <xml.etree.ElementTree.XMLTreeBuilder instance at ...
+
+    """
+    implements(IEtree)
+
+    def __init__(self):
+        from xml.etree import ElementTree
+        self.etree = ElementTree
+
+    def XMLTreeBuilder(self, html = 0, target = None):
+        return self.etree.XMLTreeBuilder(html, target)
+
+    def PI(self, target, text = None):
+        return self.etree.PI(target, text)
+
+    ProcessingInstruction = PI
+
+
+class LxmlEtree(BaseEtree):
+    """
+    Support for lxml.
+
+      >>> from cStringIO import StringIO
+      >>> from zope.interface.verify import verifyObject
+      >>> letree = LxmlEtree()
+      >>> verifyObject(IEtree, letree)
+      True
+
+      >>> letree.Comment(u'some text')
+      <Comment[some text]>
+
+      >>> letree.Element(u'testtag')
+      <Element...
+
+      >>> letree.ElementTree()
+      <etree._ElementTree...
+
+      >>> letree.XML(u'<p>some text</p>')
+      <Element p ...
+
+      >>> letree.fromstring(u'<p>some text</p>')
+      <Element p ...
+
+    When we have a element whoes namespace declaration is declared in a parent
+    element lxml doesn't print out the namespace declaration by default.
+
+      >>> multinselemstr = '<D:prop xmlns:D="DAV:"><D:owner><H:href xmlns:H="examplens">http://example.org</H:href></D:owner></D:prop>'
+      >>> multinselem = letree.fromstring(multinselemstr)
+      >>> letree.tostring(multinselem[0])
+      '<D:owner xmlns:D="DAV:"><H:href xmlns:H="examplens">http://example.org</H:href></D:owner>'
+
+      >>> elem = letree.Element(u'testtag')
+      >>> letree.iselement(elem)
+      1
+
+      >>> f = StringIO('<b>Test Source String</b>')
+      >>> letree.parse(f)
+      <etree._ElementTree object at ...
+
+      >>> letree.QName('http://example.namespace.org', 'test')
+      <etree.QName object at...
+
+      >>> letree.tostring(elem, 'ascii')
+      '<testtag/>'
+
+      >>> letree.TreeBuilder()
+      Traceback (most recent call last):
+      ...
+      NotImplementedError: lxml doesn't implement TreeBuilder
+
+      >>> subel = letree.SubElement(elem, 'foo')
+      >>> subel.getparent() is elem
+      True
+
+      >>> letree.PI('sometarget')
+      Traceback (most recent call last):
+      ...
+      NotImplementedError: lxml doesn't implement PI
+
+      >>> letree.ProcessingInstruction('sometarget')
+      Traceback (most recent call last):
+      ...
+      NotImplementedError: lxml doesn't implement PI
+
+      >>> letree.XMLTreeBuilder()
+      Traceback (most recent call last):
+      ...
+      NotImplementedError: lxml doesn't implement XMLTreeBuilder
+
+    """
+    implements(IEtree)
+
+    def __init__(self):
+        from lxml import etree
+        self.etree = etree
+
+    def tostring(self, element, encoding = None):
+        """LXML loses the namespace information whenever we print out an
+        element who namespace was defined in 
+        """
+        return self.etree.tostring(copy.copy(element), encoding)


Property changes on: zope.webdav/trunk/src/zope/webdav/zetree.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/zope.webdav-configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/zope.webdav-configure.zcml	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/zope.webdav-configure.zcml	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1 @@
+<include package="zope.webdav" />


Property changes on: zope.webdav/trunk/src/zope/webdav/zope.webdav-configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.webdav/trunk/src/zope/webdav/zope.webdav-ftesting.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/zope.webdav-ftesting.zcml	2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/zope.webdav-ftesting.zcml	2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1 @@
+<include package="zope.webdav" file="ftesting.zcml" />


Property changes on: zope.webdav/trunk/src/zope/webdav/zope.webdav-ftesting.zcml
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Checkins mailing list