[CalendarServer-changes] [4893] CalendarServer/branches/users/cdaboo/webdav-extensions-4888

source_changes at macosforge.org source_changes at macosforge.org
Mon Jan 4 12:31:58 PST 2010


Revision: 4893
          http://trac.macosforge.org/projects/calendarserver/changeset/4893
Author:   cdaboo at apple.com
Date:     2010-01-04 12:31:57 -0800 (Mon, 04 Jan 2010)
Log Message:
-----------
Sync REPORT support. add-member support.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twext/web2/dav/__init__.py
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twext/web2/dav/davxml.py
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/index.py
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/__init__.py
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/delete_common.py
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/report_common.py
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/resource.py
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/static.py
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/test/test_index.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/doc/RFC/draft-daboo-webdav-sync.txt
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/doc/RFC/draft-reschke-webdav-post.txt
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/post.py
    CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/report_sync_collection.py

Added: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/doc/RFC/draft-daboo-webdav-sync.txt
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/doc/RFC/draft-daboo-webdav-sync.txt	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/doc/RFC/draft-daboo-webdav-sync.txt	2010-01-04 20:31:57 UTC (rev 4893)
@@ -0,0 +1,952 @@
+
+
+
+Network Working Group                                           C. Daboo
+Internet-Draft                                               Apple, Inc.
+Intended status: Standards Track                             A. Quillaud
+Expires: May 22, 2010                                   Sun Microsystems
+                                                       November 18, 2009
+
+
+                 Collection Synchronization for WebDAV
+                       draft-daboo-webdav-sync-02
+
+Abstract
+
+   This specification defines an extension to WebDAV that allows
+   efficient synchronization of the contents of a WebDAV collection.
+
+Editorial Note (To be removed by RFC Editor before publication)
+
+   Please send comments to the Distributed Authoring and Versioning
+   (WebDAV) working group at <mailto:w3c-dist-auth at w3.org>, which may be
+   joined by sending a message with subject "subscribe" to
+   <mailto:w3c-dist-auth-request at w3.org>.  Discussions of the WEBDAV
+   working group are archived at
+   <http://lists.w3.org/Archives/Public/w3c-dist-auth/>.
+
+Status of This Memo
+
+   This Internet-Draft is submitted to IETF in full conformance with the
+   provisions of BCP 78 and BCP 79.
+
+   Internet-Drafts are working documents of the Internet Engineering
+   Task Force (IETF), its areas, and its working groups.  Note that
+   other groups may also distribute working documents as Internet-
+   Drafts.
+
+   Internet-Drafts are draft documents valid for a maximum of six months
+   and may be updated, replaced, or obsoleted by other documents at any
+   time.  It is inappropriate to use Internet-Drafts as reference
+   material or to cite them other than as "work in progress."
+
+   The list of current Internet-Drafts can be accessed at
+   http://www.ietf.org/ietf/1id-abstracts.txt.
+
+   The list of Internet-Draft Shadow Directories can be accessed at
+   http://www.ietf.org/shadow.html.
+
+   This Internet-Draft will expire on May 22, 2010.
+
+Copyright Notice
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                  [Page 1]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+   Copyright (c) 2009 IETF Trust and the persons identified as the
+   document authors.  All rights reserved.
+
+   This document is subject to BCP 78 and the IETF Trust's Legal
+   Provisions Relating to IETF Documents
+   (http://trustee.ietf.org/license-info) in effect on the date of
+   publication of this document.  Please review these documents
+   carefully, as they describe your rights and restrictions with respect
+   to this document.  Code Components extracted from this document must
+   include Simplified BSD License text as described in Section 4.e of
+   the Trust Legal Provisions and are provided without warranty as
+   described in the BSD License.
+
+   This document may contain material from IETF Documents or IETF
+   Contributions published or made publicly available before November
+   10, 2008.  The person(s) controlling the copyright in some of this
+   material may not have granted the IETF Trust the right to allow
+   modifications of such material outside the IETF Standards Process.
+   Without obtaining an adequate license from the person(s) controlling
+   the copyright in such materials, this document may not be modified
+   outside the IETF Standards Process, and derivative works of it may
+   not be created outside the IETF Standards Process, except to format
+   it for publication as an RFC or to translate it into languages other
+   than English.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                  [Page 2]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+Table of Contents
+
+   1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  3
+   2.  Conventions Used in This Document  . . . . . . . . . . . . . .  3
+   3.  Open Issues  . . . . . . . . . . . . . . . . . . . . . . . . .  4
+   4.  WebDAV Synchronization . . . . . . . . . . . . . . . . . . . .  4
+     4.1.  Overview . . . . . . . . . . . . . . . . . . . . . . . . .  4
+     4.2.  DAV:sync-collection report . . . . . . . . . . . . . . . .  5
+     4.3.  Types of changes reported  . . . . . . . . . . . . . . . .  7
+       4.3.1.  New resource . . . . . . . . . . . . . . . . . . . . .  7
+       4.3.2.  Modified resource  . . . . . . . . . . . . . . . . . .  7
+       4.3.3.  Removed resource . . . . . . . . . . . . . . . . . . .  7
+     4.4.  Example: Initial DAV:sync-collection REPORT  . . . . . . .  8
+     4.5.  Example: DAV:sync-collection Report with token . . . . . . 10
+   5.  DAV:sync-token Property  . . . . . . . . . . . . . . . . . . . 11
+   6.  XML Element Definitions  . . . . . . . . . . . . . . . . . . . 12
+     6.1.  DAV:sync-collection XML Element  . . . . . . . . . . . . . 12
+     6.2.  DAV:sync-token XML Element . . . . . . . . . . . . . . . . 12
+     6.3.  DAV:multistatus XML Element  . . . . . . . . . . . . . . . 13
+     6.4.  DAV:sync-response XML Element  . . . . . . . . . . . . . . 13
+   7.  Security Considerations  . . . . . . . . . . . . . . . . . . . 14
+   8.  IANA Considerations  . . . . . . . . . . . . . . . . . . . . . 14
+   9.  Acknowledgments  . . . . . . . . . . . . . . . . . . . . . . . 14
+   10. References . . . . . . . . . . . . . . . . . . . . . . . . . . 14
+     10.1. Normative References . . . . . . . . . . . . . . . . . . . 14
+     10.2. Informative References . . . . . . . . . . . . . . . . . . 14
+   Appendix A.  Change History (to be removed prior to
+                publication as an RFC)  . . . . . . . . . . . . . . . 15
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                  [Page 3]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+1.  Introduction
+
+   WebDAV [RFC4918] defines the concept of 'collections' which are
+   hierarchical groupings of WebDAV resources on an HTTP [RFC2616]
+   server.  Collections can be of arbitrary size and depth (i.e.,
+   collections within collections).  WebDAV clients that cache resource
+   content need a way to synchronize that data with the server (i.e.,
+   detect what has changed and update their cache).  This can currently
+   be done using a WebDAV PROPFIND request on a collection to list all
+   members of a collection along with their DAV:getetag property values,
+   which allows the client to determine which resources were changed,
+   added or deleted.  However, this does not scale well to large
+   collections as the XML response to the PROPFIND request will grow
+   with the collection size.
+
+   This specification defines a new WebDAV report that results in the
+   server returning to the client only information about those resources
+   which have changed, are new or were deleted since a previous
+   execution of the report on the collection.
+
+   Additionally, a new property is added to collection resources that is
+   used to convey a "synchronization token" that is guaranteed to change
+   when resources within the collection have changed.
+
+2.  Conventions Used in This Document
+
+   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+   document are to be interpreted as described in [RFC2119].
+
+   This document uses XML DTD fragments ([W3C.REC-xml-20081126], Section
+   3.2) as a purely notational convention.  WebDAV request and response
+   bodies cannot be validated by a DTD due to the specific extensibility
+   rules defined in Section 17 of [RFC4918] and due to the fact that all
+   XML elements defined by this specification use the XML namespace name
+   "DAV:".  In particular:
+
+   1.  element names use the "DAV:" namespace,
+
+   2.  element ordering is irrelevant unless explicitly stated,
+
+   3.  extension elements (elements not already defined as valid child
+       elements) may be added anywhere, except when explicitly stated
+       otherwise,
+
+   4.  extension attributes (attributes not already defined as valid for
+       this element) may be added anywhere, except when explicitly
+       stated otherwise.
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                  [Page 4]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+   When an XML element type in the "DAV:" namespace is referenced in
+   this document outside of the context of an XML fragment, the string
+   "DAV:" will be prefixed to the element type.
+
+   This document inherits, and sometimes extends, DTD productions from
+   Section 14 of [RFC4918].
+
+3.  Open Issues
+
+   1.  We are extending the multistatus response element in a
+       significant way.  The DAV:sync-response differs from a regular
+       DAV:response because, in the case of a created or modified
+       resource, it contains both a DAV:status (201 for created, 200 for
+       modified) and a DAV:propstat.  The DAV:response on the other hand
+       contains either a DAV:status or a DAV:propstat but not both.
+       Shall we define a DAV:multisyncstatus response element instead of
+       overloading DAV:multistatus ?
+
+   2.  Do clients really need to be notified that a resource was created
+       versus modified ?  They should be able to figure that out by
+       looking at the state of their current cache.  If this information
+       is not necessary, the response would not need to contain a DAV:
+       status along with the DAV:propstat.  This would allow the use of
+       a regular multistatus (simply extended with a sync-token
+       element).
+
+4.  WebDAV Synchronization
+
+4.1.  Overview
+
+   One way to synchronize data between two entities is to use some form
+   of synchronization token.  The token defines the state of the data
+   being synchronized at a particular point in time.  It can then be
+   used to determine what has changed since one point in time and
+   another.
+
+   This specification defines a new WebDAV report that is used to enable
+   client-server collection synchronization based on such a token.
+
+   In order to synchronize the contents of a collection between a server
+   and client, the server provides the client with a synchronization
+   token each time the synchronization report is executed.  That token
+   represents the state of the data being synchronized at that point in
+   time.  The client can then present that same token back to the server
+   at some later time and the server will return only those items that
+   are new, have changed or were deleted since that token was generated.
+   The server also returns a new token representing the new state at the
+   time the report was run.
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                  [Page 5]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+   Typically the first time a client connects to the server it will need
+   to be informed of the entire state of the collection (i.e., a full
+   list of all resources that are currently contained in the
+   collection).  That is done by the client sending an empty token value
+   to the server.  This indicates to the server that a full listing is
+   required.  As an alternative, the client may choose to do its first
+   synchronization using some other mechanism on the collection (e.g.
+   some other form of batch resource information retrieval such as
+   PROPFIND, SEARCH [RFC5323], or specialized REPORTs such as those
+   defined in CalDAV [RFC4791] and CardDAV [I-D.ietf-vcarddav-carddav])
+   and ask for the DAV:sync-token property to be returned.  This
+   property (defined in Section 5) contains the same token that can be
+   used later on to issue a DAV:sync-collection report.
+
+   In some cases a server may only wish to maintain a limited amount of
+   history about changes to a collection.  In that situation it will
+   return an error to the client when the client presents a token that
+   is "out of date".  At that point the client has to fall back to
+   synchronizing the entire collection by re-running the report request
+   using an empty token value.  ACL changes may also cause a token to
+   become invalid.
+
+4.2.  DAV:sync-collection report
+
+   This specification defines the DAV:sync-collection report.
+
+   If this report is implemented by a WebDAV server, then the server
+   MUST list the report in the "DAV:supported-report-set" property on
+   any collection supporting synchronization.
+
+   To implement the behavior for this report a server needs to keep
+   track of changes to any resources in a collection.  This includes
+   noting the addition of new resources, changes to existing resources
+   and removal of resources (where "removal" could be the result of a
+   DELETE or MOVE WebDAV request).  Only internal members of the
+   collection (as defined in [RFC4918]) are to be considered.  The
+   server will track each change and provide a synchronization "token"
+   to the client that describes the state of the server at a specific
+   point in time.  This "token" is returned as part of the response to
+   the "sync-collection" report.  Clients include the last token they
+   got from the server in the next "sync-collection" report that they
+   execute and the server provides the changes from the previous state,
+   represented by the token, to the current state, represented by the
+   new token returned.
+
+   The synchronization token itself is an "opaque" string - i.e., the
+   actual string data has no specific meaning or syntax.  A simple
+   implementation of such a token would be a numeric counter that counts
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                  [Page 6]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+   each change as it occurs and relates that change to the specific
+   object that changed.
+
+   Marshalling:
+
+      The request URI MUST be a collection.  The request body MUST be a
+      DAV:sync-collection XML element (see Section 6.1), which MUST
+      contain one DAV:sync-token XML element, and optionally a DAV:
+      propstat XML element.
+
+      The request MUST include a Depth header with a value of "1".
+
+      The response body for a successful request MUST be a DAV:
+      multistatus XML element, which MUST contain one DAV:sync-token
+      element in addition to any DAV:sync-response elements.
+
+      The response body for a successful DAV:sync-collection report
+      request MUST contain a DAV:sync-response element for each resource
+      that was created, has changed or been deleted since the last
+      synchronization operation as specified by the DAV:sync-token
+      provided in the request.  A given resource MUST appear only once
+      in the response.
+
+      The DAV:status element in each DAV:sync-response element is used
+      to indicate how the resource may have changed:
+
+         A status code of '201 Created' is used to indicate resources
+         that are new.
+
+         A status code of '200 OK' is used to indicate resources that
+         have changed.
+
+         A status code of '404 Not Found' is used to indicate resources
+         that have been removed.
+
+      The conditions under which each type of change may occur is
+      further described in Section 4.3.
+
+   Preconditions:
+
+      (DAV:valid-sync-token): The DAV:sync-token element value MUST map
+      to a valid token previously returned by the server.  A token may
+      become invalid as the result of being "out of date" (out of the
+      range of change history maintained by the server), or for other
+      reasons (e.g. collection deleted, then recreated, ACL changes,
+      etc...).
+
+   Postconditions:
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                  [Page 7]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+      None.
+
+4.3.  Types of changes reported
+
+   Three types of resource state changes can be returned by the DAV:
+   sync-collection report (new, modified, removed).  This section
+   further defines under which condition each of them shall be used.  It
+   also clarifies the case where a resource may have undergone multiple
+   changes in between two synchronizations.
+
+4.3.1.  New resource
+
+   A resource MUST be reported as new if it has been mapped directly
+   under the target collection since the request sync token was
+   generated.  This includes resources that have been mapped as the
+   result of a COPY, MOVE or BIND ([I-D.ietf-webdav-bind]) operation.
+   This also includes collection resources that have been created.
+
+   In the case where a mapping between a resource and the target
+   collection was removed, then a new mapping with the same URI created,
+   the new resource MUST be reported as new, while the old resource MUST
+   NOT be reported as removed.  For example, if a resource was deleted,
+   then recreated using the same URI, it should be reported as a new
+   resource only.
+
+   A resource MAY be reported as new if the user issuing the request was
+   granted access to this resource, due to access control changes.
+
+4.3.2.  Modified resource
+
+   A resource MUST be reported as modified if it is not reported as new
+   and if its entity tag value (defined in [RFC2616]) has changed since
+   the request sync token was generated.  In other words, the new
+   resource change indicator takes precedence over the modified resource
+   change indicator.
+
+   Collection resources MUST NOT be returned as modified.  Instead
+   clients are expected to synchronize changes in child collection
+   resources on an individual basis.
+
+4.3.3.  Removed resource
+
+   A resource MUST be reported as removed if its mapping under the
+   target collection has been removed since the request sync token was
+   generated, and it has not been re-mapped since it was removed.  This
+   includes resources that have been unmapped as the result of a MOVE or
+   UNBIND ([I-D.ietf-webdav-bind]) operation.  This also includes
+   collection resources that have been removed.
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                  [Page 8]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+   If a resource was created (and possibly modified), then removed in
+   between two synchronizations, it MUST NOT be reported as new,
+   modified or removed.
+
+   A resource MAY be reported as removed if the user issuing the request
+   has no longer access to this resource, due to access control changes.
+
+4.4.  Example: Initial DAV:sync-collection REPORT
+
+   In this example, the client is making its first synchronization
+   request to the server, so the DAV:sync-token element in the request
+   is empty.  It also asks for the DAV:getetag property.  The server
+   responds with the items currently in the targeted collection
+   (indicating that they are 'new' via the '201 Created' status code).
+   The current synchronization token is also returned.
+
+   >> Request <<
+
+
+   REPORT /home/cyrusdaboo/ HTTP/1.1
+   Host: webdav.example.com
+   Depth: 1
+   Content-Type: text/xml; charset="utf-8"
+   Content-Length: xxxx
+
+   <?xml version="1.0" encoding="utf-8" ?>
+   <D:sync-collection xmlns:D="DAV:">
+     <D:sync-token/>
+     <D:prop>
+       <D:getetag/>
+     </D:prop>
+   </D:sync-collection>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                  [Page 9]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+   >> Response <<
+
+
+   HTTP/1.1 207 Multi-Status
+   Content-Type: text/xml; charset="utf-8"
+   Content-Length: xxxx
+
+   <?xml version="1.0" encoding="utf-8" ?>
+   <D:multistatus xmlns:D="DAV:">
+     <D:sync-response>
+     <D:href
+   >http://webdav.example.com/home/cyrusdaboo/test.doc</D:href>
+     <D:status>HTTP/1.1 201 Created</D:status>
+     <D:propstat>
+       <D:prop>
+         <D:getetag>"00001-abcd1"</D:getetag>
+       </D:prop>
+       <D:status>HTTP/1.1 200 OK</D:status>
+     </D:propstat>
+     </D:sync-response>
+     <D:sync-response>
+     <D:href
+   >http://webdav.example.com/home/cyrusdaboo/vcard.vcf</D:href>
+     <D:status>HTTP/1.1 201 Created</D:status>
+     <D:propstat>
+       <D:prop>
+         <D:getetag>"00002-abcd1"</D:getetag>
+       </D:prop>
+       <D:status>HTTP/1.1 200 OK</D:status>
+     </D:propstat>
+     </D:sync-response>
+     <D:sync-response>
+     <D:href
+   >http://webdav.example.com/home/cyrusdaboo/calendar.ics</D:href>
+     <D:status>HTTP/1.1 201 Created</D:status>
+     <D:propstat>
+       <D:prop>
+         <D:getetag>"00003-abcd1"</D:getetag>
+       </D:prop>
+       <D:status>HTTP/1.1 200 OK</D:status>
+     </D:propstat>
+     </D:sync-response>
+     <D:sync-token>1234</D:sync-token>
+   </D:multistatus>
+
+
+
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                 [Page 10]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+4.5.  Example: DAV:sync-collection Report with token
+
+   In this example, the client is making a synchronization request to
+   the server and is using the DAV:sync-token element returned from the
+   last report it ran on this collection.  The server responds listing
+   the items that have been added, changed or removed.  The (new)
+   current synchronization token is also returned.
+
+   >> Request <<
+
+
+   REPORT /home/cyrusdaboo/ HTTP/1.1
+   Host: webdav.example.com
+   Depth: 1
+   Content-Type: text/xml; charset="utf-8"
+   Content-Length: xxxx
+
+   <?xml version="1.0" encoding="utf-8" ?>
+   <D:sync-collection xmlns:D="DAV:">
+     <D:sync-token>1234</D:sync-token>
+     <D:prop>
+       <D:getetag/>
+     </D:prop>
+   </D:sync-collection>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                 [Page 11]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+   >> Response <<
+
+
+   HTTP/1.1 207 Multi-Status
+   Content-Type: text/xml; charset="utf-8"
+   Content-Length: xxxx
+
+   <?xml version="1.0" encoding="utf-8" ?>
+   <D:multistatus xmlns:D="DAV:">
+     <D:sync-response>
+     <D:href
+   >http://webdav.example.com/home/cyrusdaboo/file.xml</D:href>
+     <D:status>HTTP/1.1 201 Created</D:status>
+     <D:propstat>
+       <D:prop>
+         <D:getetag>"00004-abcd1"</D:getetag>
+       </D:prop>
+       <D:status>HTTP/1.1 200 OK</D:status>
+     </D:propstat>
+     </D:sync-response>
+     <D:sync-response>
+     <D:href
+   >http://webdav.example.com/home/cyrusdaboo/vcard.vcf</D:href>
+     <D:status>HTTP/1.1 200 OK</D:status>
+     <D:propstat>
+       <D:prop>
+         <D:getetag>"00002-abcd2"</D:getetag>
+       </D:prop>
+       <D:status>HTTP/1.1 200 OK</D:status>
+     </D:propstat>
+     </D:sync-response>
+     <D:sync-response>
+     <D:href
+   >http://webdav.example.com/home/cyrusdaboo/test.doc</D:href>
+     <D:status>HTTP/1.1 404 Not Found</D:status>
+     </D:sync-response>
+     <D:sync-token>1238</D:sync-token>
+   </D:multistatus>
+
+
+5.  DAV:sync-token Property
+
+   Name:  sync-token
+
+   Namespace:  DAV:
+
+
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                 [Page 12]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+   Purpose:  Contains the value of the synchronization token as it would
+      be returned by a DAV:sync-collection report.
+
+   Value:  Any text.
+
+   Protected:  MUST be protected because this value is created and
+      controlled by the server.
+
+   COPY/MOVE behavior:  This property value is dependent on the final
+      state of the destination resource, not the value of the property
+      on the source resource.
+
+   Description:  The DAV:sync-token property MUST be defined on all
+      resources that support the DAV:sync-collection report.  It
+      contains the value of the synchronization token as it would be
+      returned by a DAV:sync-collection report on that resource at the
+      same point in time.  It SHOULD NOT be returned by a PROPFIND DAV:
+      allprop request (as defined in [RFC4918]).
+
+   Definition:
+
+
+   <!ELEMENT sync-token #PCDATA>
+
+
+6.  XML Element Definitions
+
+6.1.  DAV:sync-collection XML Element
+
+   Name:  sync-collection
+
+   Namespace:  DAV:
+
+   Purpose:  WebDAV report used to synchronize data between client and
+      server.
+
+   Description:  See Section 4.
+
+
+
+   <!ELEMENT sync-collection (sync-token, DAV:prop?)>
+
+
+6.2.  DAV:sync-token XML Element
+
+
+
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                 [Page 13]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+   Name:  sync-token
+
+   Namespace:  DAV:
+
+   Purpose:  The synchronization token provided by the server and
+      returned by the client.
+
+   Description:  See Section 4.
+
+
+
+   <!ELEMENT sync-token CDATA>
+
+
+6.3.  DAV:multistatus XML Element
+
+   Name:  multistatus
+
+   Namespace:  DAV:
+
+   Purpose:  Extends the DAV:multistatus element to include
+      synchronization details.
+
+   Description:  See Section 4.
+
+
+
+   <!ELEMENT multistatus ((DAV:response*, DAV:responsedescription?) |
+                (DAV:sync-response*, DAV:sync-token,
+                 DAV:responsedescription?))>
+
+
+6.4.  DAV:sync-response XML Element
+
+   Name:  sync-response
+
+   Namespace:  DAV:
+
+   Purpose:  Contains the synchronization results returned by the
+      server.
+
+   Description:  See Section 4.
+
+
+
+   <!ELEMENT sync-response (DAV:href, DAV:status, DAV:propstat?)>
+
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                 [Page 14]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+7.  Security Considerations
+
+   This extension does not introduce any new security concerns than
+   those already described in HTTP and WebDAV.
+
+8.  IANA Considerations
+
+   This document does not require any actions on the part of IANA.
+
+9.  Acknowledgments
+
+   The following individuals contributed their ideas and support for
+   writing this specification: Bernard Desruisseaux, Mike Douglass, Ciny
+   Joy and Julian Reschke.
+
+10.  References
+
+10.1.  Normative References
+
+   [RFC2119]                    Bradner, S., "Key words for use in RFCs
+                                to Indicate Requirement Levels", BCP 14,
+                                RFC 2119, March 1997.
+
+   [RFC2616]                    Fielding, R., Gettys, J., Mogul, J.,
+                                Frystyk, H., Masinter, L., Leach, P.,
+                                and T. Berners-Lee, "Hypertext Transfer
+                                Protocol -- HTTP/1.1", RFC 2616,
+                                June 1999.
+
+   [RFC4918]                    Dusseault, L., "HTTP Extensions for Web
+                                Distributed Authoring and Versioning
+                                (WebDAV)", RFC 4918, June 2007.
+
+   [W3C.REC-xml-20081126]       Sperberg-McQueen, C., Yergeau, F., Bray,
+                                T., Paoli, J., and E. Maler, "Extensible
+                                Markup Language (XML) 1.0 (Fifth
+                                Edition)", World Wide Web Consortium
+                                Recommendation REC-xml-20081126,
+                                November 2008, <http://www.w3.org/TR/
+                                2008/REC-xml-20081126>.
+
+10.2.  Informative References
+
+   [I-D.ietf-vcarddav-carddav]  Daboo, C., "vCard Extensions to WebDAV
+                                (CardDAV)",
+                                draft-ietf-vcarddav-carddav-10 (work in
+                                progress), November 2009.
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                 [Page 15]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+   [I-D.ietf-webdav-bind]       Clemm, G., Crawford, J., Reschke, J.,
+                                and J. Whitehead, "Binding Extensions to
+                                Web Distributed Authoring and Versioning
+                                (WebDAV)", draft-ietf-webdav-bind-26
+                                (work in progress), September 2009.
+
+   [RFC4791]                    Daboo, C., Desruisseaux, B., and L.
+                                Dusseault, "Calendaring Extensions to
+                                WebDAV (CalDAV)", RFC 4791, March 2007.
+
+   [RFC5323]                    Reschke, J., Reddy, S., Davis, J., and
+                                A. Babich, "Web Distributed Authoring
+                                and Versioning (WebDAV) SEARCH",
+                                RFC 5323, November 2008.
+
+Appendix A.  Change History (to be removed prior to publication as an
+             RFC)
+
+   Changes in -02:
+
+   1.  Added definition of sync-token WebDAV property.
+
+   2.  Added references to SEARCH, CalDAV, CardDAV as alternative ways
+       to first synchronize a collection.
+
+   3.  Added section defining under which condition each state change
+       (new, modified, removed) should be reported.  Added reference to
+       BIND.
+
+   4.  Incorporated feedback from Julian Reschke and Ciny Joy.
+
+   5.  More details on the use of the DAV:valid-sync-token precondition.
+
+   Changes in -01:
+
+   1.  Updated to 4918 reference.
+
+   2.  Fixed examples to properly include DAV:status in DAV:propstat
+
+   3.  Switch to using XML conventions text from RFC5323.
+
+
+
+
+
+
+
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                 [Page 16]
+
+Internet-Draft                 WebDAV Sync                 November 2009
+
+
+Authors' Addresses
+
+   Cyrus Daboo
+   Apple Inc.
+   1 Infinite Loop
+   Cupertino, CA  95014
+   USA
+
+   EMail: cyrus at daboo.name
+   URI:   http://www.apple.com/
+
+
+   Arnaud Quillaud
+   Sun Microsystems
+   180, Avenue de l'Europe
+   Saint Ismier cedex,   38334
+   France
+
+   EMail: arnaud.quillaud at sun.com
+   URI:   http://www.sun.com/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo & Quillaud          Expires May 22, 2010                 [Page 17]
+
\ No newline at end of file

Added: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/doc/RFC/draft-reschke-webdav-post.txt
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/doc/RFC/draft-reschke-webdav-post.txt	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/doc/RFC/draft-reschke-webdav-post.txt	2010-01-04 20:31:57 UTC (rev 4893)
@@ -0,0 +1,784 @@
+
+
+
+Network Working Group                                         J. Reschke
+Internet-Draft                                                greenbytes
+Intended status: Standards Track                       November 22, 2009
+Expires: May 26, 2010
+
+
+ Using POST to add Members to Web Distributed Authoring and Versioning
+                          (WebDAV) Collections
+                      draft-reschke-webdav-post-05
+
+Abstract
+
+   The Hypertext Transfer Protocol (HTTP) Extensions for the Web
+   Distributed Authoring and Versioning (WebDAV) do not define the
+   behavior for the "POST" method when applied to collections, as the
+   base specification (HTTP) leaves implementers lots of freedom for the
+   semantics of "POST".
+
+   This has led to a situation where many WebDAV servers do not
+   implement POST for collections at all, although it is well suited to
+   be used for the purpose of adding new members to a collection, where
+   the server remains in control of the newly assigned URL.  As a matter
+   of fact, the Atom Publishing Protocol (AtomPub) uses POST exactly for
+   that purpose.  On the other hand, WebDAV-based protocols such as the
+   Calendar Extensions to WebDAV (CalDAV) frequently require clients to
+   pick a unique URL, although the server could easily perform that
+   task.
+
+   This specification defines a discovery mechanism through which
+   servers can advertise support for POST requests with the
+   aforementioned "add collection member" semantics.
+
+Editorial Note (To be removed by RFC Editor before publication)
+
+   Please send comments to the Distributed Authoring and Versioning
+   (WebDAV) working group at <mailto:w3c-dist-auth at w3.org>, which may be
+   joined by sending a message with subject "subscribe" to
+   <mailto:w3c-dist-auth-request at w3.org>.  Discussions of the WEBDAV
+   working group are archived at
+   <http://lists.w3.org/Archives/Public/w3c-dist-auth/>.
+
+   Note that although discussion takes place on the WebDAV working
+   group's mailing list, this is not a working group document.
+
+   XML versions, latest edits and the issues list for this document are
+   available from
+   <http://greenbytes.de/tech/webdav/#draft-reschke-webdav-post>.
+
+
+
+
+Reschke                   Expires May 26, 2010                  [Page 1]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+Status of this Memo
+
+   This Internet-Draft is submitted to IETF in full conformance with the
+   provisions of BCP 78 and BCP 79.
+
+   Internet-Drafts are working documents of the Internet Engineering
+   Task Force (IETF), its areas, and its working groups.  Note that
+   other groups may also distribute working documents as Internet-
+   Drafts.
+
+   Internet-Drafts are draft documents valid for a maximum of six months
+   and may be updated, replaced, or obsoleted by other documents at any
+   time.  It is inappropriate to use Internet-Drafts as reference
+   material or to cite them other than as "work in progress."
+
+   The list of current Internet-Drafts can be accessed at
+   http://www.ietf.org/ietf/1id-abstracts.txt.
+
+   The list of Internet-Draft Shadow Directories can be accessed at
+   http://www.ietf.org/shadow.html.
+
+   This Internet-Draft will expire on May 26, 2010.
+
+Copyright Notice
+
+   Copyright (c) 2009 IETF Trust and the persons identified as the
+   document authors.  All rights reserved.
+
+   This document is subject to BCP 78 and the IETF Trust's Legal
+   Provisions Relating to IETF Documents
+   (http://trustee.ietf.org/license-info) in effect on the date of
+   publication of this document.  Please review these documents
+   carefully, as they describe your rights and restrictions with respect
+   to this document.  Code Components extracted from this document must
+   include Simplified BSD License text as described in Section 4.e of
+   the Trust Legal Provisions and are provided without warranty as
+   described in the BSD License.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Reschke                   Expires May 26, 2010                  [Page 2]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+Table of Contents
+
+   1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  4
+   2.  Terminology  . . . . . . . . . . . . . . . . . . . . . . . . .  5
+   3.  Protocol Extension . . . . . . . . . . . . . . . . . . . . . .  6
+     3.1.  Definition of 'Add-Member' URI . . . . . . . . . . . . . .  6
+     3.2.  Discovery  . . . . . . . . . . . . . . . . . . . . . . . .  7
+       3.2.1.  DAV:add-member Property (protected)  . . . . . . . . .  7
+       3.2.2.  Example  . . . . . . . . . . . . . . . . . . . . . . .  7
+     3.3.  Relation to AtomPub's 'Slug' Header Field  . . . . . . . .  8
+     3.4.  Example Operation  . . . . . . . . . . . . . . . . . . . .  9
+   4.  Additional Semantics for existing Methods  . . . . . . . . . .  9
+     4.1.  Additional Preconditions . . . . . . . . . . . . . . . . .  9
+     4.2.  Example: Failed PUT Request  . . . . . . . . . . . . . . . 10
+   5.  Relationship to WebDAV Access Control Protocol . . . . . . . . 10
+   6.  Internationalization Considerations  . . . . . . . . . . . . . 10
+   7.  IANA Considerations  . . . . . . . . . . . . . . . . . . . . . 11
+   8.  Security Considerations  . . . . . . . . . . . . . . . . . . . 11
+   9.  Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 11
+   10. References . . . . . . . . . . . . . . . . . . . . . . . . . . 11
+     10.1. Normative References . . . . . . . . . . . . . . . . . . . 11
+     10.2. Informative References . . . . . . . . . . . . . . . . . . 12
+   Appendix A.  Change Log (to be removed by RFC Editor before
+                publication)  . . . . . . . . . . . . . . . . . . . . 12
+     A.1.  since draft-reschke-webdav-post-00 . . . . . . . . . . . . 12
+     A.2.  since draft-reschke-webdav-post-01 . . . . . . . . . . . . 13
+     A.3.  since draft-reschke-webdav-post-02 . . . . . . . . . . . . 13
+     A.4.  since draft-reschke-webdav-post-03 . . . . . . . . . . . . 13
+     A.5.  since draft-reschke-webdav-post-04 . . . . . . . . . . . . 13
+   Appendix B.  Open issues (to be removed by RFC Editor prior to
+                publication)  . . . . . . . . . . . . . . . . . . . . 13
+     B.1.  edit . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
+     B.2.  collection . . . . . . . . . . . . . . . . . . . . . . . . 13
+   Index  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
+   Author's Address . . . . . . . . . . . . . . . . . . . . . . . . . 14
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Reschke                   Expires May 26, 2010                  [Page 3]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+1.  Introduction
+
+   The Hypertext Transfer Protocol (HTTP) Extensions for the Web
+   Distributed Authoring and Versioning (WebDAV) ([RFC4918], Section
+   9.5) do not define the behavior for the "POST" method when applied to
+   collections, as the base specification (HTTP) leaves implementers
+   lots of freedom for the semantics of "POST":
+
+      9.5 POST for Collections
+
+      Since by definition the actual function performed by POST is
+      determined by the server and often depends on the particular
+      resource, the behavior of POST when applied to collections cannot
+      be meaningfully modified because it is largely undefined.  Thus,
+      the semantics of POST are unmodified when applied to a collection.
+
+   This has led to a situation where many WebDAV servers do not
+   implement POST for collections at all, although it is well suited to
+   be used for the purpose of adding new members to a collection, where
+   the server remains in control of the newly assigned URL.  As a matter
+   of fact, the Atom Publishing Protocol (AtomPub) uses POST exactly for
+   that purpose ([RFC5023], Section 9.2):
+
+      9.2 Creating Resources with POST
+
+      To add members to a Collection, clients send POST requests to the
+      URI of the Collection.
+
+   On the other hand, WebDAV-based protocols such as Calendaring
+   Extensions to WebDAV (CalDAV) frequently require clients to pick a
+   unique URL, although the server could easily perform that task
+   ([RFC4791], Section 5.3.2):
+
+      5.3.2 Creating Calendar Object Resources
+
+      ...
+
+      When servers create new resources, it's not hard for the server to
+      choose an unmapped URI.  It's slightly tougher for clients,
+      because a client might not want to examine all resources in the
+      collection and might not want to lock the entire collection to
+      ensure that a new resource isn't created with a name collision.
+      (...)
+
+   As a matter of fact, letting the server choose the member URI not
+   only is a simplification for certain types of clients, but can also
+   reduce the complexity of the server (in that it doesn't need to
+   persist an additional client-supplied identifier where it already has
+
+
+
+Reschke                   Expires May 26, 2010                  [Page 4]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+   an internal one like a UUID or a primary key).
+
+      Note: the vCard Extensions to WebDAV (CardDAV)
+      ([draft-ietf-vcarddav-carddav]) suffer from the same issue, and
+      may be able to take advantage of this specification.
+
+   This specification defines a discovery mechanism through which
+   servers can advertise support for POST requests with the
+   aforementioned "add collection member" semantics.
+
+   Note that this specification deliberately only adresses the use case
+   of creating new non-collection resources, and that it was not a goal
+   to supply the same functionality for creating collection resources
+   (MKCOL), or for other operations that require the client to specify a
+   new URL (LOCK, MOVE, or COPY).
+
+      Note: the author previously proposed a new HTTP method for exactly
+      this purpose ([draft-reschke-http-addmember]), but quite a few
+      reviewers pointed out that this would duplicate the original
+      semantics of POST.  Thus this proposal that avoids adding a new
+      HTTP method is made.
+
+
+2.  Terminology
+
+   The terminology used here follows that in the WebDAV specification
+   ([RFC4918]).
+
+   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+   document are to be interpreted as described in [RFC2119].
+
+   This document uses XML DTD fragments ([XML]) as a purely notational
+   convention.  In particular:
+
+   o  Element ordering is irrelevant.
+
+   o  Extension elements/attributes (elements/attributes not already
+      defined as valid child elements) may be added anywhere, except
+      when explicitly stated otherwise.
+
+      Note: this specification defines new properties and precondition
+      names in the "DAV:" namespace, which the WebDAV specification
+      reserves for use by the IETF ([RFC4918], Section 21.1).  However,
+      there was rough consensus in the WebDAV community that the
+      specification is of general applicability to other WebDAV related
+      standards efforts, and thus deserves inclusion into the base
+      namespace.
+
+
+
+Reschke                   Expires May 26, 2010                  [Page 5]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+3.  Protocol Extension
+
+   Due to the reasons stated in Section 1, clients can not rely on a
+   specific server behavior when POST is applied to a collection.  This
+   problem is addressed by this specification by allowing servers to
+   advertise a URI that has the desired "add member" semantics.
+
+   Note that servers that already use POST for a different purpose can
+   just expose a separate URI.  Other servers can just advertise the
+   collection's own URI, thus avoiding minting another URI for a limited
+   purpose.
+
+3.1.  Definition of 'Add-Member' URI
+
+   The "Add-Member" URI of a WebDAV collection is a URI that will accept
+   HTTP POST requests, and will interpret these as requests to store the
+   enclosed entity as a new internal member of the collection (see
+   Section 3 of [RFC4918] for the definition of "internal member").  It
+   MUST identify a resource on the same server as the WebDAV collection
+   (the host and port components ([RFC2616], Section 3.2.2) of the URIs
+   must match).
+
+   If there are pre-conditions related to creating a resource in the
+   collection using a PUT request, then those same pre-conditions apply
+   to the new POST request behavior, and the same HTTP response body
+   will returned on failure.
+
+   The URI of the newly created resource is returned in the HTTP
+   Location response header field ([RFC2616], Section 14.30).
+
+      Note: the fact that a server advertises an "Add-Member" URI does
+      not imply any special semantics of the collection itself.  For
+      instance, member URIs assigned by the server are not necessarily
+      unique over time (a member URI may be assigned again to a new
+      resource when it previously was removed).
+
+      Note: the "Add-Member" URI can be the identical to the
+      collection's URI (in which case the server just advertises the
+      fact that POST to the WebDAV collection's URI is supported as
+      defined within this specification).  But it can also be different
+      from it, in which case it doesn't need to have any relation to the
+      collection's URI.
+
+      Given a collection URI of
+
+      /docs/collection/
+
+
+
+
+
+Reschke                   Expires May 26, 2010                  [Page 6]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+      all of the URIs below might occur as "Add-Member" URIs:
+
+      /docs/collection/
+      /docs/collection/;post
+      /docs/collection;post/
+      /docs/collection/&post
+      /post-service?path=/collection/
+
+      The remainder of the document uses the same format just for
+      reasons of consistency; any other HTTP URI on the same server
+      would do as well.
+
+3.2.  Discovery
+
+3.2.1.  DAV:add-member Property (protected)
+
+   DAV:add-member is a protected property (see [RFC4918], Section 15)
+   defined on WebDAV collections, and contains the "Add-Member" URI for
+   that collection (embedded inside a DAV:href element).
+
+   <!ELEMENT add-member (href)>
+   <!-- href: defined in [RFC4918], Section 14.7 -->
+
+   A PROPFIND/allprop request SHOULD NOT return this property (see
+   [RFC4918], Section 9.1).  Servers MUST implement the DAV:supported-
+   live-property-set property defined in Section 3.1.4 of [RFC3253], and
+   report the property DAV:add-member as a supported live property.
+
+3.2.2.  Example
+
+   >>Request
+
+   PROPFIND /collection/ HTTP/1.1
+   Host: example.com
+   Content-Type: application/xml; charset="utf-8"
+   Content-Length: 118
+
+   <?xml version="1.0" encoding="utf-8" ?>
+   <propfind xmlns="DAV:">
+     <prop>
+       <add-member/>
+     </prop>
+   </propfind>
+
+
+
+
+
+
+
+
+Reschke                   Expires May 26, 2010                  [Page 7]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+   >>Response
+
+   HTTP/1.1 207 Multi-Status
+   Content-Type: application/xml; charset="utf-8"
+   Content-Length: 340
+
+   <?xml version="1.0" encoding="utf-8" ?>
+   <multistatus xmlns="DAV:">
+     <response>
+       <href>/collection/</href>
+       <propstat>
+         <prop>
+           <add-member>
+             <href>/collection;add-member/</href>
+           </add-member>
+         </prop>
+         <status>HTTP/1.1 200 OK</status>
+       </propstat>
+     </response>
+   </multistatus>
+
+   Note that in this case, the server has minted a separate URI for the
+   purpose of adding new content.
+
+3.3.  Relation to AtomPub's 'Slug' Header Field
+
+   In the AtomPub protocol, clients can use the entity header field
+   "Slug" to suggest parts of the URI to be created (see [RFC5023],
+   Section 9.7).  Note that servers are free to ignore this suggestion,
+   or to use whatever algorithm that makes sense to generate the new
+   URI.
+
+   The same applies to the extension defined here: clients can use the
+   "Slug" header field as by its definition of a generic HTTP header
+   field.  Servers should process it exactly in the way defined by
+   AtomPub.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Reschke                   Expires May 26, 2010                  [Page 8]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+3.4.  Example Operation
+
+   >>Request
+
+   POST /collection;add-member/ HTTP/1.1
+   Host: example.com
+   Content-Type: text/plain
+   Slug: Sample Title
+   Content-Length: 12
+
+   Sample text.
+
+   >>Response
+
+   HTTP/1.1 201 Created
+   Location: http://example.com/collection/sample%20title
+
+
+4.  Additional Semantics for existing Methods
+
+   One important use case for this specification are collections that
+   act as WebDAV collections for the purpose of read access (PROPFIND
+   Depth 1/Infinity), but which only support internal member URIs
+   assigned by the server.  These collections will not allow a client to
+   create a new member using methods like PUT, MKCOL, LOCK, COPY or
+   MOVE.  Therefore, this specification defines a new precondition name
+   ([RFC4918], Section 16) that can be used to provide the client with
+   additional information about why exactly the request failed.
+
+      Note: although the precondition defined below can be used for
+      methods other than PUT, the "Add-Member" mechanism defined by this
+      specification deliberately is restricted to PUT.
+
+4.1.  Additional Preconditions
+
+   (DAV:allow-client-defined-URI): the server allows clients to specify
+   the last path segment for newly created resources.
+
+   The precondition element MAY contain a add-member-uri XML element
+   specifying the "Add-Member" URI associated with the collection, on
+   which the creation of a new child resource was attempted:
+
+   <!ELEMENT allow-client-defined-uri (add-member?)>
+
+
+
+
+
+
+
+
+Reschke                   Expires May 26, 2010                  [Page 9]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+4.2.  Example: Failed PUT Request
+
+   In this example, the client tries to use PUT to create a new internal
+   member of /collection/.
+
+   >>Request
+
+   PUT /collection/new.txt HTTP/1.1
+   Host: example.com
+   Content-Type: text/plain
+   Content-Length: 12
+
+   Sample text.
+
+   >>Response
+
+   HTTP/1.1 405 Method Not Allowed
+   Allow: GET, HEAD, TRACE, PROPFIND, COPY, MOVE
+   Content-Type: application/xml; charset=UTF-8
+   Content-Length: 172
+
+   <error xmlns="DAV:">
+     <allow-client-defined-uri>
+       <add-member>
+         <href>/collection;add-member/</href>
+       </add-member>
+     </allow-client-defined-uri>
+   </error>
+
+   The request fails with a 405 (Method Not Allowed) status, but also
+   provides the reason, and a pointer to the "Add-Member" URI in the
+   response body.
+
+
+5.  Relationship to WebDAV Access Control Protocol
+
+   The WebDAV ACL specification requires the DAV:bind privilege to be
+   granted on a collection for the client to be able add new collection
+   members ([RFC3744], Section 3.9).  Consistent with that, a server
+   MUST reject a POST request to the Add-Member URI of a collection
+   unless the principal executing the request is granted DAV:bind
+   privilege on the associated WebDAV collection resource.
+
+
+6.  Internationalization Considerations
+
+   This document does not introduce any new internationalization
+   considerations beyond those discussed in Section 20 of [RFC4918].
+
+
+
+Reschke                   Expires May 26, 2010                 [Page 10]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+7.  IANA Considerations
+
+   This specification does not require any actions from IANA.
+
+
+8.  Security Considerations
+
+   All security considerations connected to HTTP/WebDAV and XML apply
+   for this specification as well, namely, [RFC4918] (Section 20) and
+   [RFC3470] (Section 7).
+
+   Furthermore, servers should be aware that deriving the member path
+   from the data being stored in the resource could potentially expose
+   confidential information.  This could even be the case when only a
+   hash code of the content is used.
+
+   In addition, on servers that do not support this specification, a
+   malevolent user could set the DAV:add-member URI as a custom
+   property, tricking other users to post content to an entirely
+   different URI.  Clients can protect themselves against this scenario
+   by
+
+   o  not following DAV:add-member URIs to different servers, and/or
+
+   o  verifying that the DAV:add-member property is indeed a live
+      property (this can be achieved by testing the DAV:supported-live-
+      property-set property, or by checking whether DAV:add-member is
+      returned upon PROPFIND/allprop)
+
+
+9.  Acknowledgements
+
+   This document has benefited from thoughtful discussion by Cyrus Daboo
+   and Bernard Desruissaux.
+
+
+10.  References
+
+10.1.  Normative References
+
+   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
+              Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+   [RFC2616]  Fielding, R., Gettys, J., Mogul, J., Frystyk, H.,
+              Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext
+              Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
+
+   [RFC3253]  Clemm, G., Amsden, J., Ellison, T., Kaler, C., and J.
+
+
+
+Reschke                   Expires May 26, 2010                 [Page 11]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+              Whitehead, "Versioning Extensions to WebDAV", RFC 3253,
+              March 2002.
+
+   [RFC3744]  Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web
+              Distributed Authoring and Versioning (WebDAV) Access
+              Control Protocol", RFC 3744, May 2004.
+
+   [RFC4918]  Dusseault, L., Ed., "HTTP Extensions for Web Distributed
+              Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
+
+   [XML]      Bray, T., Paoli, J., Sperberg-McQueen, C., Maler, E., and
+              F. Yergeau, "Extensible Markup Language (XML) 1.0 (Fifth
+              Edition)", W3C REC-xml-20081126, November 2008,
+              <http://www.w3.org/TR/2008/REC-xml-20081126/>.
+
+10.2.  Informative References
+
+   [RFC3470]  Hollenbeck, S., Rose, M., and L. Masinter, "Guidelines for
+              the Use of Extensible Markup Language (XML) within IETF
+              Protocols", RFC 3470, BCP 70, January 2003.
+
+   [RFC4791]  Daboo, C., Desruisseaux, B., and L. Dusseault,
+              "Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,
+              March 2007.
+
+   [RFC5023]  Gregorio, J. and B. de hOra, "The Atom Publishing
+              Protocol", RFC 5023, October 2007.
+
+   [draft-ietf-vcarddav-carddav]
+              Daboo, C., "vCard Extensions to WebDAV (CardDAV)",
+              draft-ietf-vcarddav-carddav-10 (work in progress),
+              November 2009.
+
+   [draft-reschke-http-addmember]
+              Reschke, J., "The HTTP ADDMEMBER Method",
+              draft-reschke-http-addmember-00 (work in progress),
+              February 2005.
+
+
+Appendix A.  Change Log (to be removed by RFC Editor before publication)
+
+A.1.  since draft-reschke-webdav-post-00
+
+   Added Acknowledgements.
+
+   Added and resolved issues "acl", "forbidden-put", "member-uri-
+   content", "post-error-semantics", "property-trust", "rational-server-
+   uri", ""remote-uri", "uri-format" and "uri-uniqueness".
+
+
+
+Reschke                   Expires May 26, 2010                 [Page 12]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+A.2.  since draft-reschke-webdav-post-01
+
+   Add and resolve issue "containment".
+
+   Update draft-ietf-vcarddav-carddav reference.
+
+A.3.  since draft-reschke-webdav-post-02
+
+   Update XML, draft-ietf-vcarddav-carddav and
+   draft-nottingham-http-link-header references.
+
+   Add and resolve issues "link-header" and "ns".
+
+A.4.  since draft-reschke-webdav-post-03
+
+   Add and resolve issues "put-only" and "protected".
+
+A.5.  since draft-reschke-webdav-post-04
+
+   Update vcarddav reference.  In the example, do not use the same
+   content for Slug header field and request body.  Add issue
+   "collection".
+
+
+Appendix B.  Open issues (to be removed by RFC Editor prior to
+             publication)
+
+B.1.  edit
+
+   Type: edit
+
+   julian.reschke at greenbytes.de (2008-09-22): Umbrella issue for
+   editorial fixes/enhancements.
+
+B.2.  collection
+
+   Type: change
+
+   cyrus at daboo.name (2009-11-05): I have heard from some implementors
+   that they would like collection creation to also be part of this
+   draft.  In particular, CalDAV and/or CardDAV clients creating
+   calendars or address books would prefer to leave specification of the
+   resource name to the client.
+   Proposal:
+   - Add a DAV:add-collection property containing a URI (which must be
+   different than DAV:add-member)
+   - A POST on that URI creates a collection within the parent
+   collection, with a server chosen resource name
+
+
+
+Reschke                   Expires May 26, 2010                 [Page 13]
+
+Internet-Draft      POST to add to WebDAV Collections      November 2009
+
+
+   - If the POST contains an XML body with DAV:mkcol as the root
+   element, then that body is interpreted the same way as MKCOL ext.
+
+
+Index
+
+   A
+      Add-Member URI  6
+
+   C
+      Condition Names
+         DAV:allow-client-defined-URI (pre)  9
+      COPY method
+         Additional Preconditions  9
+
+   D
+      DAV:allow-client-defined-URI  9
+
+   L
+      LOCK method
+         Additional Preconditions  9
+
+   M
+      MKCOL method
+         Additional Preconditions  9
+      MOVE method
+         Additional Preconditions  9
+
+   P
+      PUT method
+         Additional Preconditions  9
+
+
+Author's Address
+
+   Julian F. Reschke
+   greenbytes GmbH
+   Hafenweg 16
+   Muenster, NW  48155
+   Germany
+
+   Phone: +49 251 2807760
+   Email: julian.reschke at greenbytes.de
+   URI:   http://greenbytes.de/tech/webdav/
+
+
+
+
+
+
+
+Reschke                   Expires May 26, 2010                 [Page 14]
+
\ No newline at end of file

