RFC 6578
Collection Synchronization for Web Distributed Authoring and Versioning (WebDAV)
RFC 6578: Collection Synchronization for WebDAV ๐
Hiii bestie, okay, this one is a whole vibe ๐ธ It's an Internet Standards Track document from the IETF (Internet Engineering Task Force), published as Request for Comments: 6578 in March 2012 ๐
. Two authors wrote it: C. Daboo from Apple Inc. and A. Quillaud from Oracle ๐. The ISSN is 2070-1721 ๐ฆ. The header doesn't list any RFCs that it obsoletes or updates, so this one is standing all on its own โจ
Abstract
This specification defines an extension to Web Distributed Authoring and Versioning (WebDAV) that allows efficient synchronization of the contents of a WebDAV collection. ๐ซถ
Status of This Memo
This is an Internet Standards Track document.
This document is a product of the Internet Engineering Task Force (IETF). It represents the consensus of the IETF community. It has received public review and has been approved for publication by the Internet Engineering Steering Group (IESG). Further information on Internet Standards is available in Section 2 of RFC 5741.
Information about the current status of this document, any errata, and how to provide feedback on it may be obtained at http://www.rfc-editor.org/info/rfc6578. ๐
Copyright Notice
Copyright (c) 2012 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, because 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 they are provided without warranty as described in the Simplified 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. ๐
Table of Contents ๐ง
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 4
2. Conventions Used in This Document . . . . . . . . . . . . . . 4
3. WebDAV Synchronization . . . . . . . . . . . . . . . . . . . . 5
3.1. Overview . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.2. DAV:sync-collection Report . . . . . . . . . . . . . . . . 6
3.3. Depth Behavior . . . . . . . . . . . . . . . . . . . . . . 8
3.4. Types of Changes Reported on Initial Synchronization . . . 9
3.5. Types of Changes Reported on Subsequent
Synchronizations . . . . . . . . . . . . . . . . . . . . . 10
3.5.1. Changed Member . . . . . . . . . . . . . . . . . . . . 10
3.5.2. Removed Member . . . . . . . . . . . . . . . . . . . . 10
3.6. Truncation of Results . . . . . . . . . . . . . . . . . . 11
3.7. Limiting Results . . . . . . . . . . . . . . . . . . . . . 12
3.8. Example: Initial DAV:sync-collection Report . . . . . . . 12
3.9. Example: DAV:sync-collection Report with Token . . . . . . 14
3.10. Example: Initial DAV:sync-collection Report with
Truncation . . . . . . . . . . . . . . . . . . . . . . . . 16
3.11. Example: Initial DAV:sync-collection Report with Limit . . 17
3.12. Example: DAV:sync-collection Report with Unsupported
Limit . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.13. Example: DAV:sync-level Set to Infinite, Initial
DAV:sync-collection Report . . . . . . . . . . . . . . . . 19
4. DAV:sync-token Property . . . . . . . . . . . . . . . . . . . 22
5. DAV:sync-token Use with If Header . . . . . . . . . . . . . . 22
5.1. Example: If Precondition with PUT . . . . . . . . . . . . 22
5.2. Example: If Precondition with MKCOL . . . . . . . . . . . 23
6. XML Element Definitions . . . . . . . . . . . . . . . . . . . 24
6.1. DAV:sync-collection XML Element . . . . . . . . . . . . . 24
6.2. DAV:sync-token XML Element . . . . . . . . . . . . . . . . 24
6.3. DAV:sync-level XML Element . . . . . . . . . . . . . . . . 24
6.4. DAV:multistatus XML Element . . . . . . . . . . . . . . . 25
7. Security Considerations . . . . . . . . . . . . . . . . . . . 25
8. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 25
9. References . . . . . . . . . . . . . . . . . . . . . . . . . . 25
9.1. Normative References . . . . . . . . . . . . . . . . . . . 25
9.2. Informative References . . . . . . . . . . . . . . . . . . 26
Appendix A. Backwards-Compatible Handling of Depth . . . . . . . 27
Appendix B. Example of a Client Synchronization Approach . . . . 27
1. Introduction โจ
Okay so, WebDAV [RFC4918] has this concept called 'collections', which are hierarchical groupings of WebDAV resources on an HTTP [RFC2616] server. ๐ Collections can be any size and any depth (so collections can live inside other collections, iykyk). ๐ธ WebDAV clients that cache resource content need a way to sync that data with the server, which means detecting what has changed and updating their cache. ๐
Right now, a client can do this with a WebDAV PROPFIND request on a collection, which lists all the members of the collection along with their DAV:getetag property values. That lets the client figure out what was changed, added, or deleted. ๐ The problem is that this doesn't scale well to big collections, because the XML response to the PROPFIND request keeps growing with the size of the collection. ๐ญ
This specification defines a new WebDAV report, and it's giving efficiency ๐ฆ Instead of everything, the server only sends back the info about member URLs that were added or deleted, or whose mapped resources were changed, since a previous run of the report on that collection.
There's also a new property added to collection resources that carries a "synchronization token". This token is guaranteed to change whenever the collection's member URLs or their mapped resources 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 can't be validated by a DTD, because of the specific extensibility rules defined in Section 17 of [RFC4918], and also because every XML element defined by this specification uses the XML namespace name "DAV:". Here's what that means in practice: ๐
- Element names use the "DAV:" namespace.
- Element ordering is irrelevant unless explicitly stated otherwise.
- Extension elements (elements not already defined as valid child elements) may be added anywhere, except when explicitly stated otherwise.
- Extension attributes (attributes not already defined as valid for this element) may be added anywhere, except when explicitly stated otherwise.
When an XML element type in the "DAV:" namespace is mentioned in this document outside of an XML fragment, the string DAV: gets put in front of the element type. ๐
This document inherits, and sometimes extends, DTD productions from Section 14 of [RFC4918]. ๐
3. WebDAV Synchronization ๐ง
3.1. Overview
Okay, so one way two entities can sync data is by using some form of synchronization token. ๐ช The token defines the state of the data being synchronized at one particular point in time. Then it can be used to figure out what changed between one point in time and another. ๐
This specification defines a new WebDAV report that enables client-server collection synchronization based on that kind of token. ๐ซถ
To sync the contents of a collection between a server and a client, the server gives the client a synchronization token every time the synchronization report runs. That token represents the state of the data being synchronized at that moment. Later on, the client can hand that same token back to the server, and the server will return only the items that are new, have changed, or were deleted since that token was generated. The server also returns a new token that represents the new state from when the report ran. โจ
Typically, the first time a client connects to the server, it needs to be told the entire state of the collection (basically, a full list of all member URLs that are currently in the collection). ๐ That happens when the client sends an empty token value to the server. That's the server's cue that a full listing is required. ๐ฅ
Alternatively, the client might choose to do its first sync using some other mechanism on the collection. For example, it could use some other form of batch resource information retrieval, like PROPFIND, SEARCH [RFC5323], or the specialized REPORTs defined in CalDAV [RFC4791] and CardDAV [RFC6352]. If it does that, it can ask for the DAV:sync-token property to be returned. This property (defined in Section 4) contains the same token that can be used later to issue a DAV:sync-collection report. ๐ฆ
Sometimes a server only wants to keep a limited amount of history about changes to a collection. In that case, when the client presents a token that is "out of date", the server returns an error to the client. โ ๏ธ At that point, the client has to fall back to syncing the entire collection by re-running the report request with an empty token value. ๐ญ
Typically, a client uses the synchronization report to get the list of changes, and then it follows up with requests to fetch the content of the changed resources. ๐ Additional changes to the collection could happen between the synchronization report and the resource content retrieval, and that could leave the client with an inconsistent view of the collection. ๐ So clients using this method need to be aware that those extra changes could happen and keep track of them. They can do that by comparing the ETag values returned in the synchronization report with the ones returned when the resource content is actually fetched, by using conditional requests as described in Section 5, or by repeating the synchronization process until no changes come back. ๐
3.2. DAV:sync-collection Report โจ
okay so if a WebDAV server does the DAV:sync-collection report, it MUST list the report in the DAV:supported-report-set property on any collection that supports synchronization ๐
the server also needs to keep track of changes to member URLs and their mapped resources in a collection (see Section 3 of [RFC4918]) ๐ that means noticing new member URLs, changes to the mapped resources of existing ones, and member URLs getting removed. the server tracks each change and gives the client a synchronization "token" that describes its state at one specific moment. the token comes back in the response to the "sync-collection" report. next time, the client sends the last token it got in its next "sync-collection" report, and the server returns the changes from the old state (the old token) up to the new state (the new token). it's giving handoff, literally ๐ฆ
the synchronization token MUST be treated as an "opaque" string by the client, which means the actual string data has no specific meaning or syntax. no peeking ๐ BUT the token MUST be a valid URI so it can be used in an If precondition request header (see Section 5). for example, a simple token could be a numeric counter that goes up with every change and links that change to the object that changed. then you just append the number to a "base" URI and boom, valid sync-token โจ
Marshalling:
the request-URI MUST identify 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, one DAV:sync-level element, and one DAV:prop XML element, and MAY contain a DAV:limit XML element ๐
this report is only defined when the Depth header has the value "0". any other value gets a 400 (Bad Request) error response ๐ซ fun fact (ngl I had to look twice): [RFC3253], Section 3.6, says that if the Depth header isn't there at all, it defaults to "0" ๐ง
for a successful request, the response body MUST be a DAV:multistatus XML element. it MUST contain one DAV:sync-token element, plus one DAV:response element for each member URL that was added, had its mapped resource changed, or was deleted since the last synchronization operation (the one specified by the DAV:sync-token in the request). a given member URL MUST appear only once in the response. if multiple member URLs of the request-URI are mapped to the same resource and that resource changes, then each of those member URLs MUST be returned in the response ๐ฏโโ๏ธ
what goes inside each DAV:response depends on how the member changed:
- for members that changed (they're new, or their mapped resource was modified), the DAV:response MUST contain at least one DAV:propstat element and MUST NOT contain any DAV:status element.
- for members that were removed, the DAV:response MUST contain one DAV:status with a value set to '404 Not Found' and MUST NOT contain any DAV:propstat element. ๐
- for members that are collections and can't support the DAV:sync-collection report, the DAV:response MUST contain one DAV:status with a value set to '403 Forbidden', plus a DAV:error containing DAV:supported-report or DAV:sync-traversal-supported (see Section 3.3 for which one fits), and MUST NOT contain any DAV:propstat element.
the conditions for each type of change are explained further in Section 3.5 ๐
Preconditions:
(DAV:valid-sync-token): the DAV:sync-token element value MUST be a valid token that the server previously returned for the collection targeted by the request-URI. servers might need to invalidate tokens they gave out before ๐ซ when that happens, clients fall back to doing a full synchronization using the report. the good news is that clients won't have to re-download resources they already cached and that haven't changed ๐ธ still, servers MUST limit themselves to invalidating tokens only when absolutely necessary. specific reasons include:
- the server might be unable to keep all the change data for a collection because of storage or performance reasons. for example, it might only be able to keep up to 3 weeks worth of changes to a collection, or only up to 10,000 total changes, or it might not want to keep changes for a deleted collection.
- a change to the server implementation: the server might get upgraded to a new implementation that tracks history in a different way, which means the old synchronization history isn't valid anymore ๐
Postconditions:
(DAV:number-of-matches-within-limits): the number of changes reported in the response must fall within the client-specified limit. this can happen if a client asks for a limit on the number of responses (see Section 3.7), but the server can't cut the result set down to that limit or below ๐ช
3.3. Depth Behavior ๐
servers MUST support only Depth:0 behavior with the DAV:sync-collection report. in other words, the report only targets the collection being synchronized, in a single request. BUT clients do need to "scope" the sync to different levels inside that collection: immediate children (level "1") and all children at any depth (level "infinite"). to pick a level, clients MUST include a DAV:sync-level XML element in the request ๐ฏ
- when the client sets the DAV:sync-level XML element to "1", only the appropriate internal member URLs (the immediate children) of the request-URI collection are reported.
- when the client sets the DAV:sync-level XML element to "infinite", all the appropriate member URLs of the request-URI collection are reported, as long as the child collections themselves also support the DAV:sync-collection report ๐งธ
- DAV:sync-token values from the server aren't tied to whichever DAV:sync-level XML element value was used in the request. so clients MAY use a DAV:sync-token from a request with one DAV:sync-level value for a similar request with a different value. the usefulness of that is limited, though ๐คทโโ๏ธ
heads up: when a server supports the DAV:sync-level XML element set to "infinite", it might not be possible to sync some child collections inside the collection targeted by the report. when that happens, the server MUST include a DAV:response element for the child collection with a 403 (Forbidden) status. the 403 MUST be sent once, when the collection is first reported to the client. the server MUST also include a DAV:error element in that DAV:response element, showing one of two possible causes:
- the DAV:sync-collection report isn't supported at all on the child collection. the DAV:error element MUST contain the DAV:supported-report element.
- the server won't report results for the child collection when a DAV:sync-collection report with the DAV:sync-level XML element set to "infinite" is run on a parent resource. this might happen when, for example, the synchronization state of the collection is controlled by another subsystem ๐ฐ in that case, clients can run the DAV:sync-collection report directly on the child collection instead. the DAV:error element MUST contain the DAV:sync-traversal-supported element.
3.4. Types of Changes Reported on Initial Synchronization ๐ฑ
when the DAV:sync-collection request has an empty DAV:sync-token element, the server MUST return all the member URLs of the collection (taking the DAV:sync-level XML element value from Section 3.3 into account, plus optional truncation of the result set per Section 3.6). it MUST NOT return any removed member URLs, because nothing's been removed from the client's point of view yet ๐ซถ and all types of member (collection or non-collection) MUST be reported.
3.5. Types of Changes Reported on Subsequent Synchronizations ๐
when the DAV:sync-collection request has a valid value for the DAV:sync-token element, two types of member URL state changes can come back: changed or removed. this section defines what triggers each one. it also covers the case where a member URL might have changed several times between two synchronization report requests ๐ง in every case, the server takes into account the DAV:sync-level XML element value (per Section 3.3) and optional truncation of the result set (per Section 3.6).
3.5.1. Changed Member ๐
a member URL MUST be reported as changed if it was newly mapped as a member of the target collection since the request sync-token was generated (for example, when a new resource was created as a child of the collection). this includes member URLs that got newly mapped through a COPY, MOVE, BIND [RFC5842], or REBIND [RFC5842] request ๐ all types of member URL (collection or non-collection) MUST be reported.
if a mapping between a member URL and the target collection was removed, and then a new mapping with the same URI was created, the member URL MUST be reported as changed and MUST NOT be reported as removed. the glow-up is real ๐
a member URL MUST be reported as changed if its mapped resource's entity tag value (defined in Section 3.11 of [RFC2616]) has changed since the request sync-token was generated ๐ท๏ธ
a member URL MAY be reported as changed if the user making the request was granted access to that member URL because of access control changes ๐
collection member URLs MUST be returned as changed if they're mapped to an underlying resource (in other words, an entity body) and the entity tag associated with that resource changes. there's no guarantee that changes to a collection's members will change any entity tag of that collection itself ๐ฌ so clients can't rely on a series of reports using the DAV:sync-level XML element set to "1" at multiple levels to track every change inside a collection. instead, a DAV:sync-level XML element with a value of "infinite" has to be used ๐ธ
3.5.2. Removed Member โจ
okay so a member MUST be reported as removed if its mapping under the target collection got removed since the request sync-token was generated, and it hasn't been remapped since it was removed ๐. This covers members that got unmapped because of a MOVE, UNBIND [RFC5842], or REBIND [RFC5842] operation. It also covers collection members that were removed, even the ones that don't support the DAV:sync-collection report themselves ๐ซถ
If a member was added (and its mapped resource possibly modified), then removed between two synchronization report requests, it MUST be reported as removed. This makes sure a client that added a member finds out about the removal, if the removal happens before the client gets a chance to run a synchronization report ๐ญ
A member MAY be reported as removed if the user issuing the request no longer has access to this member, due to access control changes ๐
For a report with the DAV:sync-level XML element value set to "infinite", where a collection is removed, the server MUST NOT report the removal of any members of the removed collection. clients MUST assume that if a collection is reported as being removed, then all members of that collection have also been removed ๐ธ
3.6. Truncation of Results ๐
A server MAY limit the number of member URLs in a response, for example to limit the amount of work it spends processing a request, or because the client set an explicit limit ๐ง If the result set is truncated, the response MUST use status code 207 (Multi-Status), return a DAV:multistatus response body, and indicate a status of 507 (Insufficient Storage) for the request-URI. That DAV:response element SHOULD include a DAV:error element with the DAV:number-of-matches-within-limits precondition, as defined in [RFC3744] (Section 9.2). DAV:response elements for all the changes being reported are also included โจ
When truncation happens, the DAV:sync-token value returned in the response MUST represent the correct state for the partial set of changes returned. That way the client can use the returned DAV:sync-token to fetch the next set of changes. The client can effectively "page" through the entire set of changes in a consistent manner, and that's giving organized energy ๐
Clients MUST handle the 507 status on the request-URI in the response to the report ๐ซถ
Here's an example. Say a server records changes using a strictly increasing integer as a "revision number", and uses that number as the DAV:sync-token value (encoded as a URI). The last DAV:sync-token the client used was http://example.com/sync/10, and since then 15 additional changes to different resources have happened. If the client runs a DAV:sync-collection request with a DAV:sync-token of http://example.com/sync/10, and there's no limit, the server would return 15 DAV:response elements and a DAV:sync-token with value http://example.com/sync/25. But if the server chooses to limit responses to at most 10 changes, it would return only 10 DAV:response elements and a DAV:sync-token with value http://example.com/sync/20, together with an extra DAV:response element for the request-URI with a status code of 507. Then the client can reissue the request with the DAV:sync-token value the server returned and fetch the remaining 5 changes. bestie, that's so clean ๐ฆ
3.7. Limiting Results ๐
A client can limit the number of results the server returns by using the DAV:limit element ([RFC5323], Section 5.17) in the request body. This is super useful when clients have limited space or bandwidth for the results ๐ If a server can't truncate the result at or below the requested number, then it MUST fail the request with a DAV:number-of-matches-within-limits postcondition error. When the server can correctly limit the results, it MUST follow the rules above for indicating a result set truncation to the client ๐ฅ
3.8. Example: Initial DAV:sync-collection Report ๐ง
In this example, the client makes its very first synchronization request, so the DAV:sync-token element in the request is empty ๐ It also asks for the DAV:getetag property and for a proprietary property. The server responds with the items currently in the targeted collection, and the current synchronization token comes back too ๐
>> Request <<
REPORT /home/cyrusdaboo/ HTTP/1.1
Host: webdav.example.com
Depth: 0
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:sync-level>1</D:sync-level>
<D:prop xmlns:R="urn:ns.example.com:boxschema">
<D:getetag/>
<R:bigbox/>
</D:prop>
</D:sync-collection>
>> 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:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/test.doc</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00001-abcd1"</D:getetag>
<R:bigbox xmlns:R="urn:ns.example.com:boxschema">
<R:BoxType>Box type A</R:BoxType>
</R:bigbox>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/vcard.vcf</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00002-abcd1"</D:getetag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<R:bigbox xmlns:R="urn:ns.example.com:boxschema"/>
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/calendar.ics</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00003-abcd1"</D:getetag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<R:bigbox xmlns:R="urn:ns.example.com:boxschema"/>
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
</D:response>
<D:sync-token>http://example.com/ns/sync/1234</D:sync-token>
</D:multistatus>
3.9. Example: DAV:sync-collection Report with Token ๐ท
In this example, the client makes a synchronization request and uses the DAV:sync-token element it got back from the last report it ran on this collection ๐ซง The server responds by listing the items that were added, changed, or removed. The (new) current synchronization token comes back too ๐
>> Request <<
REPORT /home/cyrusdaboo/ HTTP/1.1
Host: webdav.example.com
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>http://example.com/ns/sync/1234</D:sync-token>
<D:sync-level>1</D:sync-level>
<D:prop xmlns:R="urn:ns.example.com:boxschema">
<D:getetag/>
<R:bigbox/>
</D:prop>
</D:sync-collection>
>> 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:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/file.xml</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00004-abcd1"</D:getetag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<R:bigbox xmlns:R="urn:ns.example.com:boxschema"/>
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/vcard.vcf</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00002-abcd2"</D:getetag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<R:bigbox xmlns:R="urn:ns.example.com:boxschema"/>
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/test.doc</D:href>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:response>
<D:sync-token>http://example.com/ns/sync/1238</D:sync-token>
</D:multistatus>
3.10. Example: Initial DAV:sync-collection Report with Truncation ๐ญ
In this example, the client makes its first synchronization request, 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, but truncated at two items. The synchronization token for the truncated result set comes back too, and the client is so ready to page through the rest ๐
>> Request <<
REPORT /home/cyrusdaboo/ HTTP/1.1
Host: webdav.example.com
Depth: 0
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:sync-level>1</D:sync-level>
<D:prop>
<D:getetag/>
</D:prop>
</D:sync-collection>
>> 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:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/test.doc</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00001-abcd1"</D:getetag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/vcard.vcf</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00002-abcd1"</D:getetag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/</D:href>
<D:status>HTTP/1.1 507 Insufficient Storage</D:status>
<D:error><D:number-of-matches-within-limits/></D:error>
</D:response>
<D:sync-token>http://example.com/ns/sync/1233</D:sync-token>
</D:multistatus>
3.11. Example: Initial DAV:sync-collection Report with Limit โจ
okay so this is the client's very first sync ๐ธ which means the DAV:sync-token in the request is totally empty, because there's nothing to compare against yet. she asks for a limit of 1 result and wants DAV:getetag too. the server gives back what's in the collection right now but cuts it off at one item, and the sync token for that truncated batch comes back with it ๐ซถ
Request <<
REPORT /home/cyrusdaboo/ HTTP/1.1
Host: webdav.example.com
Depth: 0
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:sync-level>1</D:sync-level>
<D:limit>
<D:nresults>1</D:nresults>
</D:limit>
<D:prop>
<D:getetag/>
</D:prop>
</D:sync-collection>
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:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/test.doc</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00001-abcd1"</D:getetag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href
>http://webdav.example.com/home/cyrusdaboo/</D:href>
<D:status>HTTP/1.1 507 Insufficient Storage</D:status>
<D:error><D:number-of-matches-within-limits/></D:error>
</D:response>
<D:sync-token>http://example.com/ns/sync/1232</D:sync-token>
</D:multistatus>
3.12. Example: DAV:sync-collection Report with Unsupported Limit ๐
this time the client already has a valid DAV:sync-token and asks for a limit of 100 results, plus DAV:getetag. the server can't keep the results to that max, so it sends back a 507 status and the right postcondition error code. not it ๐
Request <<
REPORT /home/cyrusdaboo/ HTTP/1.1
Host: webdav.example.com
Depth: 0
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>http://example.com/ns/sync/1232</D:sync-token>
<D:sync-level>1</D:sync-level>
<D:limit>
<D:nresults>100</D:nresults>
</D:limit>
<D:prop>
<D:getetag/>
</D:prop>
</D:sync-collection>
Response <<
HTTP/1.1 507 Insufficient Storage
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
<?xml version="1.0" encoding="utf-8" ?>
<D:error xmlns:D="DAV:">
<D:number-of-matches-within-limits/>
</D:error>
3.13. Example: DAV:sync-level Set to Infinite, Initial DAV:sync-collection Report ๐ฆ
first sync again (so the DAV:sync-token is empty), but this time the level is "infinite". she asks for DAV:getetag and a proprietary property. the server returns everything currently in the collection, plus the current sync token ๐
the setup: /home/cyrusdaboo/collection1/ exists and has one child that also gets reported. /home/cyrusdaboo/collection2/ exists but is empty. /home/cyrusdaboo/shared/ comes back with a 403, which basically means "yes, I'm a collection, but I can't report on what's inside me within an infinite-level report." the client can just try a DAV:sync-collection report directly on that collection's URI instead ๐ง
Request <<
REPORT /home/cyrusdaboo/ HTTP/1.1
Host: webdav.example.com
Depth: 0
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:sync-level>infinite</D:sync-level>
<D:prop xmlns:R="urn:ns.example.com:boxschema">
<D:getetag/>
<R:bigbox/>
</D:prop>
</D:sync-collection>
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:response>
<D:href>/home/cyrusdaboo/collection1/</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00001-abcd1"</D:getetag>
<R:bigbox xmlns:R="urn:ns.example.com:boxschema">
<R:BoxType>Box type A</R:BoxType>
</R:bigbox>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href>/home/cyrusdaboo/collection1/test.doc</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00001-abcd1"</D:getetag>
<R:bigbox xmlns:R="urn:ns.example.com:boxschema">
<R:BoxType>Box type A</R:BoxType>
</R:bigbox>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href>/home/cyrusdaboo/collection2/</D:href>
<D:propstat>
<D:prop>
<D:getetag/>
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<R:bigbox xmlns:R="urn:ns.example.com:boxschema"/>
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href>/home/cyrusdaboo/calendar.ics</D:href>
<D:propstat>
<D:prop>
<D:getetag>"00003-abcd1"</D:getetag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<R:bigbox xmlns:R="urn:ns.example.com:boxschema"/>
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
</D:response>
<D:response>
<D:href>/home/cyrusdaboo/shared/</D:href>
<D:status>HTTP/1.1 403 Forbidden</D:status>
<D:error><D:sync-traversal-supported/></D:error>
</D:response>
<D:sync-token>http://example.com/ns/sync/1234</D:sync-token>
</D:multistatus>
4. DAV:sync-token Property ๐
- Name:
sync-token - Namespace:
DAV: - Purpose: this one holds the sync token value, exactly as a
DAV:sync-collectionreport would return it ๐ - Value: any valid URI ๐
- Protected: MUST be protected, because the server is the one creating and controlling this value ๐
- COPY/MOVE behavior: the value depends on the destination resource's final state, not on what the property said on the source resource ๐
okay the big rules: the DAV:sync-token property MUST be defined on every resource that supports the DAV:sync-collection report. its value is the sync token that a DAV:sync-collection report on that resource would give back at that same moment in time โฐ. it SHOULD NOT show up in a PROPFIND DAV:allprop request (see Section 14.2 of [RFC4918]), so don't let it crash the party ๐
- Definition:
<!ELEMENT sync-token #PCDATA>
<!-- Text MUST be a valid URI -->
5. DAV:sync-token Use with If Header ๐
WebDAV has an If precondition header that lets "state tokens" be used as preconditions on HTTP requests (see Section 10.4 of [RFC4918]). this spec says the DAV:sync-token value can be one of those tokens inside an If header ๐. so clients can make sure a request only goes through when the collection's content hasn't changed, because the DAV:sync-token is still the same ๐ซถ. servers MUST support using DAV:sync-token values in If request headers, no exceptions bestie ๐ค
5.1. Example: If Precondition with PUT ๐ธ
the client already synced /home/cyrusdaboo/collection/ with the DAV:sync-collection report. now it wants to add a new resource, but only if nothing else changed since the last sync ๐ค
heads up: because the DAV:sync-token lives on the collection and not on the resource in the request, the If header has to use the "Resource_Tag" construct so it's clear the state token belongs to the collection ๐ฆ
Request <<
PUT /home/cyrusdaboo/collection/newresource.txt HTTP/1.1
Host: webdav.example.com
If: </home/cyrusdaboo/collection/>
(<http://example.com/ns/sync/12345>)
Content-Type: text/plain; charset="utf-8"
Content-Length: xxxx
Some content here...
Response <<
HTTP/1.1 201 Created
5.2. Example: If Precondition with MKCOL ๐
same vibe: the client already synced /home/cyrusdaboo/collection/, and now it wants to make a new collection inside it, but only if there have been no other changes since last sync ๐. again the If header uses "Resource_Tag" for the same reason, because the token belongs to the collection and not the resource being targeted. this time the request fails, since another change already happened to the collection that the supplied DAV:sync-token matches ๐
Request <<
MKCOL /home/cyrusdaboo/collection/child/ HTTP/1.1
Host: webdav.example.com
If: </home/cyrusdaboo/collection/>
(<http://example.com/ns/sync/12346>)
Response <<
HTTP/1.1 412 Precondition Failed
6. XML Element Definitions ๐ง
6.1. DAV:sync-collection XML Element ๐
- Name:
sync-collection - Namespace:
DAV: - Purpose: a WebDAV report that lets the client and server sync up data ๐
- Description: see Section 3 โจ
<!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)>
<!-- DAV:limit defined in RFC 5323, Section 5.17 -->
<!-- DAV:prop defined in RFC 4918, Section 14.18 -->
6.2. DAV:sync-token XML Element ๐ฆ
- Name:
sync-token - Namespace:
DAV: - Purpose: the sync token the server provides and the client sends back ๐
- Description: see Section 3 โจ
<!ELEMENT sync-token CDATA>
<!-- Text MUST be a URI -->
6.3. DAV:sync-level XML Element ๐ซ
- Name:
sync-level - Namespace:
DAV: - Purpose: shows the "scope" of the synchronization report request ๐ญ
- Description: see Section 3.3 โจ
<!ELEMENT sync-level CDATA>
<!-- Text MUST be either "1" or "infinite" -->
6.4. DAV:multistatus XML Element ๐
- Name:
multistatus - Namespace:
DAV: - Purpose: extends the DAV:multistatus element so it can carry sync details ๐
- Description: see Section 3 โจ
<!ELEMENT multistatus (response*, responsedescription?,
sync-token?) >
<!-- DAV:multistatus originally defined in RFC 4918, Section 14.16
but overridden here to add the DAV:sync-token element -->
<!-- DAV:response defined in RFC 4918, Section 14.24 -->
<!-- DAV:responsedescription defined in RFC 4918, Section 14.25 -->
7. Security Considerations ๐
okay, real talk for a sec ๐ซถ: this extension does not introduce any new security concerns beyond the ones already described in HTTP and WebDAV. so the security stuff is basically inherited from those, and you still have to take those seriously, periodt ๐
8. Acknowledgments โจ
Huge shoutout to the people who helped shape this spec with their ideas and support: Bernard Desruisseaux, Werner Donne, Mike Douglass, Ciny Joy, Andrew McMillan, Julian Reschke, and Wilfredo Sanchez ๐ซถ We also owe a big thank-you to the Calendaring and Scheduling Consortium, who set up interoperability testing for the early implementations. Iconic team behavior ๐
9. References
9.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.
Whitehead, "Versioning Extensions to WebDAV
(Web Distributed Authoring and Versioning)", 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., "HTTP Extensions for Web Distributed
Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
[RFC5323] Reschke, J., Reddy, S., Davis, J., and A. Babich, "Web
Distributed Authoring and Versioning (WebDAV) SEARCH",
RFC 5323, November 2008.
[W3C.REC-xml-20081126]
Sperberg-McQueen, C., Yergeau, F., Paoli, J., Maler, E.,
and T. Bray, "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>.
9.2. Informative References
[RFC4791] Daboo, C., Desruisseaux, B., and L. Dusseault,
"Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,
March 2007.
[RFC5842] Clemm, G., Crawford, J., Reschke, J., and J. Whitehead,
"Binding Extensions to Web Distributed Authoring and
Versioning (WebDAV)", RFC 5842, April 2010.
[RFC6352] Daboo, C., "CardDAV: vCard Extensions to Web Distributed
Authoring and Versioning (WebDAV)", RFC 6352, August 2011.
Appendix A. Backwards-Compatible Handling of Depth ๐
Okay so back in earlier draft versions of this spec, the Depth request header was doing the job that the DAV:sync-level element does now, which was telling the server the "scope" of the sync request ๐ง If you're a server and you want to stay friends with older clients that follow that earlier spec, here's the move: if there's no DAV:sync-level element in the request body, just use the Depth header value as the stand-in for the missing DAV:sync-level ๐
Appendix B. Example of a Client Synchronization Approach ๐ฆ
This appendix is a little walkthrough of how a client might run collection sync with the WebDAV sync report from this spec. Quick disclaimer though: it's just an example and definitely NOT a normative "algorithm" you have to follow for client sync ๐
Here's the setup. We've got a WebDAV client talking to a WebDAV server that supports the sync report. The client keeps a local cache of resources in one target collection, "/collection/". Local changes are assumed to not happen, and the client only cares about changes to the immediate children of that collection. Simple vibes ๐ธ
Initial State
The client starts with an empty local cache, and it has no DAV:sync-token stored for "/collection/" yet. Blank slate bestie โจ
Initial Synchronization
The client sends a sync report to the server with an empty DAV:sync-token element and DAV:sync-level set to "1". It also asks the server to hand back the DAV:getetag WebDAV property for each resource reported.
The server replies with the current list of resources (along with their DAV:getetag properties) plus a brand new DAV:sync-token value. The client attaches that new token to the collection ๐
Next, for every reported resource, the client builds a set of (resource path, DAV:getetag) tuples. For each tuple, it fires off an HTTP GET to pull down the content, then updates that resource's (resource path, DAV:getetag) entry in its local cache using the ETag response header value that came back from the GET ๐
Routine Synchronization
Now for the everyday sync ๐ The client sends a sync report with DAV:sync-token set to whatever it cached from the last sync, DAV:sync-level set to "1", and a request for DAV:getetag on each reported resource.
The server sends back the list of changes and a new DAV:sync-token value. The client saves that new token to the collection, same as before.
Process Removed Resources
Any resource reported with a 404 response status is gone, so the client removes it from its local cache ๐๏ธ
Process Resources
For each remaining reported resource, the client builds a fresh set of (resource path, DAV:getetag) tuples. Then it compares. Anything that's in the new set but not in the current cache is new, and anything in both the new set and the cache but with a different DAV:getetag value has changed. For each of those, the client does an HTTP GET to fetch the content and updates its cache entry with the ETag response header value from that GET. Cache glow-up complete ๐
Authors' Addresses ๐ฎ
Cyrus Daboo
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
USA
EMail: cyrus@daboo.name
URI: http://www.apple.com/
Arnaud Quillaud
Oracle Corporation
180, Avenue de l'Europe
Saint Ismier cedex 38334
France
EMail: arnaud.quillaud@oracle.com
URI: http://www.oracle.com/
๐ฆ where this page comes from
- ietf: rfc6578 2012-03
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms