[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