2a2821f39938c2240c0afedd4a3a52b44277ca49
[citadel.git] / webcit / groupdav_propfind.c
1 /*
2  * $Id$
3  *
4  * Handles GroupDAV PROPFIND requests.
5  *
6  * A few notes about our XML output:
7  *
8  * --> Yes, we are spewing tags directly instead of using an XML library.
9  *     Whining about it will be summarily ignored.
10  *
11  * --> XML is deliberately output with no whitespace/newlines between tags.
12  *     This makes it difficult to read, but we have discovered clients which
13  *     crash when you try to pretty it up.
14  *
15  */
16
17 #include "webcit.h"
18 #include "webserver.h"
19 #include "groupdav.h"
20
21 /*
22  * Given an encoded UID, translate that to an unencoded Citadel EUID and
23  * then search for it in the current room.  Return a message number or -1
24  * if not found.
25  *
26  */
27 long locate_message_by_uid(const char *uid) {
28         char buf[256];
29         char decoded_uid[1024];
30         long retval = (-1L);
31
32         /* decode the UID */
33         euid_unescapize(decoded_uid, uid);
34
35         /* ask Citadel if we have this one */
36         serv_printf("EUID %s", decoded_uid);
37         serv_getln(buf, sizeof buf);
38         if (buf[0] == '2') {
39                 retval = atol(&buf[4]);
40         }
41
42         return(retval);
43 }
44
45
46
47 /*
48  * List rooms (or "collections" in DAV terminology) which contain
49  * interesting groupware objects.
50  */
51 void groupdav_collection_list(void)
52 {
53         wcsession *WCC = WC;
54         char buf[256];
55         char roomname[256];
56         int view;
57         char datestring[256];
58         time_t now;
59         time_t mtime;
60         int is_groupware_collection = 0;
61         int starting_point = 1;         /**< 0 for /, 1 for /groupdav/ */
62
63         if (WCC->Hdr->HR.Handler == NULL) {
64                 starting_point = 0;
65         }
66         else if (StrLength(WCC->Hdr->HR.ReqLine) == 0) {
67                 starting_point = 1;
68         }
69         else {
70                 starting_point = 2;
71         }
72
73         now = time(NULL);
74         http_datestring(datestring, sizeof datestring, now);
75
76         /*
77          * Be rude.  Completely ignore the XML request and simply send them
78          * everything we know about.  Let the client sort it out.
79          */
80         hprintf("HTTP/1.0 207 Multi-Status\r\n");
81         groupdav_common_headers();
82         hprintf("Date: %s\r\n", datestring);
83         hprintf("Content-type: text/xml\r\n");
84         hprintf("Content-encoding: identity\r\n");
85
86         begin_burst();
87
88         wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
89                 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
90         );
91
92         /*
93          * If the client is requesting the root, show a root node.
94          */
95         if (starting_point == 0) {
96                 wprintf("<response>");
97                         wprintf("<href>");
98                                 groupdav_identify_host();
99                                 wprintf("/");
100                         wprintf("</href>");
101                         wprintf("<propstat>");
102                                 wprintf("<status>HTTP/1.1 200 OK</status>");
103                                 wprintf("<prop>");
104                                         wprintf("<displayname>/</displayname>");
105                                         wprintf("<resourcetype><collection/></resourcetype>");
106                                         wprintf("<getlastmodified>");
107                                                 escputs(datestring);
108                                         wprintf("</getlastmodified>");
109                                 wprintf("</prop>");
110                         wprintf("</propstat>");
111                 wprintf("</response>");
112         }
113
114         /*
115          * If the client is requesting "/groupdav", show a /groupdav subdirectory.
116          */
117         if ((starting_point + WCC->Hdr->HR.dav_depth) >= 1) {
118                 wprintf("<response>");
119                         wprintf("<href>");
120                                 groupdav_identify_host();
121                                 wprintf("/groupdav");
122                         wprintf("</href>");
123                         wprintf("<propstat>");
124                                 wprintf("<status>HTTP/1.1 200 OK</status>");
125                                 wprintf("<prop>");
126                                         wprintf("<displayname>GroupDAV</displayname>");
127                                         wprintf("<resourcetype><collection/></resourcetype>");
128                                         wprintf("<getlastmodified>");
129                                                 escputs(datestring);
130                                         wprintf("</getlastmodified>");
131                                 wprintf("</prop>");
132                         wprintf("</propstat>");
133                 wprintf("</response>");
134         }
135
136         /*
137          * Now go through the list and make it look like a DAV collection
138          */
139         serv_puts("LKRA");
140         serv_getln(buf, sizeof buf);
141         if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
142
143                 extract_token(roomname, buf, 0, '|', sizeof roomname);
144                 view = extract_int(buf, 7);
145                 mtime = extract_long(buf, 8);
146                 http_datestring(datestring, sizeof datestring, mtime);
147
148                 /*
149                  * For now, only list rooms that we know a GroupDAV client
150                  * might be interested in.  In the future we may add
151                  * the rest.
152                  *
153                  * We determine the type of objects which are stored in each
154                  * room by looking at the *default* view for the room.  This
155                  * allows, for example, a Calendar room to appear as a
156                  * GroupDAV calendar even if the user has switched it to a
157                  * Calendar List view.
158                  */
159                 if (    (view == VIEW_CALENDAR) || 
160                         (view == VIEW_TASKS) || 
161                         (view == VIEW_ADDRESSBOOK) ||
162                         (view == VIEW_NOTES) ||
163                         (view == VIEW_JOURNAL) ||
164                         (view == VIEW_WIKI)
165                 ) {
166                         is_groupware_collection = 1;
167                 }
168                 else {
169                         is_groupware_collection = 0;
170                 }
171
172                 if ( (is_groupware_collection) && ((starting_point + WCC->Hdr->HR.dav_depth) >= 2) ) {
173                         wprintf("<response>");
174
175                         wprintf("<href>");
176                         groupdav_identify_host();
177                         wprintf("/groupdav/");
178                         urlescputs(roomname);
179                         wprintf("/</href>");
180
181                         wprintf("<propstat>");
182                         wprintf("<status>HTTP/1.1 200 OK</status>");
183                         wprintf("<prop>");
184                         wprintf("<displayname>");
185                         escputs(roomname);
186                         wprintf("</displayname>");
187                         wprintf("<resourcetype><collection/>");
188
189                         switch(view) {
190                         case VIEW_CALENDAR:
191                                 wprintf("<G:vevent-collection />");
192                                 break;
193                         case VIEW_TASKS:
194                                 wprintf("<G:vtodo-collection />");
195                                 break;
196                         case VIEW_ADDRESSBOOK:
197                                 wprintf("<G:vcard-collection />");
198                                 break;
199                         case VIEW_NOTES:
200                                 wprintf("<G:vnotes-collection />");
201                                 break;
202                         case VIEW_JOURNAL:
203                                 wprintf("<G:vjournal-collection />");
204                                 break;
205                         case VIEW_WIKI:
206                                 wprintf("<G:wiki-collection />");
207                                 break;
208                         }
209
210                         wprintf("</resourcetype>");
211                         wprintf("<getlastmodified>");
212                                 escputs(datestring);
213                         wprintf("</getlastmodified>");
214                         wprintf("</prop>");
215                         wprintf("</propstat>");
216                         wprintf("</response>");
217                 }
218         }
219         wprintf("</multistatus>\n");
220
221         end_burst();
222 }
223
224
225
226 /*
227  * The pathname is always going to be /groupdav/room_name/msg_num
228  */
229 void groupdav_propfind(void) 
230 {
231         wcsession *WCC = WC;
232         StrBuf *dav_roomname;
233         StrBuf *dav_uid;
234         StrBuf *MsgNum;
235         long BufLen;
236         long dav_msgnum = (-1);
237         char uid[256];
238         char encoded_uid[256];
239         long *msgs = NULL;
240         int num_msgs = 0;
241         int i;
242         char datestring[256];
243         time_t now;
244
245         now = time(NULL);
246         http_datestring(datestring, sizeof datestring, now);
247
248         dav_roomname = NewStrBuf();
249         dav_uid = NewStrBuf();
250         StrBufExtract_token(dav_roomname, WCC->Hdr->HR.ReqLine, 0, '/');
251         StrBufExtract_token(dav_uid, WCC->Hdr->HR.ReqLine, 1, '/');
252
253         /*
254          * If the room name is blank, the client is requesting a
255          * folder list.
256          */
257         if (StrLength(dav_roomname) == 0) {
258                 groupdav_collection_list();
259                 FreeStrBuf(&dav_roomname);
260                 FreeStrBuf(&dav_uid);
261                 return;
262         }
263
264         /* Go to the correct room. */
265         if (strcasecmp(ChrPtr(WCC->wc_roomname), ChrPtr(dav_roomname))) {
266                 gotoroom(dav_roomname);
267         }
268         if (strcasecmp(ChrPtr(WCC->wc_roomname), ChrPtr(dav_roomname))) {
269                 hprintf("HTTP/1.1 404 not found\r\n");
270                 groupdav_common_headers();
271                 hprintf("Date: %s\r\n", datestring);
272                 hprintf("Content-Type: text/plain\r\n");
273                 wprintf("There is no folder called \"%s\" on this server.\r\n",
274                         ChrPtr(dav_roomname)
275                 );
276                 end_burst();
277                 FreeStrBuf(&dav_roomname);
278                 FreeStrBuf(&dav_uid);
279                 return;
280         }
281
282         /* If dav_uid is non-empty, client is requesting a PROPFIND on
283          * a specific item in the room.  This is not valid GroupDAV, but
284          * it is valid WebDAV.
285          */
286         if (StrLength(dav_uid) != 0) {
287
288                 dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
289                 if (dav_msgnum < 0) {
290                         hprintf("HTTP/1.1 404 not found\r\n");
291                         groupdav_common_headers();
292                         hprintf("Content-Type: text/plain\r\n");
293                         wprintf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
294                                 ChrPtr(dav_uid),
295                                 ChrPtr(dav_roomname)
296                         );
297                         end_burst();
298                         FreeStrBuf(&dav_roomname);
299                         FreeStrBuf(&dav_uid);
300                         return;
301                 }
302
303                 /* Be rude.  Completely ignore the XML request and simply send them
304                  * everything we know about (which is going to simply be the ETag and
305                  * nothing else).  Let the client-side parser sort it out.
306                  */
307                 hprintf("HTTP/1.0 207 Multi-Status\r\n");
308                 groupdav_common_headers();
309                 hprintf("Date: %s\r\n", datestring);
310                 hprintf("Content-type: text/xml\r\n");
311                 hprintf("Content-encoding: identity\r\n");
312         
313                 begin_burst();
314         
315                 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
316                         "<multistatus xmlns=\"DAV:\">"
317                 );
318
319                 wprintf("<response>");
320                 
321                 wprintf("<href>");
322                 groupdav_identify_host();
323                 wprintf("/groupdav/");
324                 urlescputs(ChrPtr(WCC->wc_roomname));
325                 euid_escapize(encoded_uid, ChrPtr(dav_uid));
326                 wprintf("/%s", encoded_uid);
327                 wprintf("</href>");
328                 wprintf("<propstat>");
329                 wprintf("<status>HTTP/1.1 200 OK</status>");
330                 wprintf("<prop>");
331                 wprintf("<getetag>\"%ld\"</getetag>", dav_msgnum);
332                 wprintf("<getlastmodified>");
333                 escputs(datestring);
334                 wprintf("</getlastmodified>");
335                 wprintf("</prop>");
336                 wprintf("</propstat>");
337
338                 wprintf("</response>\n");
339                 wprintf("</multistatus>\n");
340                 end_burst();
341                 FreeStrBuf(&dav_roomname);
342                 FreeStrBuf(&dav_uid);
343                 return;
344         }
345         FreeStrBuf(&dav_roomname);
346         FreeStrBuf(&dav_uid);
347
348
349         /*
350          * We got to this point, which means that the client is requesting
351          * a 'collection' (i.e. a list of all items in the room).
352          *
353          * Be rude.  Completely ignore the XML request and simply send them
354          * everything we know about (which is going to simply be the ETag and
355          * nothing else).  Let the client-side parser sort it out.
356          */
357         hprintf("HTTP/1.0 207 Multi-Status\r\n");
358         groupdav_common_headers();
359         hprintf("Date: %s\r\n", datestring);
360         hprintf("Content-type: text/xml\r\n");
361         hprintf("Content-encoding: identity\r\n");
362
363         begin_burst();
364
365         wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
366                 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
367         );
368
369
370         /* Transmit the collection resource (FIXME check depth and starting point) */
371         wprintf("<response>");
372
373         wprintf("<href>");
374         groupdav_identify_host();
375         wprintf("/groupdav/");
376         urlescputs(ChrPtr(WCC->wc_roomname));
377         wprintf("</href>");
378
379         wprintf("<propstat>");
380         wprintf("<status>HTTP/1.1 200 OK</status>");
381         wprintf("<prop>");
382         wprintf("<displayname>");
383         escputs(ChrPtr(WCC->wc_roomname));
384         wprintf("</displayname>");
385         wprintf("<resourcetype><collection/>");
386
387         switch(WCC->wc_default_view) {
388                 case VIEW_CALENDAR:
389                         wprintf("<G:vevent-collection />");
390                         break;
391                 case VIEW_TASKS:
392                         wprintf("<G:vtodo-collection />");
393                         break;
394                 case VIEW_ADDRESSBOOK:
395                         wprintf("<G:vcard-collection />");
396                         break;
397         }
398
399         wprintf("</resourcetype>");
400         /* FIXME get the mtime
401         wprintf("<getlastmodified>");
402                 escputs(datestring);
403         wprintf("</getlastmodified>");
404         */
405         wprintf("</prop>");
406         wprintf("</propstat>");
407         wprintf("</response>");
408
409         /* Transmit the collection listing (FIXME check depth and starting point) */
410
411         MsgNum = NewStrBuf();
412         serv_puts("MSGS ALL");
413
414         StrBuf_ServGetln(MsgNum);
415         if (GetServerStatus(MsgNum, NULL) == 1)
416                 while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000"))  {
417                         msgs = realloc(msgs, ++num_msgs * sizeof(long));
418                         msgs[num_msgs-1] = StrTol(MsgNum);
419                 }
420
421         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
422
423                 strcpy(uid, "");
424                 now = (-1);
425                 serv_printf("MSG0 %ld|3", msgs[i]);
426                 StrBuf_ServGetln(MsgNum);
427                 if (GetServerStatus(MsgNum, NULL) == 1)
428                         while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000")) 
429                         {
430                                 if (!strncasecmp(ChrPtr(MsgNum), "exti=", 5)) {
431                                         strcpy(uid, &ChrPtr(MsgNum)[5]);
432                                 }
433                                 else if (!strncasecmp(ChrPtr(MsgNum), "time=", 5)) {
434                                         now = atol(&ChrPtr(MsgNum)[5]);
435                         }
436                 }
437
438                 if (!IsEmptyStr(uid)) {
439                         wprintf("<response>");
440                                 wprintf("<href>");
441                                         groupdav_identify_host();
442                                         wprintf("/groupdav/");
443                                         urlescputs(ChrPtr(WCC->wc_roomname));
444                                         euid_escapize(encoded_uid, uid);
445                                         wprintf("/%s", encoded_uid);
446                                 wprintf("</href>");
447                                 switch(WCC->wc_default_view) {
448                                 case VIEW_CALENDAR:
449                                         wprintf("<getcontenttype>text/x-ical</getcontenttype>");
450                                         break;
451                                 case VIEW_TASKS:
452                                         wprintf("<getcontenttype>text/x-ical</getcontenttype>");
453                                         break;
454                                 case VIEW_ADDRESSBOOK:
455                                         wprintf("<getcontenttype>text/x-vcard</getcontenttype>");
456                                         break;
457                                 }
458                                 wprintf("<propstat>");
459                                         wprintf("<status>HTTP/1.1 200 OK</status>");
460                                         wprintf("<prop>");
461                                                 wprintf("<getetag>\"%ld\"</getetag>", msgs[i]);
462                                         if (now > 0L) {
463                                                 http_datestring(datestring, sizeof datestring, now);
464                                                 wprintf("<getlastmodified>");
465                                                 escputs(datestring);
466                                                 wprintf("</getlastmodified>");
467                                         }
468                                         wprintf("</prop>");
469                                 wprintf("</propstat>");
470                         wprintf("</response>");
471                 }
472         }
473         FreeStrBuf(&MsgNum);
474
475         wprintf("</multistatus>\n");
476         end_burst();
477
478         if (msgs != NULL) {
479                 free(msgs);
480         }
481 }