WebDAV PUSH based on RFC 6202

Ken Murchison <[email protected]> Wed, 09 Apr 2014 12:28:39 -0400
Newsgroups gmane.ietf.webdav
Organization Carnegie Mellon University
Message-ID <[email protected]>
This is a multi-part message in MIME format.
--------------070102000200060809090905
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

All,

The Calendaring and Scheduling Consortium (CalConnect) is looking at 
ways to have a server "push" changes made to a calendar/addressbook 
collection out to a client.  There are already a few proprietary 
mechanisms in place for doing so, but we would like to come up with 
something standard that would be relatively simple to implement for both 
clients and servers and would be applicable to any DAV collection.

One idea that we are toying with is to leverage the existing 
DAV:sync-collection REPORT <http://datatracker.ietf.org/doc/rfc6578/> 
and HTTP long-polling <http://datatracker.ietf.org/doc/rfc6202/>.  I 
spent a few hours coding up a prototype version of HTTP long-polling and 
HTTP streaming for DAV:sync-collection REPORTs in my server which I 
describe below.  We (CalConnect) are considering using this approach or 
something similar as a starting point and we are interested in any/all 
feedback from the larger DAV community, including:

  * Is this approach sane, is there a better way, or is any type of push
    via HTTP a hopeless endeavor?
  * Will this approach (or anything similar) break in the face of
    intermediaries?
  * Will existing HTTP/DAV stacks be able to handle long-polling and/or
    streaming?
  * Should the server advertise its ability to long-poll and/or stream
    for the client to discover or simply leave it up to the client try
    one or both and see what the server does, as is the case in my
    implementation below?


Long-polling:

For long-polling, I leveraged the HTTP Prefer header 
<http://tools.ietf.org/html/draft-snell-http-prefer> and its 'wait' 
preference as a way for the client to tell the server that it wants to 
long poll.  If the client doesn't specify a DAV:sync-token (initial 
sync) or if there have been changes since the specified token, then the 
server will respond immediately.  Otherwise, it will only respond if a 
change is detected or when the timeout expires, whichever comes first.  
In the case of a delayed response, I issue a 100 (Continue) provisional 
response with a Preference-Applied header to notify the client that the 
server is indeed long-polling as requested.  This provisional response 
may or may not be necessary.  In my implementation I wait 1 sec less 
than specified to account for processing time so I don't go over what 
the client expects.

Streaming:

The client can request streaming behavior by simply including an Accept 
header with the 'multipart/mixed' media type (I chose this subtype for 
lack of something better - we could use the existing x-mixed-replace or 
create our own).  The client can also specify a timeout for the 
streaming using the same 'wait' preference as used for long-polling.  In 
the absence of a client-requested timeout, the server will continue to 
add body parts until the client disconnects or the server hits some 
internal timeout.  Because a multipart response allows for an epilogue 
following the final delimiter, a client can't just rely on the 
delimiters to detect the end of the response.  Therefore, the server 
MUST use either chunked TE or close the connection following the 
multipart response.  In my example below, I close-delimit the response 
for better readability.  FWIW, I think the same holds true for 
multipart/byteranges responses.  In my implementation I include a 
Content-Length header in the body-part headers so the client can detect 
the end of the XML body without looking for the trailing delimiter 
(mainly because the trailing delimiter doesn't appear until the next 
body part).

Examples:

Here is an example of long-polling with 3 requests (I added superfluous 
Date headers to show the timing).  The first request returns immediately 
due to a pre-existing change, the second returns upon detecting a 
subsequent change some 95 sec later, and the third times out after 3 min.

REPORT /dav/calendars/user/ken/Default/ HTTP/1.1
Host: localhost
Date: Wed, 30 Oct 2013 18:11:11 GMT
Content-Type: application/xml
Content-Length: 260
Prefer: return=minimal, wait=180

<?xml version="1.0" encoding="UTF-8"?>
<C:sync-collection xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:sync-token>http://cyrusimap.org/ns/sync/1368011844-354</D:sync-token>
   <D:sync-level>1</D:sync-level>
   <D:prop/>
</C:sync-collection>

HTTP/1.1 207 Multi-Status
Date: Wed, 30 Oct 2013 18:11:11 GMT
Vary: Accept-Encoding, Brief, Prefer
Preference-Applied: return=minimal, wait=180
Content-Type: application/xml; charset=utf-8
Content-Length: 421

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
   <D:response>