Modified: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twext/web2/dav/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twext/web2/dav/__init__.py	2009-12-23 20:11:53 UTC (rev 4892)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twext/web2/dav/__init__.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -15,5 +15,14 @@
 ##
 
 """
-Extentions to twisted.web2.dav
+Extensions to twisted.web2.dav
 """
+
+#
+# Register additional WebDAV XML elements
+#
+
+from twext.web2.dav import davxml
+import twisted.web2.dav.davxml
+
+twisted.web2.dav.davxml.registerElements(davxml)

Modified: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twext/web2/dav/davxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twext/web2/dav/davxml.py	2009-12-23 20:11:53 UTC (rev 4892)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twext/web2/dav/davxml.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -21,12 +21,16 @@
 __all__ = [
     "sname2qname",
     "qname2sname",
+    "ErrorDescription",
     "ErrorResponse",
+    "AddMember",
+    "SyncCollection",
+    "SyncToken",
 ]
 
 from twisted.web2.http import Response
 from twisted.web2.dav.http import ErrorResponse as SuperErrorResponse
-from twisted.web2.dav.davxml import twisted_dav_namespace, WebDAVTextElement
+from twisted.web2.dav.davxml import dav_namespace, twisted_dav_namespace, WebDAVElement, WebDAVTextElement
 from twisted.web2.dav.davxml import WebDAVUnknownElement, Error
 from twisted.web2.http_headers import MimeType
 
