RFC 8144

Use of the Prefer Header Field in Web Distributed Authoring and Versioning (WebDAV)

πŸ“… published April 2017, from ietf

Use of the Prefer Header Field in Web Distributed Authoring and Versioning (WebDAV) πŸ’–

Okay bestie, this is RFC 8144 and the title block has the main tea ✨ It was written by K. Murchison from CMU, it's a Standards Track doc (so it's the real deal πŸ’…), and it's dated April 2017. Its ISSN is 2070-1721. The big thing is that it updates RFC 7240, the one that defines the Prefer header in the first place πŸŽ€. The title block doesn't list any documents it obsoletes.

Abstract ✨

So the abstract is the quick pitch πŸ¦‹ This doc explains how a WebDAV client can use the Prefer header field (from RFC 7240) to ask a server to behave a certain way while it's building a response to a request. It also creates a brand new preference called "depth-noroot" 🌸

And yes, this doc updates RFC 7240, as mentioned up top πŸ’…

Status of This Memo 🌈

Okay, the official status stuff, but still cute. This is an Internet Standards Track document. It's a product of the Internet Engineering Task Force (IETF), it represents the consensus of the IETF community, and it got public review and was approved for publication by the Internet Engineering Steering Group (IESG). If you want more on Internet Standards, check Section 2 of RFC 7841. For the current status, any errata, and how to give feedback, go to http://www.rfc-editor.org/info/rfc8144 πŸ”—

Copyright (c) 2017 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. Please read those carefully, because they spell out your rights and restrictions for 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.

Table of Contents πŸ—‚οΈ

   1. Introduction ....................................................3
      1.1. Notational Conventions .....................................3
   2. Reducing WebDAV Response Verbosity with "return=minimal" ........3
      2.1. Minimal PROPFIND and REPORT Responses ......................4
      2.2. Minimal PROPPATCH Response .................................5
      2.3. Minimal MKCALENDAR and MKCOL Responses .....................5
   3. Reducing WebDAV Roundtrips with "return=representation" .........6
      3.1. Successful State-Changing Requests .........................6
      3.2. Unsuccessful Conditional State-Changing Requests ...........6
   4. The "depth-noroot" Processing Preference ........................7
   5. Security Considerations .........................................7
   6. IANA Considerations .............................................8
      6.1. Preference Registration ....................................8
      6.2. Method References ..........................................8
      6.3. Status Code References .....................................9
   7. References ......................................................9
      7.1. Normative References .......................................9
      7.2. Informative References ....................................10
   Appendix A.  The Brief and Extended Depth Header Fields ...........12
   Appendix B.  Examples .............................................12
     B.1.  PROPFIND ..................................................12
     B.2.  REPORT ....................................................16
     B.3.  PROPPATCH .................................................21
     B.4.  MKCOL .....................................................22
     B.5.  POST ......................................................23
     B.6.  PUT .......................................................27
   Acknowledgements ..................................................28
   Author's Address ..................................................28

1. Introduction 🌸

Okay, so [RFC7240] defines the Prefer header field and the "return=minimal" preference. That preference says the client wants the server to send back a minimal response to a successful request, but it leaves what counts as "minimal" totally up to the server πŸ€·β€β™€οΈ Section 2 of this spec is where we get precise about what a server is expected to do when it builds minimal responses to successful WebDAV [RFC4918] requests. Main character energy πŸ‘‘

[RFC7240] also defines the "return=representation" preference. That one means the client wants the server to include an entity showing the current state of the resource in the response to a successful request. Section 3 of this spec gives recommendations on when clients should use it, and it also stretches it to cover 412 (Precondition Failed) [RFC7232] responses ✨

Last but not least, Section 4 defines the "depth-noroot" preference, which can be used with HTTP methods that support the Depth header field 🧚

1.1. Notational Conventions πŸ“

Here's the key word lowdown. 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 doc also references XML element types in the "DAV:" [RFC4918], "urn:ietf:params:xml:ns:caldav" [RFC4791], and "urn:ietf:params:xml:ns:carddav" [RFC6352] namespaces, but only outside the context of an XML fragment. When it does that, the strings "DAV:", "CALDAV:", and "CARDDAV:" get prepended to the XML element types, respectively πŸŽ€

