How to display upcoming events in Nextcloud calendar using text-based terminal emulator

Display upcoming events in Nextcloud calendar using text-based terminal emulator because it is fun and challenging.

This article is quite lengthy as it includes XML output and parsed data for easier understanding.

Initial assumptions

Today's date is Sun Dec 17 10:07:09 CET 2017.

The start date will be defined as 20171217T000000 and end date as 20171218T000000.

Nextcloud DAV resource is available at https://cloud.example.org/remote.php/dav/ using username crono and application password zabie.

Install required software

Install curl utility to request and download Nextcloud calendar events.

$ sudo apt-get install curl

Install xmlstarlet utility to parse downloaded XML documents.

$ sudo apt-get install xmlstarlet

Create application password

Define individual password for an application in NextCloud server, so it could be revoked later. Do not use your password.

Command-line operations

Get path for the user's principal resource on the server.

$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:current-user-principal />
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/</d:href>
    <d:propstat>
      <d:prop>
        <d:current-user-principal>
          <d:href>/remote.php/dav/principals/users/crono/</d:href>
        </d:current-user-principal>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:current-user-principal />
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/d:current-user-principal/d:href' -n
/remote.php/dav/principals/users/crono/

Get path that contains calendar collections owned by the user using user's principal resource address obtained in the previous step.

$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">                                                                                                          
                 <d:prop>    
                   <c:calendar-home-set />                                        
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/principals/users/crono/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/principals/users/crono/</d:href>
    <d:propstat>
      <d:prop>
        <cal:calendar-home-set>
          <d:href>/remote.php/dav/calendars/crono/</d:href>
        </cal:calendar-home-set>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop>
                   <c:calendar-home-set />
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/principals/users/crono/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-home-set/d:href' -n
/remote.php/dav/calendars/crono/

Get calendar paths for given collection.

$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 1' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:displayname/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://o
wncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname/>
      </d:prop>
      <d:status>HTTP/1.1 404 Not Found</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/personal/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname>Personal</d:displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/debian/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname>Debian</d:displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/work/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname>Work</d:displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/projects/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname>Projects</d:displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/inbox/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname/>
      </d:prop>
      <d:status>HTTP/1.1 404 Not Found</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/outbox/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname/>
      </d:prop>
      <d:status>HTTP/1.1 404 Not Found</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request PROPFIND \
       --header 'Content-Type: text/xml' \
       --header 'Depth: 1' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:displayname/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/ | \
  xmlstarlet sel -t -m 'd:multistatus/d:response' -i  "string-length(d:propstat/d:prop/d:displayname)" -i "d:propstat/d:status='HTTP/1.1 200 OK'" -v "d:href" -n
/remote.php/dav/calendars/crono/personal/
/remote.php/dav/calendars/crono/debian/
/remote.php/dav/calendars/crono/work/
/remote.php/dav/calendars/crono/projects/

Get calendar name.

$ curl --silent \
       --request PROPFIND \
       --header "Content-Type: text/xml" \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:displayname/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org//remote.php/dav/calendars/crono/personal/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/personal/</d:href>
    <d:propstat>
      <d:prop>
        <d:displayname>Personal</d:displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request PROPFIND \
       --header "Content-Type: text/xml" \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:prop>
                   <d:displayname/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org//remote.php/dav/calendars/crono/personal/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/d:displayname' -n
Personal

Get event types stored in this calendar e.g. VTODO, VEVENT.

$ curl --silent \
       --request PROPFIND \
       --header "Content-Type: text/xml" \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:" xmlns:cal="urn:ietf:params:xml:ns:caldav">
                 <d:prop>
                   <cal:supported-calendar-component-set/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org//remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/projects/</d:href>
    <d:propstat>
      <d:prop>
        <cal:supported-calendar-component-set>
          <cal:comp name="VEVENT"/>
          <cal:comp name="VTODO"/>
        </cal:supported-calendar-component-set>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request PROPFIND \
       --header "Content-Type: text/xml" \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:" xmlns:cal="urn:ietf:params:xml:ns:caldav">
                 <d:prop>
                   <cal:supported-calendar-component-set/>
                 </d:prop>
               </d:propfind>' \
       --user crono:zabie \
       https://cloud.example.org//remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:supported-calendar-component-set/cal:comp/@name' -n
VEVENT
VTODO

Get today's events.