@@ -88,6 +92,7 @@
     Renders itself as a DAV:error XML document.
     """
     error = None
+    unregistered = True     # base class is already registered
 
     def __init__(self, code, error, description=None):
         """
@@ -120,3 +125,58 @@
     def __repr__(self):
         return "<%s %s %s>" % (self.__class__.__name__, self.code, self.error.sname())
 
+class AddMember (WebDAVElement):
+    """
+    A property on a collection to allow for "anonymous" creation of resources.
+    (draft-reschke-webdav-post)
+    """
+    name = "add-member"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "href"): (0, 1) }
+
+class SyncCollection (WebDAVElement):
+    """
+    DAV report used to retrieve specific calendar component items via their
+    URIs.
+    (CalDAV-access-09, section 9.9)
+    """
+    name = "sync-collection"
+
+    # To allow for an empty element in a supported-report-set property we need
+    # to relax the child restrictions
+    allowed_children = {
+        (dav_namespace, "sync-token"): (0, 1), # When used in the REPORT this is required
+        (dav_namespace, "prop"    ):   (0, 1),
+    }
+
+    def __init__(self, *children, **attributes):
+        super(SyncCollection, self).__init__(*children, **attributes)
+
+        self.property = None
+        self.sync_token = None
+
+        for child in self.children:
+            qname = child.qname()
+
+            if qname == (dav_namespace, "sync-token"):
+                
+                self.sync_token = str(child)
+
+            elif qname in (
+                (dav_namespace, "prop"    ),
+            ):
+                if property is not None:
+                    raise ValueError("Only one of DAV:prop allowed")
+                self.property = child
+
+class SyncToken (WebDAVTextElement):
+    """
+    Synchronization token used in report and as a property.
+    """
+    name = "sync-token"
+    hidden = True
+    protected = True
+
+