2. Reducing WebDAV Response Verbosity with "return=minimal" 🌷

Some payload bodies in WebDAV responses, like 207 (Multi-Status) [RFC4918] responses, can be super verbose or even unnecessary sometimes 😭 This spec shows how the Prefer header field, paired with its "return=minimal" preference, lets clients cut down that verbosity. The client asks the server to leave out any parts of the response that can be figured out just from their absence 🫢

2.1. Minimal PROPFIND and REPORT Responses πŸ”

When a PROPFIND [RFC4918] request, or a REPORT [RFC3253] request whose report type results in a 207 (Multi-Status) response, includes a Prefer header field with a preference of "return=minimal", the server SHOULD omit all DAV:propstat XML elements that contain a DAV:status XML element of value 404 (Not Found) [RFC7231] from the 207 (Multi-Status) response. If leaving out such a DAV:propstat element would leave a DAV:response XML element with zero DAV:propstat elements, then the server MUST substitute one of the following in its place:

  • a DAV:propstat element consisting of an empty DAV:prop element and a DAV:status element of value 200 (OK) [RFC7231]
  • a DAV:status element of value 200 (OK)

The following report types are candidates that could benefit from the "return=minimal" preference. NOTE: This list is not intended to be normative or exhaustive.

  • DAV:expand-property [RFC3253]
  • DAV:acl-principal-prop-set [RFC3744]
  • DAV:principal-property-search [RFC3744]
  • DAV:sync-collection [RFC6578]
  • CALDAV:calendar-query [RFC4791]
  • CALDAV:calendar-multiget [RFC4791]
  • CARDDAV:addressbook-query [RFC6352]
  • CARDDAV:addressbook-multiget [RFC6352]

See Appendices B.1 and B.2 for examples πŸ’–

2.2. Minimal PROPPATCH Response ✏️

When a PROPPATCH [RFC4918] request contains a Prefer header field with a preference of "return=minimal", and all instructions are processed successfully, the server SHOULD return one of the following responses instead of a 207 (Multi-Status) response:

  • 204 (No Content) [RFC7231]
  • 200 (OK) [RFC7231] (preferably with a zero-length message body)

See Appendix B.3 for examples 🌸

2.3. Minimal MKCALENDAR and MKCOL Responses πŸ“…

Both the MKCALENDAR [RFC4791] and Extended MKCOL [RFC5689] specs say a server MAY return a message body in response to a successful request. This spec spells out the intended behavior when the Prefer header field is present 🧚

When a MKCALENDAR or an extended MKCOL request contains a Prefer header field with a preference of "return=minimal", and the collection is created with all requested properties set successfully, the server SHOULD return a 201 (Created) [RFC7231] response with an empty (zero-length) message body ✨

Quick note: the reasoning behind wanting a minimal success response to have an empty body is twofold πŸ€“

  • [RFC4791], Section 5.3.1 states: "If a response body for a successful request is included, it MUST be a CALDAV:mkcalendar-response XML element."
  • [RFC5689], Section 3 states: "When an empty response body is returned with a success request status code, the client can assume that all properties were set."

See Appendix B.4 for examples πŸ“

3. Reducing WebDAV Roundtrips with "return=representation" πŸ”

[RFC7240] describes the "return=representation" preference as a way to optimize communication between client and server. It cuts out the need for a follow-up GET request to fetch the current representation of the resource after a modification. This preference works just as well when the server modifies a resource itself, and when another client has modified it 🫢

3.1. Successful State-Changing Requests πŸ’…

The state-changing methods PUT [RFC7231], COPY/MOVE [RFC4918], PATCH [RFC5789], and POST [RFC5995] can be used to create or update a resource. Sometimes, like with Calendaring Extensions to WebDAV (CalDAV) Scheduling [RFC6638], the created or updated representation is different from what was sent in the request body or from what the effective request URI points to. So when the client gets a 2xx (Successful) [RFC7231] response to a state-changing request and would normally send a GET request next to fetch the current representation, the client can instead put a Prefer header field with the "return=representation" preference right in the state-changing request πŸŽ€

