propfind assumes DAV header always exists and causes null exception when missing
Steps to reproduce
Trying to use caldav to sync Super-Productivity[1] app with Mailcow/SOGo[2] server calendar.
- Spin up dockerized mailcow. a. Create mail account. b. Login to mail account. c. Open calendar. d. Click on "3-dot" icon next to calendar name, select "Links to this Calendar" e. Copy authorized caldav url.
- Clone Super-Productivity repo:
git clone [email protected]:johannesjo/super-productivity.git - Set up dev environment:
cd super-productivity; npm i @angular/cli; npm i - Run dev app:
npm run ng serve, then in another terminalnpm start - In Super-Productivity, find Inbox in Projects drawer (left side), select "Settings" from "3-dot" icon for Inbox.
- Expand CalDav Issue Integration. a. Enable caldav. b. Enter caldav url from step (1.e). E.g. https://mail.example.com/SOGo/dav c. Enter caldav path from step (1.e). E.g. Calendar/personal d. Enter username e. Enter password f. Enable four checkbox/toggles below: Show uncompleted..., Auto poll..., Auto add..., and Auto complete....
- Open dev tools with Ctrl-Shift-I.
- Select Console tab and observe "Caldav: Error: CALDAV NETWORK ERROR: TypeError: Cannot read properties of null (reading 'split')"
[1]: https://github.com/johannesjo/super-productivity
[2]: https://github.com/mailcow/mailcow-dockerized
Expected behavior
Expected no type error.
If library needs DAV header, then it should first make an OPTIONS request which must deliver a DAV header to comply with spec, then perform a subsequent PROPFIND request for other fields.
Actual behavior
"Caldav: Error: CALDAV NETWORK ERROR: TypeError: Cannot read properties of null (reading 'split')" crashes caldav sync in SuperProductivity.
Library version
1.1.0
Additional info
Suspect code
At index.js:155, request.propfind initiates the a PROPFIND request.
At index.js:158, _extractAdvertisedDavFeatures is called.
At index.js:541-542, the DAV: header is retrieved and sliced without a null check.
SOGo behavior
If I use curl to execute PROPFIND, SOGo never provides a DAV header.
If I use curl to execute OPTIONS, SOGo provides a DAV header.
E.g.
(curl --user "name:pass" -s -X PROPFIND -H "Content-Type: application/xml" -D /dev/stderr https://mail.example.com/SOGo/dav/name%40example.com | xmllint -format -) 2>&1 | bat
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ STDIN
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ HTTP/2 207
2 │ server: nginx
3 │ date: Sat, 02 Mar 2024 03:37:17 GMT
4 │ content-type: text/xml; charset=utf-8
5 │ content-length: 3883
6 │ x-dav-error: 200 No error
7 │ ms-author-via: DAV
8 │ pragma: no-cache
9 │ cache-control: no-cache
10 │ x-frame-options: SAMEORIGIN
11 │
12 │ <?xml version="1.0" encoding="utf-8"?>
13 │ <D:multistatus xmlns:ap="http://apache.org/dav/props/" xmlns:D="DAV:">
14 │ <D:response>
15 │ <D:href>/SOGo/dav/name%40example.com/freebusy.ifb</D:href>
16 │ <D:propstat>
17 │ <D:status>HTTP/1.1 200 OK</D:status>
18 │ <D:prop>
19 │ <D:getlastmodified>Fri, 01 Mar 2024 22:38:53 -0500</D:getlastmodified>
20 │ <D:resourcetype/>
21 │ <D:getcontenttype>text/calendar</D:getcontenttype>
22 │ <D:displayname>freebusy.ifb</D:displayname>
23 │ <D:href>/SOGo/dav/name%40example.com/freebusy.ifb</D:href>
24 │ <ap:executable>0</ap:executable>
25 │ </D:prop>
26 │ </D:propstat>
27 │ </D:response>
... │
158 │ </D:multistatus>
───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
(curl --user "name:pass" -s -X OPTIONS -H "Content-Type: application/xml" -D /dev/stderr https://mail.example.com/SOGo/dav/name%40example.com) 2>&1 | bat
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ STDIN
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ HTTP/2 200
2 │ server: nginx
3 │ date: Sat, 02 Mar 2024 03:40:04 GMT
4 │ content-type: text/plain; charset=utf-8
5 │ content-length: 0
6 │ allow: GET, HEAD, POST, OPTIONS, MKCOL, MKCALENDAR, DELETE, PUT, LOCK, UNLOCK, COPY, MOVE, REPORT, PROPFIND, SEARCH
7 │ dav: 1, 2, access-control, addressbook, calendar-access, calendar-schedule, calendar-auto-schedule, calendar-proxy, calendar-query-extended, extended-mkcol, calendarserver-principal-property-search
8 │ strict-transport-security: max-age=15768000;
9 │ x-content-type-options: nosniff
10 │ x-xss-protection: 1; mode=block
11 │ x-robots-tag: none
12 │ x-download-options: noopen
13 │ x-frame-options: SAMEORIGIN
14 │ x-permitted-cross-domain-policies: none
15 │ referrer-policy: strict-origin
16 │
───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
WebDAV Spec
According to the spec, PROPFIND uses DAV XML elements, but doesn't explicitly state a requirement/expectation for a DAV header [1].
Further along, the OPTIONS method is required to return the DAV header [2].
All DAV-compliant resources MUST return the DAV header with compliance-class "1" on all OPTIONS responses
Section F.3 makes the claim that DAV header can be used with requests [3]. I don't know whether "requests" in this context refers to other WebDAV methods.
The DAV header now allows non-IETF extensions through URIs in addition to compliance class tokens. It also can now be used in requests, although this specification does not define any associated semantics for the compliance classes defined in here (see Section 10.1).
[1]: http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
[2]: http://www.webdav.org/specs/rfc4918.html#HEADER_DAV
[3]: http://www.webdav.org/specs/rfc4918.html#n-other-changes