Modified: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/index.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/index.py	2009-12-23 20:11:53 UTC (rev 4892)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/index.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -57,7 +57,7 @@
 log = Logger()
 
 db_basename = db_prefix + "sqlite"
-schema_version = "8"
+schema_version = "9"
 collection_types = {"Calendar": "Regular Calendar Collection", "iTIP": "iTIP Calendar Collection"}
 
 icalfbtype_to_indexfbtype = {
@@ -101,6 +101,9 @@
 class IndexedSearchException(ValueError):
     pass
 
+class SyncTokenValidException(ValueError):
+    pass
+
 class AbstractCalendarIndex(AbstractSQLDatabase, LoggingMixIn):
     """
     Calendar collection index abstract base class that defines the apis for the index.
@@ -180,7 +183,7 @@
             if name is not None and self.resource.getChild(name_utf8) is None:
                 # Clean up
                 log.err("Stale resource record found for child %s with UID %s in %s" % (name, uid, self.resource))
-                self._delete_from_db(name, uid)
+                self._delete_from_db(name, uid, None)
                 self._db_commit()
             else:
                 resources.append(name)
@@ -212,7 +215,7 @@
 
         return uid
 
-    def addResource(self, name, calendar, fast=False, reCreate=False):
+    def addResource(self, name, calendar, revision, fast=False, reCreate=False):
         """
         Adding or updating an existing resource.
         To check for an update we attempt to get an existing UID
@@ -225,12 +228,12 @@
         """
         oldUID = self.resourceUIDForName(name)
         if oldUID is not None:
-            self._delete_from_db(name, oldUID)
-        self._add_to_db(name, calendar, reCreate=reCreate)
+            self._delete_from_db(name, oldUID, None)
+        self._add_to_db(name, calendar, revision, reCreate=reCreate)
         if not fast:
             self._db_commit()
 
-    def deleteResource(self, name):
+    def deleteResource(self, name, revision):
         """
         Remove this resource from the index.
         @param name: the name of the resource to add.
@@ -238,7 +241,7 @@
         """
         uid = self.resourceUIDForName(name)
         if uid is not None:
-            self._delete_from_db(name, uid)
+            self._delete_from_db(name, uid, revision)
             self._db_commit()
 
     def resourceExists(self, name):
@@ -274,6 +277,26 @@
             self.log_info("Search falls outside range of index for %s %s" % (name, minDate))
             self.reExpandResource(name, minDate)
 
+    def whatchanged(self, revision):
+
+        results = [(name.encode("utf-8"), created, wasdeleted) for name, created, wasdeleted in self._db_execute("select NAME, CREATEDREVISION, WASDELETED from REVISIONS where REVISION > :1", revision)]
+        results.sort(key=lambda x:x[1])
+        
+        changed = []
+        deleted = []
+        for name, created, wasdeleted in results:
+            if name:
+                if wasdeleted == 'Y':
+                    # Don't report items that were created/deleted since the requested revision
+                    if created <= revision:
+                        deleted.append(name)
+                else:
+                    changed.append(name)
+            else:
+                raise SyncTokenValidException
+        
+        return changed, deleted,
+
     def indexedSearch(self, filter, fbtype=False):
         """
         Finds resources matching the given qualifiers.
@@ -357,7 +380,7 @@
         """
         return schema_version
 
-    def _add_to_db(self, name, calendar, cursor = None, expand_until=None, reCreate=False):
+    def _add_to_db(self, name, calendar, revision, cursor=None, expand_until=None, reCreate=False):
         """
         Records the given calendar resource in the index with the given name.
         Resource names and UIDs must both be unique; only one resource name may
@@ -370,7 +393,7 @@
         """
         raise NotImplementedError
 
-    def _delete_from_db(self, name, uid):
+    def _delete_from_db(self, name, uid, revision):
         """
         Deletes the specified entry from all dbs.
         @param name: the name of the resource to delete.
@@ -445,6 +468,28 @@
             """
         )
 
+        #
+        # REVISIONS table tracks changes
+        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
+        #   REVISION: revision number
+        #   WASDELETED: Y if revision deleted, N if added or changed
+        #
+        q.execute(
+            """
+            create table REVISIONS (
+                NAME            text unique,
+                REVISION        integer,
+                CREATEDREVISION integer,
+                WASDELETED      text(1)
+            )
+            """
+        )
+        q.execute(
+            """
+            create index REVISION on REVISIONS (REVISION)
+            """
+        )
+
         if uidunique:
             #
             # RESERVED table tracks reserved UIDs
@@ -473,15 +518,29 @@
         Upgrade the data from an older version of the DB.
         """
 
-        # When going to version 8 all we need to do is add an index
-        if old_version < "8":
-            q.execute("create index STARTENDFLOAT on TIMESPAN (START, END, FLOAT)")
-
-        # When going to version 7,8 all we need to do is add a column to the resource and timespan
+        # When going to version 7+ all we need to do is add a column to the resource and timespan
         if old_version < "7":
             q.execute("alter table RESOURCE add column ORGANIZER text default '?'")
             q.execute("alter table TIMESPAN add column FBTYPE text(1) default '?'")
 
+        # When going to version 8+ all we need to do is add an index
+        if old_version < "8":
+            q.execute("create index STARTENDFLOAT on TIMESPAN (START, END, FLOAT)")
+
+        # When going to version 9+ all we need to do is add revision table and index
+        if old_version < "9":
+            q.execute(
+                """
+                create table REVISIONS (
+                    NAME            text unique,
+                    REVISION        integer,
+                    CREATEDREVISION integer,
+                    WASDELETED      text(1)
+                )
+                """
+            )
+            q.execute("create index REVISION on REVISIONS (REVISION)")
+
     def notExpandedBeyond(self, minDate):
         """
         Gives all resources which have not been expanded beyond a given date
@@ -495,10 +554,10 @@
         with a longer expansion.
         """
         calendar = self.resource.getChild(name).iCalendar()
-        self._add_to_db(name, calendar, expand_until=expand_until, reCreate=True)
+        self._add_to_db(name, calendar, None, expand_until=expand_until, reCreate=True)
         self._db_commit()
 
-    def _add_to_db(self, name, calendar, cursor = None, expand_until=None, reCreate=False):
+    def _add_to_db(self, name, calendar, revision, cursor = None, expand_until=None, reCreate=False):
         """
         Records the given calendar resource in the index with the given name.
         Resource names and UIDs must both be unique; only one resource name may
@@ -536,7 +595,7 @@
             log.err("Invalid instance %s when indexing %s in %s" % (e.rid, name, self.resource,))
             raise
 
-        self._delete_from_db(name, uid)
+        self._delete_from_db(name, uid, None)
 
         for key in instances:
             instance = instances[key]
@@ -562,7 +621,7 @@
                 values (:1, :2, :3, :4, :5)
                 """, name, float, start, end, '?'
             )