<D:href>/dav/calendars/user/ken/Default/4E4B3490-6F01-41B9-AA5B-FE2CD6A30632.ics</D:href>
     <D:propstat>
       <D:prop/>
       <D:status>HTTP/1.1 200 OK</D:status>
     </D:propstat>
   </D:response>
<D:sync-token>http://cyrusimap.org/ns/sync/1368011844-355</D:sync-token>
</D:multistatus>



REPORT /dav/calendars/user/ken/Default/ HTTP/1.1
Host: localhost
Date: Wed, 30 Oct 2013 18:11:12 GMT
Content-Type: application/xml
Content-Length: 260
Prefer: return=minimal, wait=180

<?xml version="1.0" encoding="UTF-8"?>
<C:sync-collection xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:sync-token>http://cyrusimap.org/ns/sync/1368011844-355</D:sync-token>
   <D:sync-level>1</D:sync-level>
   <D:prop/>
</C:sync-collection>

HTTP/1.1 100 Continue
Date: Wed, 30 Oct 2013 18:11:12 GMT
Preference-Applied: wait=180

HTTP/1.1 207 Multi-Status
Date: Wed, 30 Oct 2013 18:12:47 GMT
Vary: Accept-Encoding, Brief, Prefer
Preference-Applied: return=minimal, wait=180
Content-Type: application/xml; charset=utf-8
Content-Length: 375

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
   <D:response>
<D:href>/dav/calendars/user/ken/Default/4E4B3490-6F01-41B9-AA5B-FE2CD6A30632.ics</D:href>
     <D:status>HTTP/1.1 404 Not Found</D:status>
   </D:response>
<D:sync-token>http://cyrusimap.org/ns/sync/1368011844-356</D:sync-token>
</D:multistatus>



REPORT /dav/calendars/user/ken/Default/ HTTP/1.1
Host: localhost
Date: Wed, 30 Oct 2013 18:12:48 GMT
Content-Type: application/xml
Content-Length: 260
Prefer: return=minimal, wait=180

<?xml version="1.0" encoding="UTF-8"?>
<C:sync-collection xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:sync-token>http://cyrusimap.org/ns/sync/1368011844-356</D:sync-token>
   <D:sync-level>1</D:sync-level>
   <D:prop/>
</C:sync-collection>

HTTP/1.1 100 Continue
Date: Wed, 30 Oct 2013 18:12:48 GMT
Preference-Applied: wait=180

HTTP/1.1 207 Multi-Status
Date: Wed, 30 Oct 2013 18:15:47 GMT
Vary: Accept-Encoding, Brief, Prefer
Preference-Applied: return=minimal, wait=180
Content-Type: application/xml; charset=utf-8
Content-Length: 202

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:sync-token>http://cyrusimap.org/ns/sync/1368011844-356</D:sync-token>
</D:multistatus>



Here is the same sequence of events utilizing streaming with a timeout:

REPORT /dav/calendars/user/ken/Default/ HTTP/1.1
Host: localhost
Date: Wed, 30 Oct 2013 18:11:06 GMT
Content-Type: application/xml
Content-Length: 260
Prefer: return=minimal, wait=180
Accept: multipart/mixed

<?xml version="1.0" encoding="UTF-8"?>
<C:sync-collection xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:sync-token>http://cyrusimap.org/ns/sync/1368011844-354</D:sync-token>
   <D:sync-level>1</D:sync-level>
   <D:prop/>
</C:sync-collection>

HTTP/1.1 207 Multi-Status
Connection: close
Date: Wed, 30 Oct 2013 18:11:06 GMT
Vary: Accept-Encoding, Brief, Prefer
Preference-Applied: return=minimal, wait=180
Content-Type: multipart/mixed; 
boundary="localhost-29378-1383156666-1025603243"

This is a message with multiple parts in MIME format.

--localhost-29378-1383156666-1025603243
Date: Wed, 30 Oct 2013 18:11:06 GMT
Content-Type: application/xml; charset=utf-8
Content-Length: 421

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
   <D:response>
<D:href>/dav/calendars/user/ken/Default/4E4B3490-6F01-41B9-AA5B-FE2CD6A30632.ics</D:href>
     <D:propstat>
       <D:prop/>
       <D:status>HTTP/1.1 200 OK</D:status>
     </D:propstat>
   </D:response>