When a state-changing request includes a Prefer header field with a preference of "return=representation", and the resource is created or updated successfully, the server SHOULD include an entity showing the current state of the resource in the resulting 201 (Created) or 200 (OK) [RFC7231] response. Not only does this squish the create/update and retrieve steps into one roundtrip, but getting the current representation back in the response also means the client knows any changes were made by the server and not by a concurrent client. That gives the operation a level of atomicity πŸ”₯

See Appendix B.5 for examples πŸ¦‹

3.2. Unsuccessful Conditional State-Changing Requests πŸ™ˆ

okay so, clients using a state-changing method like the ones above often make their request conditional by adding an If-Match or If-None-Match [RFC7232] header πŸ’… why? to stop them from accidentally overwriting a resource that someone else changed in parallel 😭 and if the server replies with a 412 (Precondition Failed) [RFC7232], the client would normally send a GET to grab the current representation. bestie, it doesn't have to! it can just add a Prefer header with the "return=representation" preference to the conditional request in the first place 🫢 one less roundtrip, iconic.

When a conditional state-changing request has a Prefer header with a preference of "return=representation" and the condition evaluates to false, the server SHOULD include an entity representing the current state of the resource in the resulting 412 (Precondition Failed) [RFC7232] response ✨

See Appendix B.6 for examples. πŸ“


4. The "depth-noroot" Processing Preference πŸ¦‹

the "depth-noroot" preference is basically the client saying "hey server, please skip the target (root) resource when you run the HTTP method, and only apply it to the target resource's subordinate resources" 🌸 the vibe is very "not me, I'm not doing the parent, just the kids" πŸ’€

this preference is only meant for HTTP methods whose definitions explicitly support the Depth [RFC4918] header field. also, it only applies when Depth is 1 or infinity, either implicitly or explicitly. main character energy, but make it specific πŸ‘‘

The "depth-noroot" preference MAY be used in conjunction with the "return=minimal" preference in a single request. πŸ’–

See Appendix B.1 for examples. ✨


5. Security Considerations 🚨

okay bestie, serious moment πŸ’€ using the Prefer header with WebDAV requests does NOT introduce any new security considerations. the only ones are the ones already discussed in [RFC7240], plus the ones already inherent in those requests. no new drama, but the old drama is still very much in the chat 😀


6. IANA Considerations πŸ“‹

6.1. Preference Registration

the following preference has been added to the HTTP Preferences Registry defined in Section 5.1 of [RFC7240] πŸŽ€

Preference:  depth-noroot

Description:  The "depth-noroot" preference indicates that the client
   wishes for the server to exclude the target (root) resource from
   processing by the HTTP method and only apply the HTTP method to
   the target resource's subordinate resources.

Reference:  RFC 8144, Section 4

Notes:  This preference is only intended to be used with HTTP methods
   whose definitions explicitly provide support for the Depth
   [RFC4918] header field.  Furthermore, this preference only applies
   when the Depth header field has a value of "1" or "infinity"
   (either implicitly or explicitly).

6.2. Method References