-             
+            
         self._db_execute(
             """
             insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
@@ -570,7 +629,18 @@
             """, name, uid, calendar.resourceType(), instances.limit, organizer
         )
 
-    def _delete_from_db(self, name, uid):
+        if revision is not None:
+            created = self._db_value_for_sql("select CREATEDREVISION from REVISIONS where NAME = :1", name)
+            if created is None:
+                created = revision
+            self._db_execute(
+                """
+                insert or replace into REVISIONS (NAME, REVISION, CREATEDREVISION, WASDELETED)
+                values (:1, :2, :3, :4)
+                """, name, revision, revision, 'N',
+            )
+
+    def _delete_from_db(self, name, uid, revision):
         """
         Deletes the specified entry from all dbs.
         @param name: the name of the resource to delete.
@@ -578,6 +648,13 @@
         """
         self._db_execute("delete from TIMESPAN where NAME = :1", name)
         self._db_execute("delete from RESOURCE where NAME = :1", name)
+        if revision is not None:
+            self._db_execute(
+                """
+                update REVISIONS SET REVISION = :1, WASDELETED = :2
+                where NAME = :3
+                """, revision, 'Y', name
+            )
 
 
 def wrapInDeferred(f):
@@ -832,13 +909,16 @@
                 log.err("Non-calendar resource: %s" % (name,))
             else:
                 #log.msg("Indexing resource: %s" % (name,))