$ curl --silent \
       --request REPORT \
        --header "Depth: 1" \
        --header "Content-Type: text/xml" \
        --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                  <d:prop><d:getetag /><c:calendar-data /></d:prop>
                  <c:filter>
                    <c:comp-filter name="VCALENDAR">
                      <c:comp-filter name="VEVENT">
                        <c:time-range  start="20171217T000000" end="20171218T000000"/>
                      </c:comp-filter>
                    </c:comp-filter>
                  </c:filter>
                </c:calendar-query>' \
        --user crono:zabie \
  https://cloud.example.org/remote.php/dav/calendars/crono/personal/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/personal/Nextcloud-KKZ132DLC63FFE1S4T9B.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"ba8c6d9009abb08fa4ea2be6cbe2ebe9"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
PRODID:-//Nextcloud calendar v1.5.7
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20171216T221307
DTSTAMP:20171216T221307
LAST-MODIFIED:20171217T012313
UID:JW3J5YXFDDADD3DUISWGNR
SUMMARY:Configure VPN for Netflix
CLASS:PUBLIC
STATUS:CONFIRMED
DTSTART;VALUE=DATE:20171217
DTEND;VALUE=DATE:20171218
END:VEVENT
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/personal/Nextcloud-SLK1IPCBJPSIANH6LFY3TQ.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"0921b6eb221dbd4eadc68dc22ebc78dd"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
PRODID:-//Nextcloud calendar v1.5.7
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20171215T235237
DTSTAMP:20171215T235237
LAST-MODIFIED:20171217T012316
UID:VSTUTPGUYRHMWP6HXLP3G
SUMMARY:Renew car insurance
CLASS:PUBLIC
STATUS:CONFIRMED
DTSTART;VALUE=DATE:20171217
DTEND;VALUE=DATE:20171218
END:VEVENT
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                 <c:filter>
                   <c:comp-filter name="VCALENDAR">
                     <c:comp-filter name="VEVENT">
                       <c:time-range  start="20171217T000000" end="20171218T000000"/>
                     </c:comp-filter>
                   </c:comp-filter>
                 </c:filter>
               </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/personal/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n
BEGIN:VCALENDAR
PRODID:-//Nextcloud calendar v1.5.7
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20171216T221307
DTSTAMP:20171216T221307
LAST-MODIFIED:20171217T012313
UID:JW3J5YXFDDADD3DUISWGNR
SUMMARY:Configure VPN for Netflix
CLASS:PUBLIC
STATUS:CONFIRMED
DTSTART;VALUE=DATE:20171217
DTEND;VALUE=DATE:20171218
END:VEVENT
END:VCALENDAR
BEGIN:VCALENDAR
PRODID:-//Nextcloud calendar v1.5.7
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20171215T235237
DTSTAMP:20171215T235237
LAST-MODIFIED:20171217T012316
UID:VSTUTPGUYRHMWP6HXLP3G
SUMMARY:Renew car insurance
CLASS:PUBLIC
STATUS:CONFIRMED
DTSTART;VALUE=DATE:20171217
DTEND;VALUE=DATE:20171218
END:VEVENT
END:VCALENDAR

Get tasks to be done today.

$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                 <c:filter>
                   <c:comp-filter name="VCALENDAR">
                     <c:comp-filter name="VTODO">
                       <c:prop-filter name="DUE">
                         <c:time-range start="20171217T000000" end="20171218T000000"/>
                         <c:is-defined/>
                       </c:prop-filter>
                       <c:prop-filter name="COMPLETED">
                         <c:is-not-defined/>
                       </c:prop-filter>
                     </c:comp-filter>
                   </c:comp-filter>
                 </c:filter>
               </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/projects/Nextcloud-3kmehrxc5bmio2wj1g8geb.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"574bf3275954deb3912d3ee34cfcece5"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T210907
DTSTAMP:20171217T012302
LAST-MODIFIED:20171217T012302
UID:0phsdngneftk
SUMMARY:Create blog post about CalDAV
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171217T225000
CATEGORIES:Idea
END:VTODO
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                 <c:filter>
                   <c:comp-filter name="VCALENDAR">
                     <c:comp-filter name="VTODO">
                       <c:prop-filter name="DUE">
                         <c:time-range start="20171217T000000" end="20171218T000000"/>
                         <c:is-defined/>
                       </c:prop-filter>
                       <c:prop-filter name="COMPLETED">
                         <c:is-not-defined/>
                       </c:prop-filter>
                     </c:comp-filter>
                   </c:comp-filter>
                 </c:filter>
               </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T210907