the following methods have had their references updated in the "HTTP Method Registry" (http://www.iana.org/assignments/http-methods) 🧚

+------------+------+------------+----------------------------------+
| Method     | Safe | Idempotent | References                       |
| Name       |      |            |                                  |
+------------+------+------------+----------------------------------+
| MKCALENDAR | no   | yes        | RFC 4791, Section 5.3.1; RFC     |
|            |      |            | 8144, Section 2.3                |
| MKCOL      | no   | yes        | RFC 4918, Section 9.3; RFC 5689, |
|            |      |            | Section 3; RFC 8144, Section 2.3 |
| PROPFIND   | yes  | yes        | RFC 4918, Section 9.1; RFC 8144, |
|            |      |            | Section 2.1                      |
| PROPPATCH  | no   | yes        | RFC 4918, Section 9.2; RFC 8144, |
|            |      |            | Section 2.2                      |
| REPORT     | yes  | yes        | RFC 3253, Section 3.6; RFC 8144, |
|            |      |            | Section 2.1                      |
+------------+------+------------+----------------------------------+

6.3. Status Code References

the following status code has had its references updated in the "HTTP Status Codes" registry (http://www.iana.org/assignments/http-status-codes) 🌈

+-------+-------------------+---------------------------------------+
| Value | Description       | References                            |
+-------+-------------------+---------------------------------------+
| 412   | Precondition      | RFC 7232, Section 4.2; RFC 8144,      |
|       | Failed            | Section 3.2                           |
+-------+-------------------+---------------------------------------+

7. References πŸ“š

7.1. Normative References

7.2. Informative References


Appendix A. The Brief and Extended Depth Header Fields πŸŽ€

this doc is based heavily on the Brief [MSDN.aa563501] and extended Depth [MSDN.aa563950] header fields 🫢 the behaviours in Sections 2.1 and 2.2 are identical to what Brief gives you with PROPFIND [MSDN.aa580336] and PROPPATCH [MSDN.aa493854] respectively, and the behaviour in Section 4 is identical to the 1,noroot [MSDN.aa563950] and infinity,noroot [MSDN.aa563950] Depth values. iconic lineage πŸ‘‘

client and server implementations that already support the Brief header can add support for the return=minimal preference with nominal effort πŸ™Œ

if a server supporting the Prefer header receives both the Brief and Prefer headers in a request, clients can expect the server to ignore the Brief header and only use the Prefer header preferences. πŸ’€

Appendix B. Examples 🧚

B.1. PROPFIND

B.1.1. Typical PROPFIND Request/Response with Depth:1 ✨

okay so this one is giving "one real property, one fake property" πŸ™ˆ it asks for one known property and one unknown property from the child resources πŸ’…

>> Request <<

PROPFIND /container/ HTTP/1.1
Host: webdav.example.com
Content-Type: application/xml; charset=utf-8
Content-Length: 189
Depth: 1

<?xml version="1.0" encoding="UTF-8"?>
<D:propfind xmlns:D="DAV:" xmlns:X="http://ns.example.com/foobar/">
  <D:prop>
    <D:resourcetype/>
    <X:foobar/>
  </D:prop>
</D:propfind>

>> Response <<

HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset=utf-8
Content-Length: 1722

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:"
               xmlns:X="http://ns.example.com/foobar/">
  <D:response>
    <D:href>/container/</D:href>
    <D:propstat>
      <D:prop>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <X:foobar/>
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>/container/work/</D:href>
    <D:propstat>
      <D:prop>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <X:foobar/>
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>/container/home/</D:href>
    <D:propstat>
      <D:prop>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <X:foobar/>
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>/container/foo.txt</D:href>
    <D:propstat>
      <D:prop>
        <D:resourcetype/>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <X:foobar/>
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

B.1.2. Minimal PROPFIND Request/Response with Depth:1 πŸ’–

this one is the minimal glow-up 🧚 same vibe as before, one real property and one fake one, but this time it's only the child resources that get the spotlight 🌸

>> Request <<

PROPFIND /container/ HTTP/1.1
Host: webdav.example.com
Content-Type: application/xml; charset=utf-8
Content-Length: 189
Depth: 1
Prefer: return=minimal, depth-noroot

<?xml version="1.0" encoding="UTF-8"?>
<D:propfind xmlns:D="DAV:" xmlns:X="http://ns.example.com/foobar/">
  <D:prop>
    <D:resourcetype/>
    <X:foobar/>
  </D:prop>
</D:propfind>

>> Response <<

HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset=utf-8
Content-Length: 837
Preference-Applied: return=minimal, depth-noroot

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>/container/work/</D:href>
    <D:propstat>
      <D:prop>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>/container/home/</D:href>
    <D:propstat>
      <D:prop>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>/container/foo.txt</D:href>
    <D:propstat>
      <D:prop>
        <D:resourcetype/>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

B.1.3. Minimal PROPFIND Request/Response with an Empty DAV:propstat Element πŸ¦‹

bestie, this one is so iconic 😭 it goes for one unknown property from a collection, and the response comes back with a totally empty DAV:propstat element. no cap, very minimal, very aesthetic 🫢

>> Request <<

PROPFIND /container/ HTTP/1.1
Host: webdav.example.com
Content-Type: application/xml; charset=utf-8
Content-Length: 166
Prefer: return=minimal

<?xml version="1.0" encoding="UTF-8"?>
<D:propfind xmlns:D="DAV:" xmlns:X="http://ns.example.com/foobar/">
  <D:prop>
    <X:foobar/>
  </D:prop>
</D:propfind>

>> Response <<

HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset=utf-8
Content-Length: 255
Preference-Applied: return=minimal

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>/container/</D:href>
    <D:propstat>
      <D:prop/>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

B.2. REPORT πŸŽ€

B.2.1. Typical REPORT Request/Response πŸ‘‘

this example is giving main character energy πŸ’… it tries to grab an unknown property from a bunch of resources using the DAV:expand-property [RFC3253] REPORT type. so many nested properties, periodt 😭

>> Request <<

REPORT /dav/principals/ HTTP/1.1
Host: webdav.example.com
Content-type: text/xml; charset=utf-8
Content-length: 847

<?xml version="1.0" encoding="utf-8"?>
<D:expand-property xmlns:D="DAV:">
  <D:property name="current-user-principal">
    <D:property name="resourcetype"/>
    <D:property name="displayname"/>
    <D:property name="foobar"
                namespace="http://ns.example.com/foobar"/>
    <D:property name="calendar-home-set"
                namespace="urn:ietf:params:xml:ns:caldav">
      <D:property name="resourcetype"/>
      <D:property name="foobar"
                  namespace="http://ns.example.com/foobar"/>
    </D:property>
    <D:property name="addressbook-home-set"
                namespace="urn:ietf:params:xml:ns:carddav">
      <D:property name="resourcetype"/>
      <D:property name="foobar"
                  namespace="http://ns.example.com/foobar"/>
    </D:property>
  </D:property>
</D:expand-property>

>> Response <<

HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset=utf-8
Content-Length: 2664

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:"
               xmlns:C="urn:ietf:params:xml:ns:caldav"
               xmlns:R="urn:ietf:params:xml:ns:carddav"
               xmlns:X="http://ns.example.com/foobar">
  <D:response>
    <D:href>/dav/principals/</D:href>
    <D:propstat>
      <D:prop>
        <D:current-user-principal>
          <D:response>
            <D:href>/dav/principals/user/ken/</D:href>
            <D:propstat>
              <D:prop>
                <D:resourcetype>
                  <D:principal/>

                </D:resourcetype>
                <D:displayname>ken</D:displayname>
                <C:calendar-home-set>
                  <D:response>
                    <D:href>/dav/calendars/user/ken/</D:href>
                    <D:propstat>
                      <D:prop>
                        <D:resourcetype>
                          <D:collection/>
                        </D:resourcetype>
                      </D:prop>
                      <D:status>HTTP/1.1 200 OK</D:status>
                    </D:propstat>
                    <D:propstat>
                      <D:prop>
                        <X:foobar/>
                      </D:prop>
                      <D:status>HTTP/1.1 404 Not Found</D:status>
                    </D:propstat>
                  </D:response>
                </C:calendar-home-set>
                <R:addressbook-home-set>
                  <D:response>
                    <D:href>/dav/addressbooks/user/ken/</D:href>
                    <D:propstat>
                      <D:prop>
                        <D:resourcetype>
                          <D:collection/>
                        </D:resourcetype>
                      </D:prop>
                      <D:status>HTTP/1.1 200 OK</D:status>
                    </D:propstat>
                    <D:propstat>
                      <D:prop>
                        <X:foobar/>
                      </D:prop>
                      <D:status>HTTP/1.1 404 Not Found</D:status>
                    </D:propstat>
                  </D:response>
                </R:addressbook-home-set>
              </D:prop>
              <D:status>HTTP/1.1 200 OK</D:status>
            </D:propstat>
            <D:propstat>
              <D:prop>
                <X:foobar/>
              </D:prop>
              <D:status>HTTP/1.1 404 Not Found</D:status>

            </D:propstat>
          </D:response>
        </D:current-user-principal>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

B.2.2. Minimal REPORT Request/Response πŸ¦‹

okay so this example tries to grab an unknown property (foobar πŸ‘€) from a bunch of resources at once, using the DAV:expand-property [RFC3253] REPORT type πŸ’…

Request <<
REPORT /dav/principals/ HTTP/1.1
Host: webdav.example.com
Content-Type: application/xml; charset=utf-8
Content-Length: 847
Prefer: return=minimal

<?xml version="1.0" encoding="utf-8"?>
<D:expand-property xmlns:D="DAV:">
  <D:property name="current-user-principal">
    <D:property name="resourcetype"/>
    <D:property name="displayname"/>
    <D:property name="foobar"
                namespace="http://ns.example.com/foobar"/>
    <D:property name="calendar-home-set"
                namespace="urn:ietf:params:xml:ns:caldav">
      <D:property name="resourcetype"/>
      <D:property name="foobar"
                  namespace="http://ns.example.com/foobar"/>
    </D:property>
    <D:property name="addressbook-home-set"
                namespace="urn:ietf:params:xml:ns:carddav">
      <D:property name="resourcetype"/>
      <D:property name="foobar"
                  namespace="http://ns.example.com/foobar"/>
    </D:property>
  </D:property>
</D:expand-property>
Response <<
HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset=utf-8
Content-Length: 1998
Preference-Applied: return=minimal

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:"
               xmlns:C="urn:ietf:params:xml:ns:caldav"
               xmlns:R="urn:ietf:params:xml:ns:carddav"
               xmlns:X="http://ns.example.com/foobar">
  <D:response>
    <D:href>/dav/principals/</D:href>
    <D:propstat>
      <D:prop>
        <D:current-user-principal>
          <D:response>
            <D:href>/dav/principals/user/ken/</D:href>
            <D:propstat>
              <D:prop>
                <D:resourcetype>
                  <D:principal/>
                </D:resourcetype>
                <D:displayname>ken</D:displayname>
                <C:calendar-home-set>
                  <D:response>
                    <D:href>/dav/calendars/user/ken/</D:href>
                    <D:propstat>
                      <D:prop>
                        <D:resourcetype>
                          <D:collection/>
                        </D:resourcetype>
                      </D:prop>
                      <D:status>HTTP/1.1 200 OK</D:status>
                    </D:propstat>
                  </D:response>
                </C:calendar-home-set>
                <R:addressbook-home-set>
                  <D:response>
                    <D:href>/dav/addressbooks/user/ken/</D:href>
                    <D:propstat>
                      <D:prop>
                        <D:resourcetype>
                          <D:collection/>
                        </D:resourcetype>
                      </D:prop>
                      <D:status>HTTP/1.1 200 OK</D:status>
                    </D:propstat>
                  </D:response>
                </R:addressbook-home-set>
              </D:prop>
              <D:status>HTTP/1.1 200 OK</D:status>
            </D:propstat>
          </D:response>
        </D:current-user-principal>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

B.3. PROPPATCH πŸŽ€

B.3.1. Typical PROPPATCH Request/Response 🌸

Request <<
PROPPATCH /container/ HTTP/1.1
Host: webdav.example.com
Content-Type: application/xml; charset=utf-8
Content-Length: 199

<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
  <D:set>
    <D:prop>
      <D:displayname>My Container</D:displayname>
    </D:prop>
  </D:set>
</D:propertyupdate>
Response <<
HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset=utf-8
Content-Length: 297

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>/container/</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname/>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

B.3.2. Minimal PROPPATCH Request/Response 🧚

same energy as the one above, but this time the client adds Prefer: return=minimal and the server keeps its reply short and sweet πŸ’–

Request <<
PROPPATCH /container/ HTTP/1.1
Host: webdav.example.com
Content-Type: application/xml; charset=utf-8
Content-Length: 199
Prefer: return=minimal

<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
  <D:set>
    <D:prop>
      <D:displayname>My Container</D:displayname>
    </D:prop>
  </D:set>
</D:propertyupdate>
Response <<
HTTP/1.1 200 OK
Content-Length: 0
Preference-Applied: return=minimal

B.4. MKCOL πŸ“

B.4.1. Verbose MKCOL Request/Response πŸ“š

this one makes a brand new collection and gives it a displayname, and the response comes back with all the details 🫢

Request <<
MKCOL /container/ HTTP/1.1
Host: webdav.example.com
Content-Type: application/xml; charset=utf-8
Content-Length: 181

<?xml version="1.0" encoding="utf-8"?>
<D:mkcol xmlns:D="DAV:">
  <D:set>
    <D:prop>
      <D:displayname>My Container</D:displayname>
    </D:prop>
  </D:set>
</D:mkcol>
Response <<
HTTP/1.1 201 Created
Cache-Control: no-cache
Content-Type: application/xml; charset=utf-8
Content-Length: 224

<?xml version="1.0" encoding="utf-8"?>
<D:mkcol-response xmlns:D="DAV:">
  <D:propstat>
    <D:prop>
      <D:displayname/>
    </D:prop>
    <D:status>HTTP/1.1 200 OK</D:status>
  </D:propstat>
</D:mkcol-response>

B.4.2. Minimal MKCOL Request/Response ✨

same MKCOL, but with Prefer: return=minimal, so the response body is gone and it's just the status and headers 🌈

Request <<
MKCOL /container/ HTTP/1.1
Host: webdav.example.com
Content-Type: application/xml; charset=utf-8
Content-Length: 181
Prefer: return=minimal

<?xml version="1.0" encoding="utf-8"?>
<D:mkcol xmlns:D="DAV:">
  <D:set>
    <D:prop>
      <D:displayname>My Container</D:displayname>
    </D:prop>
  </D:set>
</D:mkcol>
Response <<
HTTP/1.1 201 Created
Cache-Control: no-cache
Content-Length: 0
Preference-Applied: return=minimal

B.5. POST 🌷

B.5.1. Typical Resource Creation and Retrieval via POST + GET πŸ’Œ

okay, important bit: this request is not conditional. Why? Because with the POST [RFC5995] method, the client lets the server pick the resource URI, so it's guaranteed not to touch an existing resource ✨

Request <<
POST /container/work;add-member/ HTTP/1.1
Host: caldav.example.com
Content-Type: text/calendar; charset=utf-8
Content-Length: 521

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:CD87465FA
SEQUENCE:0
DTSTAMP:20120602T185254Z
DTSTART:20120602T160000Z
DTEND:20120602T170000Z
TRANSP:OPAQUE
SUMMARY:Lunch
ORGANIZER;CN="Ken Murchison":mailto:murch@example.com
ATTENDEE;CN="Ken Murchison";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
 mailto:murch@example.com
ATTENDEE;CN="John Doe";CUTYPE=INDIVIDUAL;PARTSTAT
 =NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:jdoe@
 example.com
END:VEVENT
END:VCALENDAR
Response <<
HTTP/1.1 201 Created
Location: /container/work/abc.ics
Content-Length: 0

Note that the server did not include any validator header fields (e.g., ETag) in the response πŸ™ˆ, which signals that the created representation differs from the one sent in the body of the request. So the client has to send a separate GET request to grab the current representation:

Request <<
GET /container/work/abc.ics HTTP/1.1
Host: caldav.example.com
Response <<
HTTP/1.1 200 OK
Content-Type: text/calendar; charset=utf-8
Content-Length: 541
ETag: "nahduyejc"
Schedule-Tag: "jfd84hgbcn"

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Server//EN
BEGIN:VEVENT
UID:CD87465FA
SEQUENCE:0
DTSTAMP:20120602T185300Z
DTSTART:20120602T160000Z
DTEND:20120602T170000Z
TRANSP:OPAQUE
SUMMARY:Lunch
ORGANIZER;CN="Ken Murchison":mailto:murch@example.com
ATTENDEE;CN="Ken Murchison";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
 mailto:murch@example.com
ATTENDEE;CN="John Doe";CUTYPE=INDIVIDUAL;PARTSTAT
 =NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;SCHEDULE-STATUS=
 1.2:mailto:jdoe@example.com
END:VEVENT
END:VCALENDAR

B.5.2. Streamlined Resource Creation and Retrieval via POST πŸŽ€

same deal as before, but this time the client sends Prefer: return=representation so the server hands back the whole thing in one go 🧚

Note that this request is not conditional, because by using the POST [RFC5995] method, the client lets the server choose the resource URI, so it's guaranteed not to modify an existing resource πŸ’…

Request <<
POST /container/work;add-member/ HTTP/1.1
Host: caldav.example.com
Content-Type: text/calendar; charset=utf-8
Content-Length: 521
Prefer: return=representation

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:CD87465FA
SEQUENCE:0
DTSTAMP:20120602T185254Z
DTSTART:20120602T160000Z
DTEND:20120602T170000Z
TRANSP:OPAQUE
SUMMARY:Lunch
ORGANIZER;CN="Ken Murchison":mailto:murch@example.com
ATTENDEE;CN="Ken Murchison";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
 mailto:murch@example.com
ATTENDEE;CN="John Doe";CUTYPE=INDIVIDUAL;PARTSTAT
 =NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:jdoe@
 example.com
END:VEVENT
END:VCALENDAR
Response <<
HTTP/1.1 201 Created
Location: /container/work/abc.ics
Content-Type: text/calendar; charset=utf-8
Content-Length: 541
Content-Location: /container/work/abc.ics
ETag: "nahduyejc"
Schedule-Tag: "jfd84hgbcn"
Preference-Applied: return=representation

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Server//EN
BEGIN:VEVENT
UID:CD87465FA
SEQUENCE:0
DTSTAMP:20120602T185300Z
DTSTART:20120602T160000Z
DTEND:20120602T170000Z
TRANSP:OPAQUE
SUMMARY:Lunch
ORGANIZER;CN="Ken Murchison":mailto:murch@example.com
ATTENDEE;CN="Ken Murchison";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
 mailto:murch@example.com
ATTENDEE;CN="John Doe";CUTYPE=INDIVIDUAL;PARTSTAT
 =NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;SCHEDULE-STATUS=
 1.2:mailto:jdoe@example.com
END:VEVENT
END:VCALENDAR

B.6. PUT πŸ’œ

B.6.1. Typical Conditional Resource Update Failure and Retrieval via PUT + GET 😭

the client tries to update a file, but the If-Match precondition fails because someone else already changed it πŸ’€ so the client has to go grab the current version with a GET

Request <<
PUT /container/motd.txt HTTP/1.1
Host: dav.example.com
Content-Type: text/plain
Content-Length: 69
If-Match: "asd973"

Either write something worth reading or do something worth writing.
Response <<
HTTP/1.1 412 Precondition Failed
Content-Length: 0

The resource has been modified by another user agent (ETag mismatch); therefore, the client has to send a separate GET request to retrieve the current representation:

Request <<
GET /container/motd.txt HTTP/1.1
Host: dav.example.com
Response <<
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 52
ETag: "789sdas"

An investment in knowledge pays the best interest.

B.6.2. Streamlined Conditional Resource Update Failure and Retrieval via PUT πŸ¦‹

same failed update, but this time the client asks for Prefer: return=representation, so the 412 response already includes the current version 🫢

Request <<
PUT /container/motd.txt HTTP/1.1
Host: dav.example.com
Content-Type: text/plain
Content-Length: 69
If-Match: "asd973"
Prefer: return=representation

Either write something worth reading or do something worth writing.
Response <<
HTTP/1.1 412 Precondition Failed
Content-Type: text/plain
Content-Length: 52
Content-Location: /container/motd.txt
ETag: "789sdas"
Preference-Applied: return=representation

An investment in knowledge pays the best interest.

Acknowledgements ✨

okay bestie, the author gives a big shoutout to the people who helped make this spec happen 🫢 Cyrus Daboo, Helge Hess, Andrew McMillan, Arnaud Quillaud, and Julian Reschke all contributed their ideas and support πŸ’–

and the author also thanks the Calendaring and Scheduling Consortium πŸ¦‹ they gave advice on this spec and organized interoperability testing events to help polish it up. literally a whole team effort, no cap πŸ‘‘

Author's Address 🌸

this is where you can find the author, Kenneth Murchison, at Carnegie Mellon University πŸŽ“ he's based in Pittsburgh, PA, in the United States of America πŸ‡ΊπŸ‡Έ

Kenneth Murchison
Carnegie Mellon University
5000 Forbes Avenue
Pittsburgh, PA  15213
United States of America

Phone: +1-412-268-1982
Email: murch@andrew.cmu.edu
πŸ“¦ where this page comes from