-                self.addResource(name, calendar, True, reCreate=True)
+                self.addResource(name, calendar, 0, True, reCreate=True)
             finally:
                 stream.close()
 
         # Do commit outside of the loop for better performance
         if do_commit:
             self._db_commit()
+            
+        # This is a deferred but we can't defer at this point...
+        self.resource.bumpSyncToken(True)
 
 class IndexSchedule (CalendarIndex):
     """
@@ -944,10 +1024,13 @@
                 log.err("Non-calendar resource: %s" % (name,))
             else:
                 #log.msg("Indexing resource: %s" % (name,))
-                self.addResource(name, calendar, True, reCreate=True)
+                self.addResource(name, calendar, 0, True, reCreate=True)
             finally:
                 stream.close()
 
         # Do commit outside of the loop for better performance
         if do_commit:
             self._db_commit()
+            
+        # This is a deferred but we can't defer at this point...
+        self.resource.bumpSyncToken(True)

Modified: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/__init__.py	2009-12-23 20:11:53 UTC (rev 4892)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/__init__.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -27,10 +27,12 @@
     "get",
     "mkcalendar",
     "mkcol",
+    "post",
     "propfind",
     "put",
     "report",
     "report_calquery",
     "report_freebusy",
     "report_multiget",
+    "report_sync_collection",
 ]

Modified: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/delete_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/delete_common.py	2009-12-23 20:11:53 UTC (rev 4892)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/delete_common.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -105,11 +105,9 @@
 
         if response == responsecode.NO_CONTENT:
             if isPseudoCalendarCollectionResource(parent):
+                newrevision = (yield parent.bumpSyncToken())
                 index = parent.index()
-                index.deleteResource(delresource.fp.basename())
-
-                # Change CTag on the parent calendar collection
-                yield parent.updateCTag()
+                index.deleteResource(delresource.fp.basename(), newrevision)
                 
         returnValue(response)
 
@@ -161,12 +159,10 @@
                 yield delresource.quotaSizeAdjust(self.request, -old_size)
     
             if response == responsecode.NO_CONTENT:
+                newrevision = (yield parent.bumpSyncToken())
                 index = parent.index()
-                index.deleteResource(delresource.fp.basename())
+                index.deleteResource(delresource.fp.basename(), newrevision)
     
-                # Change CTag on the parent calendar collection
-                yield parent.updateCTag()
-    
                 # Do scheduling
                 if scheduler and not self.internal_request:
                     yield scheduler.doImplicitScheduling()
@@ -217,7 +213,7 @@
 
         # Now do normal delete
         # Change CTag
-        yield delresource.updateCTag()
+        yield delresource.bumpSyncToken()
         more_responses = (yield self.deleteResource(delresource, deluri, parent))
         
         if isinstance(more_responses, MultiStatusResponse):

Added: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/post.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/post.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/post.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -0,0 +1,94 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from hashlib import md5
+from twisted.web2.filter.location import addLocation
+import time
+
+"""
+CalDAV POST method.
+"""
+
+__all__ = ["http_POST"]
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.web2 import responsecode
+from twext.web2.dav.davxml import ErrorResponse
+from twisted.web2.dav.util import allDataFromStream, joinURL
+from twisted.web2.http import HTTPError, StatusResponse
+
+from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.method.put_common import StoreCalendarObjectResource
+from twistedcaldav.log import Logger
+
+log = Logger()
+
+ at inlineCallbacks
+def http_POST(self, request):
+
+    # POST can support many different APIs
+    
+    # Handle ;add-member
+    if request.params and request.params == "add-member" and self.isCalendarCollection():
+        
+        parentURL = request.path
+        parent = self
+
+        # Content-type check
+        content_type = request.headers.getHeader("content-type")
+        if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "calendar"):
+            log.err("MIME type %s not allowed in calendar collection" % (content_type,))
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
+            
+        # Read the calendar component from the stream
+        try:
+            calendardata = (yield allDataFromStream(request.stream))
+            if not hasattr(request, "extendedLogItems"):
+                request.extendedLogItems = {}
+            request.extendedLogItems["cl"] = str(len(calendardata)) if calendardata else "0"
+
+            # We must have some data at this point
+            if calendardata is None:
+                # Use correct DAV:error response
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description="No calendar data"))
+
+            # Create a new name if one was not provided
+            name =  md5(str(calendardata) + str(time.time()) + self.fp.path).hexdigest() + ".ics"
+        
+            # Get a resource for the new item
+            newchildURL = joinURL(parentURL, name)
+            newchild = (yield request.locateResource(newchildURL))
+
+            storer = StoreCalendarObjectResource(
+                request = request,
+                destination = newchild,
+                destination_uri = newchildURL,
+                destinationcal = True,
+                destinationparent = parent,
+                calendar = calendardata,
+            )
+            result = (yield storer.run())
+
+            # May need to add a location header
+            addLocation(request, request.unparseURL(path=newchildURL, params=""))
+
+            returnValue(result)
+
+        except ValueError, e:
+            log.err("Error while handling (calendar) PUT: %s" % (e,))
+            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
+
+    # Default behavior
+    returnValue(responsecode.NOT_ALLOWED)

Modified: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/put_common.py	2009-12-23 20:11:53 UTC (rev 4892)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/put_common.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -273,6 +273,7 @@
         
         self.rollback = None
         self.access = None
+        self.newrevision = None
 
     @inlineCallbacks
     def fullValidation(self):
@@ -830,7 +831,8 @@
     def doSourceDelete(self):
         # Delete index for original item
         if self.sourcecal:
-            self.source_index.deleteResource(self.source.fp.basename())
+            self.newrevision = (yield self.sourceparent.bumpSyncToken())
+            self.source_index.deleteResource(self.source.fp.basename(), self.newrevision)
             self.rollback.source_index_deleted = True
             log.debug("Source index removed %s" % (self.source.fp.path,))
 
@@ -838,10 +840,6 @@
         delete(self.source_uri, self.source.fp, "0")
         self.rollback.source_deleted = True
         log.debug("Source removed %s" % (self.source.fp.path,))
-
-        # Change CTag on the parent calendar collection
-        if self.sourcecal:
-            yield self.sourceparent.updateCTag()
   
         returnValue(None)
 
@@ -878,7 +876,7 @@
         
         # Add or update the index for this resource.
         try:
-            self.source_index.addResource(self.source.fp.basename(), self.calendar)
+            self.source_index.addResource(self.source.fp.basename(), self.calendar, self.newrevision)
         except TooManyInstancesError, ex:
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
@@ -899,7 +897,7 @@
         
         # Add or update the index for this resource.
         try:
-            self.destination_index.addResource(self.destination.fp.basename(), caltoindex)
+            self.destination_index.addResource(self.destination.fp.basename(), caltoindex, self.newrevision)
             log.debug("Destination indexed %s" % (self.destination.fp.path,))
         except TooManyInstancesError, ex:
             log.err("Cannot index calendar resource as there are too many recurrence instances %s" % self.destination)
@@ -926,7 +924,7 @@
         
         # Delete index for original item
         if self.destinationcal:
-            self.destination_index.deleteResource(self.destination.fp.basename())
+            self.destination_index.deleteResource(self.destination.fp.basename(), None)
             self.rollback.destination_index_deleted = True
             log.debug("Destination index removed %s" % (self.destination.fp.path,))
 
@@ -1088,6 +1086,7 @@
     
             # Index the new resource if storing to a calendar.
             if self.destinationcal:
+                self.newrevision = (yield self.destinationparent.bumpSyncToken())
                 result = self.doDestinationIndex(self.calendar)
                 if result is not None:
                     self.rollback.Rollback()
@@ -1101,10 +1100,6 @@
             if self.destquota is not None:
                 yield self.doDestinationQuotaCheck()
     
-            if self.destinationcal:
-                # Change CTag on the parent calendar collection
-                yield self.destinationparent.updateCTag()
-    
             # Can now commit changes and forget the rollback details
             self.rollback.Commit()
     

Modified: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/report_common.py	2009-12-23 20:11:53 UTC (rev 4892)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/report_common.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -452,7 +452,7 @@
                     processEventFreeBusy(calendar, fbinfo, timerange, tzinfo)
                     
                     # Lets also force an index rebuild for this resource so that next time we have the fbtype set
-                    calresource.index().addResource(name, calendar, reCreate=True)
+                    calresource.index().addResource(name, calendar, None, reCreate=True)
 
                 elif calendar.mainType() == "VFREEBUSY":
                     processFreeBusyFreeBusy(calendar, fbinfo, timerange)

Added: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/report_sync_collection.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/report_sync_collection.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/method/report_sync_collection.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -0,0 +1,146 @@
+##
+# Copyright (c) 2006-2008 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+DAV sync-collection report
+"""
+
+__all__ = ["report_DAV__sync_collection"]
+
+from twext.web2.dav.davxml import ErrorResponse, SyncToken
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python.failure import Failure
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.element.base import WebDAVElement
+from twisted.web2.dav.http import MultiStatusResponse, statusForFailure
+from twisted.web2.dav.method.prop_common import responseForHref
+from twisted.web2.dav.method.propfind import propertyName
+from twisted.web2.dav.util import joinURL
+from twisted.web2.http import HTTPError
+
+from twistedcaldav.config import config
+from twistedcaldav.log import Logger
+
+import functools
+
+log = Logger()
+
+ at inlineCallbacks
+def report_DAV__sync_collection(self, request, sync_collection):
+    """
+    Generate a sync-collection REPORT.
+    """
+    if not self.isPseudoCalendarCollection() or not config.EnableSyncReport:
+        log.err("sync-collection report is only allowed on calendar/inbox collection resources %s" % (self,))
+        raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, davxml.SupportedReport()))
+   
+    responses = []
+
+    propertyreq = sync_collection.property
+    
+    @inlineCallbacks
+    def _namedPropertiesForResource(request, props, resource, forbidden=False):
+        """
+        Return the specified properties on the specified resource.
+        @param request: the L{IRequest} for the current request.
+        @param props: a list of property elements or qname tuples for the properties of interest.
+        @param resource: the L{DAVFile} for the targeted resource.
+        @return: a map of OK and NOT FOUND property values.
+        """
+        properties_by_status = {
+            responsecode.OK        : [],
+            responsecode.FORBIDDEN : [],
+            responsecode.NOT_FOUND : [],
+        }
+        
+        for property in props:
+            if isinstance(property, WebDAVElement):
+                qname = property.qname()
+            else:
+                qname = property
+
+            if forbidden:
+                properties_by_status[responsecode.FORBIDDEN].append(propertyName(qname))
+            else:
+                props = (yield resource.listProperties(request))
+                if qname in props:
+                    try:
+                        prop = (yield resource.readProperty(qname, request))
+                        properties_by_status[responsecode.OK].append(prop)
+                    except:
+                        f = Failure()
+                        log.err("Error reading property %r for resource %s: %s" % (qname, request.uri, f.value))
+                        status = statusForFailure(f, "getting property: %s" % (qname,))
+                        if status not in properties_by_status: properties_by_status[status] = []
+                        properties_by_status[status].append(propertyName(qname))
+                else:
+                    properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname))
+        
+        yield properties_by_status
+    
+    # Do some optimization of access control calculation by determining any inherited ACLs outside of
+    # the child resource loop and supply those to the checkPrivileges on each child.
+    filteredaces = (yield self.inheritedACEsforChildren(request))
+
+    changed, removed, newtoken = self.whatchanged(sync_collection.sync_token)
+
+    # Now determine which valid resources are readable and which are not
+    ok_resources = []
+    forbidden_resources = []
+    if changed:
+        yield self.findChildrenFaster(
+            "1",
+            request,
+            lambda x, y: ok_resources.append((x, y)),
+            lambda x, y: forbidden_resources.append((x, y)),
+            changed,
+            (davxml.Read(),),
+            inherited_aces=filteredaces
+        )
+
+    for child, child_uri in ok_resources:
+        href = davxml.HRef.fromString(child_uri)
+        yield responseForHref(
+            request,
+            responses,
+            href,
+            child,
+            functools.partial(_namedPropertiesForResource, forbidden=False) if propertyreq else None,
+            propertyreq)
+
+    for child, child_uri in forbidden_resources:
+        href = davxml.HRef.fromString(child_uri)
+        yield responseForHref(
+            request,
+            responses,
+            href,
+            child,
+            functools.partial(_namedPropertiesForResource, forbidden=True) if propertyreq else None,
+            propertyreq)
+
+    for name in removed:
+        href = davxml.HRef.fromString(joinURL(request.uri, name))
+        responses.append(davxml.StatusResponse(davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
+    
+    if not hasattr(request, "extendedLogItems"):
+        request.extendedLogItems = {}
+    request.extendedLogItems["responses"] = len(responses)
+
+    responses.append(SyncToken.fromString(newtoken))
+
+    returnValue(MultiStatusResponse(responses))

Modified: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/resource.py	2009-12-23 20:11:53 UTC (rev 4892)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/resource.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -31,7 +31,7 @@
 
 from zope.interface import implements
 
-from twext.web2.dav.davxml import ErrorResponse
+from twext.web2.dav.davxml import ErrorResponse, AddMember, SyncCollection
 
 from twisted.internet import reactor
 from twisted.internet.defer import Deferred, maybeDeferred, succeed
@@ -189,6 +189,7 @@
 
     liveProperties = DAVResource.liveProperties + (
         (dav_namespace,    "owner"),               # Private Events needs this but it is also OK to return empty
+        (dav_namespace,    "add-member"),
         (caldav_namespace, "supported-calendar-component-set"),
         (caldav_namespace, "supported-calendar-data"         ),
     )
@@ -227,6 +228,10 @@
                 owner = (yield self.owner(request))
                 returnValue(davxml.Owner(owner))
 
+            elif name == "add-member" and config.EnableAddMember and self.isCalendarCollection():
+                url = (yield self.canonicalURL(request))
+                returnValue(AddMember(davxml.HRef(url + "/;add-member")))
+
         elif namespace == caldav_namespace:
             if name == "supported-calendar-component-set":
                 # CalDAV-access-09, section 5.2.3
@@ -690,9 +695,12 @@
         result = super(CalDAVResource, self).supportedReports()
         result.append(davxml.Report(caldavxml.CalendarQuery(),))
         result.append(davxml.Report(caldavxml.CalendarMultiGet(),))
-        if (self.isCollection()):
+        if self.isCollection():
             # Only allowed on collections
             result.append(davxml.Report(caldavxml.FreeBusyQuery(),))
+        if self.isPseudoCalendarCollection() and config.EnableSyncReport:
+            # Only allowed on calendar/inbox collections
+            result.append(davxml.Report(SyncCollection(),))
         return result
 
     def writeNewACEs(self, newaces):

Modified: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/static.py	2009-12-23 20:11:53 UTC (rev 4892)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/static.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -38,6 +38,7 @@
 import os
 import errno
 from urlparse import urlsplit
+from uuid import uuid4
 
 from twext.web2.dav.davxml import ErrorResponse
 
@@ -47,6 +48,7 @@
 from twisted.web2 import responsecode, http, http_headers
 from twisted.web2.http import HTTPError, StatusResponse
 from twisted.web2.dav import davxml
+from twisted.web2.dav.element.base import dav_namespace
 from twisted.web2.dav.fileop import mkcollection, rmdir
 from twisted.web2.dav.idav import IDAVResource
 from twisted.web2.dav.noneprops import NonePropertyStore
@@ -64,7 +66,7 @@
 from twistedcaldav.freebusyurl import FreeBusyURLResource
 from twistedcaldav.ical import Component as iComponent
 from twistedcaldav.ical import Property as iProperty
-from twistedcaldav.index import Index, IndexSchedule
+from twistedcaldav.index import Index, IndexSchedule, SyncTokenValidException
 from twistedcaldav.resource import CalDAVResource, isCalendarCollectionResource, isPseudoCalendarCollectionResource
 from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource, IScheduleInboxResource
 from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource
@@ -208,7 +210,7 @@
                 raise HTTPError(status)
 
             # Initialize CTag on the calendar collection
-            d1 = self.updateCTag()
+            d1 = self.bumpSyncToken()
 
             # Calendar is initially transparent to freebusy
             self.writeDeadProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Transparent()))