DTSTAMP:20171217T012302
LAST-MODIFIED:20171217T012302
UID:0phsdngneftk
SUMMARY:Create blog post about CalDAV
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171217T225000
CATEGORIES:Idea
END:VTODO
END:VCALENDAR

Get overdue tasks.

$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                 <c:filter>
                    <c:comp-filter name="VCALENDAR">
                      <c:comp-filter name="VTODO">
                        <c:prop-filter name="DUE">
                          <c:time-range end="20171217T000000"/>
                          <c:is-defined/>
                        </c:prop-filter>
                        <c:prop-filter name="COMPLETED">
                          <c:is-not-defined/>
                        </c:prop-filter>
                      </c:comp-filter>
                    </c:comp-filter>
                 </c:filter>
               </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/projects/Nextcloud-dejyd26qk1vzx8lq938ct.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"c2d6081a16ce1982086c1073225e6595"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T235541
DTSTAMP:20171216T224646
LAST-MODIFIED:20171216T224646
UID:t9xf9ou7dz
SUMMARY:Inspect RFC4791
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171215T000000
STATUS:NEEDS-ACTION
CATEGORIES:Idea
END:VTODO
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                 <c:filter>
                    <c:comp-filter name="VCALENDAR">
                      <c:comp-filter name="VTODO">
                        <c:prop-filter name="DUE">
                          <c:time-range end="20171217T000000"/>
                          <c:is-defined/>
                        </c:prop-filter>
                        <c:prop-filter name="COMPLETED">
                          <c:is-not-defined/>
                        </c:prop-filter>
                      </c:comp-filter>
                    </c:comp-filter>
                 </c:filter>
               </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T235541
DTSTAMP:20171216T224646
LAST-MODIFIED:20171216T224646
UID:t9xf9ou7dz
SUMMARY:Inspect RFC4791
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171215T000000
STATUS:NEEDS-ACTION
CATEGORIES:Idea
END:VTODO
END:VCALENDAR

Get tasks that belong to "Idea" category but does not contain "RFC" string.

$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                   <c:filter>
                     <c:comp-filter name="VCALENDAR">
                       <c:comp-filter name="VTODO">
                         <c:prop-filter name="SUMMARY">
                           <c:text-match negate-condition="yes" collation="i;ascii-casemap">rfc</c:text-match>
                         </c:prop-filter>
                         <c:prop-filter name="CATEGORIES">
                           <c:text-match collation="i;ascii-casemap">idea</c:text-match>
                         </c:prop-filter>
                         <c:prop-filter name="COMPLETED">                                    
                           <c:is-not-defined/>                                               
                         </c:prop-filter> 
                       </c:comp-filter>
                     </c:comp-filter>
                   </c:filter>                              
                 </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/projects/Nextcloud-3kmehrxc5bmio2wj1g8geb.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"574bf3275954deb3912d3ee34cfcece5"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T210907
DTSTAMP:20171217T012302
LAST-MODIFIED:20171217T012302
UID:0phsdngneftk
SUMMARY:Create blog post about CalDAV
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171217T225000
CATEGORIES:Idea
END:VTODO
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                   <c:filter>
                     <c:comp-filter name="VCALENDAR">
                       <c:comp-filter name="VTODO">
                         <c:prop-filter name="SUMMARY">
                           <c:text-match negate-condition="yes" collation="i;ascii-casemap">rfc</c:text-match>
                         </c:prop-filter>
                         <c:prop-filter name="CATEGORIES">
                           <c:text-match collation="i;ascii-casemap">idea</c:text-match>
                         </c:prop-filter>
                         <c:prop-filter name="COMPLETED">                                    
                           <c:is-not-defined/>                                               
                         </c:prop-filter> 
                       </c:comp-filter>
                     </c:comp-filter>
                   </c:filter>                              
                 </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/projects/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20171215T210907
DTSTAMP:20171217T012302
LAST-MODIFIED:20171217T012302
UID:0phsdngneftk
SUMMARY:Create blog post about CalDAV
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
DUE:20171217T225000
CATEGORIES:Idea
END:VTODO
END:VCALENDAR

Get tasks without defined due date.