<D:sync-token>http://cyrusimap.org/ns/sync/1368011844-355</D:sync-token>
</D:multistatus>

--localhost-29378-1383156666-1025603243
Date: Wed, 30 Oct 2013 18:12:47 GMT
Content-Type: application/xml; charset=utf-8
Content-Length: 375

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
   <D:response>
<D:href>/dav/calendars/user/ken/Default/4E4B3490-6F01-41B9-AA5B-FE2CD6A30632.ics</D:href>
     <D:status>HTTP/1.1 404 Not Found</D:status>
   </D:response>
<D:sync-token>http://cyrusimap.org/ns/sync/1368011844-356</D:sync-token>
</D:multistatus>

--localhost-29378-1383156666-1025603243
Date: Wed, 30 Oct 2013 18:14:05 GMT
Content-Type: application/xml; charset=utf-8
Content-Length: 202

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:sync-token>http://cyrusimap.org/ns/sync/1368011844-356</D:sync-token>
</D:multistatus>

--localhost-29378-1383156666-1025603243--

End of MIME multipart body.

-- 
Kenneth Murchison
Principal Systems Software Engineer
Carnegie Mellon University


--------------070102000200060809090905
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit

<html>
  <head>
    <meta http-equiv="content-type" content="text/html;
      charset=ISO-8859-1">
  </head>
  <body bgcolor="#FFFFFF" text="#000000">
    All,<br>
    <br>
    The Calendaring and Scheduling Consortium (CalConnect) is looking at
    ways to have a server "push" changes made to a calendar/addressbook
    collection out to a client.&nbsp; There are already a few proprietary
    mechanisms in place for doing so, but we would like to come up with
    something standard that would be relatively simple to implement for
    both clients and servers and would be applicable to any DAV
    collection.<br>
    <br>
    One idea that we are toying with is to leverage the existing <a
      href="http://datatracker.ietf.org/doc/rfc6578/">DAV:sync-collection
      REPORT</a> and <a href="http://datatracker.ietf.org/doc/rfc6202/">HTTP
      long-polling</a>.&nbsp; I spent a few hours coding up a prototype
    version of HTTP long-polling and HTTP streaming for
    DAV:sync-collection REPORTs in my server which I describe below.&nbsp; We
    (CalConnect) are considering using this approach or something
    similar as a starting point and we are interested in any/all
    feedback from the larger DAV community, including:<br>
    <ul>
      <li>Is this approach sane, is there a better way, or is any type
        of push via HTTP a hopeless endeavor?</li>
      <li>Will this approach (or anything similar) break in the face of
        intermediaries?</li>
      <li>Will existing HTTP/DAV stacks be able to handle long-polling
        and/or streaming?<br>
      </li>
      <li>Should the server advertise its ability to long-poll and/or
        stream for the client to discover or simply leave it up to the
        client try one or both and see what the server does, as is the
        case in my implementation below?<br>
      </li>
    </ul>
    <br>
    Long-polling:<br>
    <br>
    For long-polling, I leveraged the <a
      href="http://tools.ietf.org/html/draft-snell-http-prefer">HTTP
      Prefer header</a> and its 'wait' preference as a way for the
    client to tell the server that it wants to long poll.&nbsp; If the client
    doesn't specify a DAV:sync-token (initial sync) or if there have
    been changes since the specified token, then the server will respond
    immediately.&nbsp; Otherwise, it will only respond if a change is
    detected or when the timeout expires, whichever comes first.&nbsp; In the
    case of a delayed response, I issue a 100 (Continue) provisional
    response with a Preference-Applied header to notify the client that
    the server is indeed long-polling as requested.&nbsp; This provisional
    response may or may not be necessary.&nbsp; In my implementation I wait 1
    sec less than specified to account for processing time so I don't go
    over what the client expects.<br>
    <br>
    Streaming:<br>
    <br>
    The client can request streaming behavior by simply including an
    Accept header with the 'multipart/mixed' media type (I chose this
    subtype for lack of something better - we could use the existing
    x-mixed-replace or create our own).&nbsp; The client can also specify a
    timeout for the streaming using the same 'wait' preference as used
    for long-polling.&nbsp; In the absence of a client-requested timeout, the
    server will continue to add body parts until the client disconnects
    or the server hits some internal timeout.&nbsp; Because a multipart
    response allows for an epilogue following the final delimiter, a
    client can't just rely on the delimiters to detect the end of the
    response.&nbsp; Therefore, the server MUST use either chunked TE or close
    the connection following the multipart response.&nbsp; In my example
    below, I close-delimit the response for better readability.&nbsp; FWIW, I
    think the same holds true for multipart/byteranges responses.&nbsp; In my
    implementation I include a Content-Length header in the body-part
    headers so the client can detect the end of the XML body without
    looking for the trailing delimiter (mainly because the trailing
    delimiter doesn't appear until the next body part).<br>
    <br>
    Examples:<br>
    <br>
    Here is an example of long-polling with 3 requests (I added
    superfluous Date headers to show the timing).&nbsp; The first request
    returns immediately due to a pre-existing change, the second returns
    upon detecting a subsequent change some 95 sec later, and the third
    times out after 3 min.<br>
    <br>
    REPORT /dav/calendars/user/ken/Default/ HTTP/1.1<br>
    Host: localhost<br>
    Date: Wed, 30 Oct 2013 18:11:11 GMT<br>
    Content-Type: application/xml<br>
    Content-Length: 260<br>
    Prefer: return=minimal, wait=180<br>
    <br>
    &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>
    &lt;C:sync-collection xmlns:D="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"&gt;<br>
    &nbsp;
    &lt;D:sync-token&gt;<a class="moz-txt-link-freetext"
      href="http://cyrusimap.org/ns/sync/1368011844-354">http://cyrusimap.org/ns/sync/1368011844-354</a>&lt;/D:sync-token&gt;<br>
    &nbsp; &lt;D:sync-level&gt;1&lt;/D:sync-level&gt;<br>
    &nbsp; &lt;D:prop/&gt;<br>
    &lt;/C:sync-collection&gt;<br>
    <br>
    HTTP/1.1 207 Multi-Status<br>
    Date: Wed, 30 Oct 2013 18:11:11 GMT<br>
    Vary: Accept-Encoding, Brief, Prefer<br>
    Preference-Applied: return=minimal, wait=180<br>
    Content-Type: application/xml; charset=utf-8<br>
    Content-Length: 421<br>
    <br>
    &lt;?xml version="1.0" encoding="utf-8"?&gt;<br>
    &lt;D:multistatus xmlns:D="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"&gt;<br>
    &nbsp; &lt;D:response&gt;<br>
    &nbsp;&nbsp;&nbsp;
&lt;D:href&gt;/dav/calendars/user/ken/Default/4E4B3490-6F01-41B9-AA5B-FE2CD6A30632.ics&lt;/D:href&gt;<br>
    &nbsp;&nbsp;&nbsp; &lt;D:propstat&gt;<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;D:prop/&gt;<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;D:status&gt;HTTP/1.1 200 OK&lt;/D:status&gt;<br>
    &nbsp;&nbsp;&nbsp; &lt;/D:propstat&gt;<br>
    &nbsp; &lt;/D:response&gt;<br>
    &nbsp;
    &lt;D:sync-token&gt;<a class="moz-txt-link-freetext"
      href="http://cyrusimap.org/ns/sync/1368011844-355">http://cyrusimap.org/ns/sync/1368011844-355</a>&lt;/D:sync-token&gt;<br>
    &lt;/D:multistatus&gt;<br>
    <br>
    <br>
    <br>
    REPORT /dav/calendars/user/ken/Default/ HTTP/1.1<br>
    Host: localhost<br>
    Date: Wed, 30 Oct 2013 18:11:12 GMT<br>
    Content-Type: application/xml<br>
    Content-Length: 260<br>
    Prefer: return=minimal, wait=180<br>
    <br>
    &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>
    &lt;C:sync-collection xmlns:D="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"&gt;<br>
    &nbsp;
    &lt;D:sync-token&gt;<a class="moz-txt-link-freetext"
      href="http://cyrusimap.org/ns/sync/1368011844-355">http://cyrusimap.org/ns/sync/1368011844-355</a>&lt;/D:sync-token&gt;<br>
    &nbsp; &lt;D:sync-level&gt;1&lt;/D:sync-level&gt;<br>
    &nbsp; &lt;D:prop/&gt;<br>
    &lt;/C:sync-collection&gt;<br>
    <br>
    HTTP/1.1 100 Continue<br>
    Date: Wed, 30 Oct 2013 18:11:12 GMT<br>
    Preference-Applied: wait=180<br>
    <br>
    HTTP/1.1 207 Multi-Status<br>
    Date: Wed, 30 Oct 2013 18:12:47 GMT<br>
    Vary: Accept-Encoding, Brief, Prefer<br>
    Preference-Applied: return=minimal, wait=180<br>
    Content-Type: application/xml; charset=utf-8<br>
    Content-Length: 375<br>
    <br>
    &lt;?xml version="1.0" encoding="utf-8"?&gt;<br>
    &lt;D:multistatus xmlns:D="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"&gt;<br>
    &nbsp; &lt;D:response&gt;<br>
    &nbsp;&nbsp;&nbsp;
&lt;D:href&gt;/dav/calendars/user/ken/Default/4E4B3490-6F01-41B9-AA5B-FE2CD6A30632.ics&lt;/D:href&gt;<br>
    &nbsp;&nbsp;&nbsp; &lt;D:status&gt;HTTP/1.1 404 Not Found&lt;/D:status&gt;<br>
    &nbsp; &lt;/D:response&gt;<br>
    &nbsp;
    &lt;D:sync-token&gt;<a class="moz-txt-link-freetext"
      href="http://cyrusimap.org/ns/sync/1368011844-356">http://cyrusimap.org/ns/sync/1368011844-356</a>&lt;/D:sync-token&gt;<br>
    &lt;/D:multistatus&gt;<br>
    <br>
    <br>
    <br>
    REPORT /dav/calendars/user/ken/Default/ HTTP/1.1<br>
    Host: localhost<br>
    Date: Wed, 30 Oct 2013 18:12:48 GMT<br>
    Content-Type: application/xml<br>
    Content-Length: 260<br>
    Prefer: return=minimal, wait=180<br>
    <br>
    &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>
    &lt;C:sync-collection xmlns:D="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"&gt;<br>
    &nbsp;
    &lt;D:sync-token&gt;<a class="moz-txt-link-freetext"
      href="http://cyrusimap.org/ns/sync/1368011844-356">http://cyrusimap.org/ns/sync/1368011844-356</a>&lt;/D:sync-token&gt;<br>
    &nbsp; &lt;D:sync-level&gt;1&lt;/D:sync-level&gt;<br>
    &nbsp; &lt;D:prop/&gt;<br>
    &lt;/C:sync-collection&gt;<br>
    <br>
    HTTP/1.1 100 Continue<br>
    Date: Wed, 30 Oct 2013 18:12:48 GMT<br>
    Preference-Applied: wait=180<br>
    <br>
    HTTP/1.1 207 Multi-Status<br>
    Date: Wed, 30 Oct 2013 18:15:47 GMT<br>
    Vary: Accept-Encoding, Brief, Prefer<br>
    Preference-Applied: return=minimal, wait=180<br>
    Content-Type: application/xml; charset=utf-8<br>
    Content-Length: 202<br>
    <br>
    &lt;?xml version="1.0" encoding="utf-8"?&gt;<br>
    &lt;D:multistatus xmlns:D="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"&gt;<br>
    &nbsp;
    &lt;D:sync-token&gt;<a class="moz-txt-link-freetext"
      href="http://cyrusimap.org/ns/sync/1368011844-356">http://cyrusimap.org/ns/sync/1368011844-356</a>&lt;/D:sync-token&gt;<br>
    &lt;/D:multistatus&gt;<br>
    <br>
    <br>
    <br>
    Here is the same sequence of events utilizing streaming with a
    timeout:<br>
    <br>
    REPORT /dav/calendars/user/ken/Default/ HTTP/1.1<br>
    Host: localhost<br>
    Date: Wed, 30 Oct 2013 18:11:06 GMT<br>
    Content-Type: application/xml<br>
    Content-Length: 260<br>
    Prefer: return=minimal, wait=180<br>
    Accept: multipart/mixed<br>
    <br>
    &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>
    &lt;C:sync-collection xmlns:D="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"&gt;<br>
    &nbsp;
    &lt;D:sync-token&gt;<a class="moz-txt-link-freetext"
      href="http://cyrusimap.org/ns/sync/1368011844-354">http://cyrusimap.org/ns/sync/1368011844-354</a>&lt;/D:sync-token&gt;<br>
    &nbsp; &lt;D:sync-level&gt;1&lt;/D:sync-level&gt;<br>
    &nbsp; &lt;D:prop/&gt;<br>
    &lt;/C:sync-collection&gt;<br>
    <br>
    HTTP/1.1 207 Multi-Status<br>
    Connection: close<br>
    Date: Wed, 30 Oct 2013 18:11:06 GMT<br>
    Vary: Accept-Encoding, Brief, Prefer<br>
    Preference-Applied: return=minimal, wait=180<br>
    Content-Type: multipart/mixed;
    boundary="localhost-29378-1383156666-1025603243"<br>
    <br>
    This is a message with multiple parts in MIME format.<br>
    <br>
    --localhost-29378-1383156666-1025603243<br>
    Date: Wed, 30 Oct 2013 18:11:06 GMT<br>
    Content-Type: application/xml; charset=utf-8<br>
    Content-Length: 421<br>
    <br>
    &lt;?xml version="1.0" encoding="utf-8"?&gt;<br>
    &lt;D:multistatus xmlns:D="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"&gt;<br>
    &nbsp; &lt;D:response&gt;<br>
    &nbsp;&nbsp;&nbsp;
&lt;D:href&gt;/dav/calendars/user/ken/Default/4E4B3490-6F01-41B9-AA5B-FE2CD6A30632.ics&lt;/D:href&gt;<br>
    &nbsp;&nbsp;&nbsp; &lt;D:propstat&gt;<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;D:prop/&gt;<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;D:status&gt;HTTP/1.1 200 OK&lt;/D:status&gt;<br>
    &nbsp;&nbsp;&nbsp; &lt;/D:propstat&gt;<br>
    &nbsp; &lt;/D:response&gt;<br>
    &nbsp;
    &lt;D:sync-token&gt;<a class="moz-txt-link-freetext"
      href="http://cyrusimap.org/ns/sync/1368011844-355">http://cyrusimap.org/ns/sync/1368011844-355</a>&lt;/D:sync-token&gt;<br>
    &lt;/D:multistatus&gt;<br>
    <br>
    --localhost-29378-1383156666-1025603243<br>
    Date: Wed, 30 Oct 2013 18:12:47 GMT<br>
    Content-Type: application/xml; charset=utf-8<br>
    Content-Length: 375<br>
    <br>
    &lt;?xml version="1.0" encoding="utf-8"?&gt;<br>
    &lt;D:multistatus xmlns:D="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"&gt;<br>
    &nbsp; &lt;D:response&gt;<br>
    &nbsp;&nbsp;&nbsp;
&lt;D:href&gt;/dav/calendars/user/ken/Default/4E4B3490-6F01-41B9-AA5B-FE2CD6A30632.ics&lt;/D:href&gt;<br>
    &nbsp;&nbsp;&nbsp; &lt;D:status&gt;HTTP/1.1 404 Not Found&lt;/D:status&gt;<br>
    &nbsp; &lt;/D:response&gt;<br>
    &nbsp;
    &lt;D:sync-token&gt;<a class="moz-txt-link-freetext"
      href="http://cyrusimap.org/ns/sync/1368011844-356">http://cyrusimap.org/ns/sync/1368011844-356</a>&lt;/D:sync-token&gt;<br>
    &lt;/D:multistatus&gt;<br>
    <br>
    --localhost-29378-1383156666-1025603243<br>
    Date: Wed, 30 Oct 2013 18:14:05 GMT<br>
    Content-Type: application/xml; charset=utf-8<br>
    Content-Length: 202<br>
    <br>
    &lt;?xml version="1.0" encoding="utf-8"?&gt;<br>
    &lt;D:multistatus xmlns:D="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"&gt;<br>
    &nbsp;
    &lt;D:sync-token&gt;<a class="moz-txt-link-freetext"
      href="http://cyrusimap.org/ns/sync/1368011844-356">http://cyrusimap.org/ns/sync/1368011844-356</a>&lt;/D:sync-token&gt;<br>
    &lt;/D:multistatus&gt;<br>
    <br>
    --localhost-29378-1383156666-1025603243--<br>
    <br>
    End of MIME multipart body.<br>
    <br>
    <pre class="moz-signature" cols="72">-- 
Kenneth Murchison
Principal Systems Software Engineer
Carnegie Mellon University
</pre>
  </body>
</html>

--------------070102000200060809090905--