@@ -373,6 +375,84 @@
         """
         return Index(self)
 
+    def whatchanged(self, client_token):
+        
+        current_token = str(self.readDeadProperty(customxml.GETCTag))
+        current_uuid, current_revision = current_token.split("#", 1)
+        current_revision = int(current_revision)
+
+        if client_token:
+            try:
+                caluuid, revision = client_token.split("#", 1)
+                revision = int(revision)
+                
+                # Check client token validity
+                if caluuid != current_uuid:
+                    raise ValueError
+                if revision > current_revision:
+                    raise ValueError
+            except ValueError:
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (dav_namespace, "valid-sync-token")))
+        else:
+            revision = 0
+
+        try:
+            changed, removed = self.index().whatchanged(revision)
+        except SyncTokenValidException:
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (dav_namespace, "valid-sync-token")))
+
+        return changed, removed, current_token
+
+    def bumpSyncToken(self, reset=False):
+        """
+        Increment the sync-token which is also the ctag.
+        
+        return: a deferred that returns the new revision number
+        """
+        assert self.isCollection()
+        
+        try:
+            if reset:
+                raise ValueError
+            token = str(self.readDeadProperty(customxml.GETCTag))
+            caluuid, revision = token.split("#", 1)
+            revision = int(revision) + 1
+            token = "%s#%d" % (caluuid, revision,)
+
+        except (HTTPError, ValueError):
+            # Initialise it
+            caluuid = uuid4()
+            revision = 0
+            token = "%s#%d" % (caluuid, revision,)
+
+        d = self.updateCTag(token)
+        d.addCallback(lambda _:revision)
+        return d
+            
+    def updateCTag(self, token=None):
+        assert self.isCollection()
+        
+        if not token:
+            token = str(datetime.datetime.now())
+        try:
+            self.writeDeadProperty(customxml.GETCTag(token))
+        except:
+            return fail(Failure())
+
+        if hasattr(self, 'clientNotifier'):
+            self.clientNotifier.notify(op="update")
+        else:
+            log.debug("%r does not have a clientNotifier but the CTag changed"
+                      % (self,))
+
+        if hasattr(self, 'cacheNotifier'):
+            return self.cacheNotifier.changed()
+        else:
+            log.debug("%r does not have a cacheNotifier but the CTag changed"
+                      % (self,))
+
+        return succeed(True)
+
     ##
     # File
     ##
@@ -439,28 +519,6 @@
 
         return similar
 
-    def updateCTag(self):
-        assert self.isCollection()
-        try:
-            self.writeDeadProperty(customxml.GETCTag(
-                    str(datetime.datetime.now())))
-        except:
-            return fail(Failure())
-
-        if hasattr(self, 'clientNotifier'):
-            self.clientNotifier.notify(op="update")
-        else:
-            log.debug("%r does not have a clientNotifier but the CTag changed"
-                      % (self,))
-
-        if hasattr(self, 'cacheNotifier'):
-            return self.cacheNotifier.changed()
-        else:
-            log.debug("%r does not have a cacheNotifier but the CTag changed"
-                      % (self,))
-
-        return succeed(True)
-
     ##
     # Quota
     ##
@@ -883,7 +941,7 @@
         if self.provisionFile():
 
             # Initialize CTag on the calendar collection
-            self.updateCTag()
+            self.bumpSyncToken()
 
             # Initialize the index
             self.index().create()
@@ -910,10 +968,7 @@
         ScheduleOutboxResource.__init__(self, parent)
 
     def provision(self):
-        if self.provisionFile():
-            # Initialize CTag on the calendar collection
-            self.updateCTag()
-
+        self.provisionFile()
         return super(ScheduleOutboxFile, self).provision()
 
     def __repr__(self):

Modified: CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/test/test_index.py
===================================================================
--- CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/test/test_index.py	2009-12-23 20:11:53 UTC (rev 4892)
+++ CalendarServer/branches/users/cdaboo/webdav-extensions-4888/twistedcaldav/test/test_index.py	2010-01-04 20:31:57 UTC (rev 4893)
@@ -20,7 +20,7 @@
 from twistedcaldav.ical import Component
 from twistedcaldav.index import Index, default_future_expansion_duration,\
     maximum_future_expansion_duration, IndexedSearchException,\
-    AbstractCalendarIndex
+    AbstractCalendarIndex, icalfbtype_to_indexfbtype
 from twistedcaldav.index import ReservationError, MemcachedUIDReserver
 from twistedcaldav.instance import InvalidOverriddenInstanceError
 from twistedcaldav.test.util import InMemoryMemcacheProtocol
@@ -230,17 +230,19 @@
             ),
         )
 
+        revision = 0
         for description, name, calendar_txt, reCreate, ok in data:
+            revision += 1
             calendar = Component.fromString(calendar_txt)
             if ok:
                 f = open(os.path.join(self.site.resource.fp.path, name), "w")
                 f.write(calendar_txt)
                 del f
 
-                self.db.addResource(name, calendar, reCreate=reCreate)
+                self.db.addResource(name, calendar, revision, reCreate=reCreate)
                 self.assertTrue(self.db.resourceExists(name), msg=description)
             else:
-                self.assertRaises(InvalidOverriddenInstanceError, self.db.addResource, name, calendar)
+                self.assertRaises(InvalidOverriddenInstanceError, self.db.addResource, name, calendar, revision)
                 self.assertFalse(self.db.resourceExists(name), msg=description)
 
         self.db._db_recreate()
@@ -402,14 +404,16 @@
             ),
         )
 
+        revision = 0
         for description, name, calendar_txt, trstart, trend, organizer, instances in data:
+            revision += 1
             calendar = Component.fromString(calendar_txt)
 
             f = open(os.path.join(self.site.resource.fp.path, name), "w")
             f.write(calendar_txt)
             del f
 
-            self.db.addResource(name, calendar)
+            self.db.addResource(name, calendar, revision)
             self.assertTrue(self.db.resourceExists(name), msg=description)
 
             # Create fake filter element to match time-range
@@ -434,6 +438,69 @@
 
             self.assertEqual(set(instances), index_results, msg=description)
 
+    def test_index_revisions(self):
+        data1 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+        data2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+"""
+        data3 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.3
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+"""
+
+        calendar = Component.fromString(data1)
+        self.db.addResource("data1.ics", calendar, 1)
+        calendar = Component.fromString(data2)
+        self.db.addResource("data2.ics", calendar, 2)
+        calendar = Component.fromString(data3)
+        self.db.addResource("data3.ics", calendar, 3)
+        self.db.deleteResource("data3.ics", 4)
+
+        tests = (
+            (0, (["data1.ics", "data2.ics",], [],)),
+            (1, (["data2.ics",], [],)),
+            (2, ([], [],)),
+            (3, ([], ["data3.ics",],)),
+            (4, ([], [],)),
+            (5, ([], [],)),
+        )
+        
+        for revision, results in tests:
+            self.assertEquals(self.db.whatchanged(revision), results, "Mismatched results for whatchanged with revision %d" % (revision,))
+
 class SQLIndexUpgradeTests (twistedcaldav.test.util.TestCase):
     """
     Test abstract SQL DB class