$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                   <c:filter>
                     <c:comp-filter name="VCALENDAR">
                       <c:comp-filter name="VTODO">
                         <c:prop-filter name="DUE">
                           <c:is-not-defined/>
                         </c:prop-filter>
                         <c:prop-filter name="COMPLETED">
                           <c:is-not-defined/>
                         </c:prop-filter>
                       </c:comp-filter>
                     </c:comp-filter>
                   </c:filter>
                 </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/debian/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/debian/Nextcloud-4xuiemhm07tko9lfqumdhq.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"150984578c2b889bacb96df2ba8fdb8c"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20170801T233039
DTSTAMP:20171216T215618
LAST-MODIFIED:20171216T215618
UID:8wddgk2ytvi
SUMMARY:Install OpenVPN
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
END:VTODO
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/debian/Nextcloud-uxeoyrtcfhoo5orjm6gefi.ics</d:href>
    <d:propstat>
      <d:prop>
        <d:getetag>"3ff5ad3d4a982916d8995a59d77dd594"</d:getetag>
        <cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20170801T225943
DTSTAMP:20171216T215443
LAST-MODIFIED:20171216T215443
UID:ygs9uj2lh3n
SUMMARY:Upgrade iptables firewall
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
END:VTODO
END:VCALENDAR</cal:calendar-data>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>
$ curl --silent \
       --request REPORT \
       --header "Depth: 1" \
       --header "Content-Type: text/xml" \
       --data '<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
                 <d:prop><d:getetag /><c:calendar-data /></d:prop>
                   <c:filter>
                     <c:comp-filter name="VCALENDAR">
                       <c:comp-filter name="VTODO">
                         <c:prop-filter name="DUE">
                           <c:is-not-defined/>
                         </c:prop-filter>
                         <c:prop-filter name="COMPLETED">
                           <c:is-not-defined/>
                         </c:prop-filter>
                       </c:comp-filter>
                     </c:comp-filter>
                   </c:filter>
                 </c:calendar-query>' \
       --user crono:zabie \
       https://cloud.example.org/remote.php/dav/calendars/crono/debian/ | \
  xmlstarlet sel -t -v 'd:multistatus/d:response/d:propstat/d:prop/cal:calendar-data' -n
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20170801T233039
DTSTAMP:20171216T215618
LAST-MODIFIED:20171216T215618
UID:8wddgk2ytvi
SUMMARY:Install OpenVPN
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
END:VTODO
END:VCALENDAR
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.9.5
BEGIN:VTODO
CREATED:20170801T225943
DTSTAMP:20171216T215443
LAST-MODIFIED:20171216T215443
UID:ygs9uj2lh3n
SUMMARY:Upgrade iptables firewall
PRIORITY:0
PERCENT-COMPLETE:0
X-OC-HIDESUBTASKS:0
END:VTODO
END:VCALENDAR

Additional notes

You can return all property names and values on the calendar resource.

$ curl --silent \
       --request PROPFIND \
       --header "Content-Type: text/xml" \
       --header 'Depth: 0' \
       --data '<d:propfind xmlns:d="DAV:">
                 <d:allprop/>
               </d:propfind>' \
       --user crono:zabie \
  https://cloud.example.org//remote.php/dav/calendars/crono/personal/ | \
  xmlstarlet format
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:response>
    <d:href>/remote.php/dav/calendars/crono/personal/</d:href>
    <d:propstat>
      <d:prop>
        <d:resourcetype>
          <d:collection/>
          <cal:calendar/>
        </d:resourcetype>
        <cs:getctag>http://sabre.io/ns/sync/57</cs:getctag>
        <s:sync-token>57</s:sync-token>
        <cal:supported-calendar-component-set>
          <cal:comp name="VEVENT"/>
          <cal:comp name="VTODO"/>
        </cal:supported-calendar-component-set>
        <cal:schedule-calendar-transp>
          <cal:opaque/>
        </cal:schedule-calendar-transp>
        <oc:owner-principal>principals/users/crono</oc:owner-principal>
        <d:displayname>Personal</d:displayname>
        <x1:calendar-order xmlns:x1="http://apple.com/ns/ical/">0</x1:calendar-order>
        <x1:calendar-color xmlns:x1="http://apple.com/ns/ical/">#63DA38</x1:calendar-color>
        <x2:owner-displayname xmlns:x2="http://nextcloud.com/ns">crono</x2:owner-displayname>
      </d:prop>
      <d:status>HTTP/1.1 200 OK</d:status>
    </d:propstat>
  </d:response>
</d:multistatus>

References

About Milosz Galazka

Milosz is a Linux Foundation Certified Engineer working for a successful Polish company as a system administrator and a long time supporter of Free Software Foundation and Debian operating system.