@@ -556,7 +623,7 @@
             except InvalidOverriddenInstanceError:
                 raise
     
-            self._delete_from_db(name, uid)
+            self._delete_from_db(name, uid, None)
     
             for key in instances:
                 instance = instances[key]
@@ -675,6 +742,77 @@
                     """
                 )
 
+        def _add_to_db(self, name, calendar, cursor = None, expand_until=None, reCreate=False):
+            """
+            Records the given calendar resource in the index with the given name.
+            Resource names and UIDs must both be unique; only one resource name may
+            be associated with any given UID and vice versa.
+            NB This method does not commit the changes to the db - the caller
+            MUST take care of that
+            @param name: the name of the resource to add.
+            @param calendar: a L{Calendar} object representing the resource
+                contents.
+            """
+            uid = calendar.resourceUID()
+            organizer = calendar.getOrganizer()
+            if not organizer:
+                organizer = ""
+    
+            # Decide how far to expand based on the component
+            master = calendar.masterComponent()
+            if master is None or not calendar.isRecurring() and not calendar.isRecurringUnbounded():
+                # When there is no master we have a set of overridden components - index them all.
+                # When there is one instance - index it.
+                # When bounded - index all.
+                expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+            else:
+                if expand_until:
+                    expand = expand_until
+                else:
+                    expand = datetime.date.today() + default_future_expansion_duration
+        
+                if expand > (datetime.date.today() + maximum_future_expansion_duration):
+                    raise IndexedSearchException
+    
+            try:
+                instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
+            except InvalidOverriddenInstanceError, e:
+                raise
+    
+            self._delete_from_db(name, uid, None)
+    
+            for key in instances:
+                instance = instances[key]
+                start = instance.start.replace(tzinfo=utc)
+                end = instance.end.replace(tzinfo=utc)
+                float = 'Y' if instance.start.tzinfo is None else 'N'
+                self._db_execute(
+                    """
+                    insert into TIMESPAN (NAME, FLOAT, START, END, FBTYPE)
+                    values (:1, :2, :3, :4, :5)
+                    """, name, float, start, end, icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F')
+                )
+    
+            # Special - for unbounded recurrence we insert a value for "infinity"
+            # that will allow an open-ended time-range to always match it.
+            if calendar.isRecurringUnbounded():
+                start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+                end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
+                float = 'N'
+                self._db_execute(
+                    """
+                    insert into TIMESPAN (NAME, FLOAT, START, END, FBTYPE)
+                    values (:1, :2, :3, :4, :5)
+                    """, name, float, start, end, '?'
+                )
+                 
+            self._db_execute(
+                """
+                insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
+                values (:1, :2, :3, :4, :5)
+                """, name, uid, calendar.resourceType(), instances.limit, organizer
+            )
+
     def setUp(self):
         super(SQLIndexUpgradeTests, self).setUp()
         self.site.resource.isCalendarCollection = lambda: True
@@ -749,7 +887,7 @@
 END:VCALENDAR
 """
     
-            olddb.addResource(calendar_name, Component.fromString(calendar_data))
+            olddb.addResource(calendar_name, Component.fromString(calendar_data), 1)
             self.assertTrue(olddb.resourceExists(calendar_name))
     
             if olddb._db_version() == "6":
@@ -772,7 +910,7 @@
             else:
                 self.assertEqual(value, "B")
     
-            self.db.addResource(calendar_name, Component.fromString(calendar_data))
+            self.db.addResource(calendar_name, Component.fromString(calendar_data), 2)
             self.assertTrue(olddb.resourceExists(calendar_name))
     
             value = self.db._db_value_for_sql("select ORGANIZER from RESOURCE where NAME = :1", calendar_name)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100104/eee800fc/attachment-0001.html>


More information about the calendarserver-changes mailing list