* Properly handle VEVENT saves (fixed nasty loopy loopy bug)
[citadel.git] / webcit / calendar.c
1 /*
2  * $Id$
3  *
4  * Functions which handle calendar objects and their processing/display.
5  *
6  */
7
8 #include <ctype.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13 #include <signal.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <sys/socket.h>
17 #include <limits.h>
18 #include <netinet/in.h>
19 #include <netdb.h>
20 #include <string.h>
21 #include <pwd.h>
22 #include <errno.h>
23 #include <stdarg.h>
24 #include <pthread.h>
25 #include <signal.h>
26 #include <time.h>
27 #include "webcit.h"
28 #include "webserver.h"
29
30 #ifndef HAVE_ICAL_H
31
32 /*
33  * Handler stubs for builds with no calendar library available
34  */
35 void cal_process_attachment(char *part_source) {
36
37         wprintf("<I>This message contains calendaring/scheduling information,"
38                 " but support for calendars is not available on this "
39                 "particular system.  Please ask your system administrator to "
40                 "install a new version of the Citadel web service with "
41                 "calendaring enabled.</I><BR>\n"
42         );
43
44 }
45
46 void display_calendar(long msgnum) {
47         wprintf("<i>Cannot display calendar item</i><br>\n");
48 }
49
50 void display_task(long msgnum) {
51         wprintf("<i>Cannot display item from task list</i><br>\n");
52 }
53
54 #else /* HAVE_ICAL_H */
55
56
57 /******   End of handler stubs.  Everything below this line is real.   ******/
58
59
60 /*
61  * Process a single calendar component.
62  * It won't be a compound component at this point because those have
63  * already been broken down by cal_process_object().
64  */
65 void cal_process_subcomponent(icalcomponent *cal) {
66         wprintf("cal_process_subcomponent() called<BR>\n");
67         wprintf("cal_process_subcomponent() exiting<BR>\n");
68 }
69
70
71
72
73
74 /*
75  * Process a calendar object
76  * ...at this point it's already been deserialized by cal_process_attachment()
77  */
78 void cal_process_object(icalcomponent *cal) {
79         icalcomponent *c;
80         int num_subcomponents = 0;
81
82         wprintf("cal_process_object() called<BR>\n");
83
84         /* Iterate through all subcomponents */
85         wprintf("Iterating through all sub-components<BR>\n");
86         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
87             (c != 0);
88             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
89                 cal_process_subcomponent(c);
90                 ++num_subcomponents;
91         }
92
93         /* Iterate through all subcomponents */
94         wprintf("Iterating through VEVENTs<BR>\n");
95         for (c = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
96             (c != 0);
97             c = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT)) {
98                 cal_process_subcomponent(c);
99                 --num_subcomponents;
100         }
101
102         /* Iterate through all subcomponents */
103         wprintf("Iterating through VTODOs<BR>\n");
104         for (c = icalcomponent_get_first_component(cal, ICAL_VTODO_COMPONENT);
105             (c != 0);
106             c = icalcomponent_get_next_component(cal, ICAL_VTODO_COMPONENT)) {
107                 cal_process_subcomponent(c);
108                 --num_subcomponents;
109         }
110
111         /* Iterate through all subcomponents */
112         wprintf("Iterating through VJOURNALs<BR>\n");
113         for (c = icalcomponent_get_first_component(cal, ICAL_VJOURNAL_COMPONENT);
114             (c != 0);
115             c = icalcomponent_get_next_component(cal, ICAL_VJOURNAL_COMPONENT)) {
116                 cal_process_subcomponent(c);
117                 --num_subcomponents;
118         }
119
120         /* Iterate through all subcomponents */
121         wprintf("Iterating through VCALENDARs<BR>\n");
122         for (c = icalcomponent_get_first_component(cal, ICAL_VCALENDAR_COMPONENT);
123             (c != 0);
124             c = icalcomponent_get_next_component(cal, ICAL_VCALENDAR_COMPONENT)) {
125                 cal_process_subcomponent(c);
126                 --num_subcomponents;
127         }
128
129         /* Iterate through all subcomponents */
130         wprintf("Iterating through VFREEBUSYs<BR>\n");
131         for (c = icalcomponent_get_first_component(cal, ICAL_VFREEBUSY_COMPONENT);
132             (c != 0);
133             c = icalcomponent_get_next_component(cal, ICAL_VFREEBUSY_COMPONENT)) {
134                 cal_process_subcomponent(c);
135                 --num_subcomponents;
136         }
137
138         /* Iterate through all subcomponents */
139         wprintf("Iterating through VALARMs<BR>\n");
140         for (c = icalcomponent_get_first_component(cal, ICAL_VALARM_COMPONENT);
141             (c != 0);
142             c = icalcomponent_get_next_component(cal, ICAL_VALARM_COMPONENT)) {
143                 cal_process_subcomponent(c);
144                 --num_subcomponents;
145         }
146
147         /* Iterate through all subcomponents */
148         wprintf("Iterating through VTIMEZONEs<BR>\n");
149         for (c = icalcomponent_get_first_component(cal, ICAL_VTIMEZONE_COMPONENT);
150             (c != 0);
151             c = icalcomponent_get_next_component(cal, ICAL_VTIMEZONE_COMPONENT)) {
152                 cal_process_subcomponent(c);
153                 --num_subcomponents;
154         }
155
156         /* Iterate through all subcomponents */
157         wprintf("Iterating through VSCHEDULEs<BR>\n");
158         for (c = icalcomponent_get_first_component(cal, ICAL_VSCHEDULE_COMPONENT);
159             (c != 0);
160             c = icalcomponent_get_next_component(cal, ICAL_VSCHEDULE_COMPONENT)) {
161                 cal_process_subcomponent(c);
162                 --num_subcomponents;
163         }
164
165         /* Iterate through all subcomponents */
166         wprintf("Iterating through VQUERYs<BR>\n");
167         for (c = icalcomponent_get_first_component(cal, ICAL_VQUERY_COMPONENT);
168             (c != 0);
169             c = icalcomponent_get_next_component(cal, ICAL_VQUERY_COMPONENT)) {
170                 cal_process_subcomponent(c);
171                 --num_subcomponents;
172         }
173
174         /* Iterate through all subcomponents */
175         wprintf("Iterating through VCARs<BR>\n");
176         for (c = icalcomponent_get_first_component(cal, ICAL_VCAR_COMPONENT);
177             (c != 0);
178             c = icalcomponent_get_next_component(cal, ICAL_VCAR_COMPONENT)) {
179                 cal_process_subcomponent(c);
180                 --num_subcomponents;
181         }
182
183         /* Iterate through all subcomponents */
184         wprintf("Iterating through VCOMMANDs<BR>\n");
185         for (c = icalcomponent_get_first_component(cal, ICAL_VCOMMAND_COMPONENT);
186             (c != 0);
187             c = icalcomponent_get_next_component(cal, ICAL_VCOMMAND_COMPONENT)) {
188                 cal_process_subcomponent(c);
189                 --num_subcomponents;
190         }
191
192         if (num_subcomponents != 0) {
193                 wprintf("Warning: %d subcomponents unhandled<BR>\n",
194                         num_subcomponents);
195         }
196
197         wprintf("cal_process_object() exiting<BR>\n");
198 }
199
200
201 /*
202  * Deserialize a calendar object in a message so it can be processed.
203  * (This is the main entry point for these things)
204  */
205 void cal_process_attachment(char *part_source) {
206         icalcomponent *cal;
207
208         wprintf("Processing calendar attachment<BR>\n");
209         cal = icalcomponent_new_from_string(part_source);
210
211         if (cal == NULL) {
212                 wprintf("Error parsing calendar object: %s<BR>\n",
213                         icalerror_strerror(icalerrno));
214                 return;
215         }
216
217         cal_process_object(cal);
218
219         /* Free the memory we obtained from libical's constructor */
220         icalcomponent_free(cal);
221 }
222
223 /*****************************************************************************/
224
225
226
227 /*
228  * Display handlers for message reading
229  */
230
231
232
233 /*
234  * If we're reading calendar items, just store them for now.  We have to
235  * sort and re-output them later when we draw the calendar.
236  */
237 void display_individual_cal(icalcomponent *cal, long msgnum) {
238
239         WC->num_cal += 1;
240
241         WC->disp_cal = realloc(WC->disp_cal,
242                         (sizeof(icalcomponent *) * WC->num_cal) );
243         WC->disp_cal[WC->num_cal - 1] = icalcomponent_new_clone(cal);
244
245         WC->cal_msgnum = realloc(WC->cal_msgnum,
246                         (sizeof(long) * WC->num_cal) );
247         WC->cal_msgnum[WC->num_cal - 1] = msgnum;
248 }
249
250
251
252 /*
253  * Display a task in the task list
254  */
255 void display_individual_task(icalcomponent *vtodo, long msgnum) {
256         icalproperty *p;
257
258         p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY);
259         wprintf("<LI><A HREF=\"/display_edit_task?msgnum=%ld\">", msgnum);
260         if (p != NULL) {
261                 escputs((char *)icalproperty_get_comment(p));
262         }
263         wprintf("</A>\n");
264 }
265
266
267 /*
268  * Display a task by itself (for editing)
269  */
270 void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum) {
271         icalcomponent *vtodo;
272         icalproperty *p;
273         struct icaltimetype t;
274         time_t now;
275         int created_new_vtodo = 0;
276
277         now = time(NULL);
278
279         if (supplied_vtodo != NULL) {
280                 vtodo = supplied_vtodo;
281         }
282         else {
283                 vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
284                 created_new_vtodo = 1;
285         }
286
287         output_headers(3);
288         wprintf("<TABLE WIDTH=100%% BORDER=0 BGCOLOR=007700><TR><TD>"
289                 "<FONT SIZE=+1 COLOR=\"FFFFFF\""
290                 "<B>Edit task</B>"
291                 "</FONT></TD></TR></TABLE><BR>\n"
292         );
293
294         wprintf("<FORM METHOD=\"POST\" ACTION=\"/save_task\">\n");
295         wprintf("<INPUT TYPE=\"hidden\" NAME=\"msgnum\" VALUE=\"%ld\">\n",
296                 msgnum);
297
298         wprintf("Summary: "
299                 "<INPUT TYPE=\"text\" NAME=\"summary\" "
300                 "MAXLENGTH=\"64\" SIZE=\"64\" VALUE=\"");
301         p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY);
302         if (p != NULL) {
303                 escputs((char *)icalproperty_get_comment(p));
304         }
305         wprintf("\"><BR>\n");
306
307         wprintf("Start date: ");
308         p = icalcomponent_get_first_property(vtodo, ICAL_DTSTART_PROPERTY);
309         if (p != NULL) {
310                 t = icalproperty_get_dtstart(p);
311         }
312         else {
313                 t = icaltime_from_timet(now, 0);
314         }
315         display_icaltimetype_as_webform(&t, "dtstart");
316         wprintf("<BR>\n");
317
318         wprintf("Due date: ");
319         p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY);
320         if (p != NULL) {
321                 t = icalproperty_get_due(p);
322         }
323         else {
324                 t = icaltime_from_timet(now, 0);
325         }
326         display_icaltimetype_as_webform(&t, "due");
327         wprintf("<BR>\n");
328
329         wprintf("<CENTER><TEXTAREA NAME=\"description\" wrap=soft "
330                 "ROWS=10 COLS=80 WIDTH=80>\n"
331         );
332         p = icalcomponent_get_first_property(vtodo, ICAL_DESCRIPTION_PROPERTY);
333         if (p != NULL) {
334                 escputs((char *)icalproperty_get_comment(p));
335         }
336         wprintf("</TEXTAREA><BR>\n");
337
338         wprintf("<INPUT TYPE=\"submit\" NAME=\"sc\" VALUE=\"Save\">"
339                 "&nbsp;&nbsp;"
340                 "<INPUT TYPE=\"submit\" NAME=\"sc\" VALUE=\"Delete\">\n"
341                 "&nbsp;&nbsp;"
342                 "<INPUT TYPE=\"submit\" NAME=\"sc\" VALUE=\"Cancel\">\n"
343                 "</CENTER>\n"
344         );
345
346         wprintf("</FORM>\n");
347
348         wDumpContent(1);
349
350         if (created_new_vtodo) {
351                 icalcomponent_free(vtodo);
352         }
353 }
354
355 /*
356  * Save an edited task
357  */
358 void save_individual_task(icalcomponent *supplied_vtodo, long msgnum) {
359         char buf[SIZ];
360         int delete_existing = 0;
361         icalproperty *prop;
362         icalcomponent *vtodo;
363         int created_new_vtodo = 0;
364
365         if (supplied_vtodo != NULL) {
366                 vtodo = supplied_vtodo;
367         }
368         else {
369                 vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
370                 created_new_vtodo = 1;
371         }
372
373         if (!strcasecmp(bstr("sc"), "Save")) {
374
375                 /* Replace values in the component with ones from the form */
376
377                 while (prop = icalcomponent_get_first_property(vtodo,
378                       ICAL_SUMMARY_PROPERTY), prop != NULL) {
379                         icalcomponent_remove_property(vtodo, prop);
380                 }
381                 icalcomponent_add_property(vtodo,
382                         icalproperty_new_summary(bstr("summary")));
383                 
384                 while (prop = icalcomponent_get_first_property(vtodo,
385                       ICAL_DESCRIPTION_PROPERTY), prop != NULL) {
386                         icalcomponent_remove_property(vtodo, prop);
387                 }
388                 icalcomponent_add_property(vtodo,
389                         icalproperty_new_description(bstr("description")));
390         
391                 while (prop = icalcomponent_get_first_property(vtodo,
392                       ICAL_DTSTART_PROPERTY), prop != NULL) {
393                         icalcomponent_remove_property(vtodo, prop);
394                 }
395                 icalcomponent_add_property(vtodo,
396                         icalproperty_new_dtstart(
397                                 icaltime_from_webform("dtstart")
398                         )
399                 );
400         
401                 while (prop = icalcomponent_get_first_property(vtodo,
402                       ICAL_DUE_PROPERTY), prop != NULL) {
403                         icalcomponent_remove_property(vtodo, prop);
404                 }
405                 icalcomponent_add_property(vtodo,
406                         icalproperty_new_due(
407                                 icaltime_from_webform("due")
408                         )
409                 );
410         
411                 /* Serialize it and save it to the message base */
412                 serv_puts("ENT0 1|||4");
413                 serv_gets(buf);
414                 if (buf[0] == '4') {
415                         serv_puts("Content-type: text/calendar");
416                         serv_puts("");
417                         serv_puts(icalcomponent_as_ical_string(vtodo));
418                         serv_puts("000");
419                         delete_existing = 1;
420                 }
421         }
422
423         /*
424          * If the user clicked 'Delete' then delete it, period.
425          */
426         if (!strcasecmp(bstr("sc"), "Delete")) {
427                 delete_existing = 1;
428         }
429
430         if ( (delete_existing) && (msgnum > 0L) ) {
431                 serv_printf("DELE %ld", atol(bstr("msgnum")));
432                 serv_gets(buf);
433         }
434
435         if (created_new_vtodo) {
436                 icalcomponent_free(vtodo);
437         }
438
439         /* Go back to the task list */
440         readloop("readfwd");
441 }
442
443
444
445 /*
446  * Code common to all display handlers.  Given a message number and a MIME
447  * type, we load the message and hunt for that MIME type.  If found, we load
448  * the relevant part, deserialize it into a libical component, filter it for
449  * the requested object type, and feed it to the specified handler.
450  */
451 void display_using_handler(long msgnum,
452                         char *mimetype,
453                         icalcomponent_kind which_kind,
454                         void (*callback)(icalcomponent *, long)
455         ) {
456         char buf[SIZ];
457         char mime_partnum[SIZ];
458         char mime_filename[SIZ];
459         char mime_content_type[SIZ];
460         char mime_disposition[SIZ];
461         int mime_length;
462         char relevant_partnum[SIZ];
463         char *relevant_source = NULL;
464         icalcomponent *cal, *c;
465
466         sprintf(buf, "MSG0 %ld|1", msgnum);     /* ask for headers only */
467         serv_puts(buf);
468         serv_gets(buf);
469         if (buf[0] != '1') return;
470
471         while (serv_gets(buf), strcmp(buf, "000")) {
472                 if (!strncasecmp(buf, "part=", 5)) {
473                         extract(mime_filename, &buf[5], 1);
474                         extract(mime_partnum, &buf[5], 2);
475                         extract(mime_disposition, &buf[5], 3);
476                         extract(mime_content_type, &buf[5], 4);
477                         mime_length = extract_int(&buf[5], 5);
478
479                         if (!strcasecmp(mime_content_type, "text/calendar")) {
480                                 strcpy(relevant_partnum, mime_partnum);
481                         }
482
483                 }
484         }
485
486         if (strlen(relevant_partnum) > 0) {
487                 relevant_source = load_mimepart(msgnum, relevant_partnum);
488                 if (relevant_source != NULL) {
489
490                         cal = icalcomponent_new_from_string(relevant_source);
491                         if (cal != NULL) {
492
493                                 /* Simple components of desired type */
494                                 if (icalcomponent_isa(cal) == which_kind) {
495                                         callback(cal, msgnum);
496                                 }
497
498                                 /* Subcomponents of desired type */
499                                 for (c = icalcomponent_get_first_component(cal,
500                                     which_kind);
501                                     (c != 0);
502                                     c = icalcomponent_get_next_component(cal,
503                                     which_kind)) {
504                                         callback(c, msgnum);
505                                 }
506                                 icalcomponent_free(cal);
507                         }
508                         free(relevant_source);
509                 }
510         }
511
512 }
513
514 void display_calendar(long msgnum) {
515         display_using_handler(msgnum, "text/calendar",
516                                 ICAL_VEVENT_COMPONENT,
517                                 display_individual_cal);
518 }
519
520 void display_task(long msgnum) {
521         display_using_handler(msgnum, "text/calendar",
522                                 ICAL_VTODO_COMPONENT,
523                                 display_individual_task);
524 }
525
526 void display_edit_task(void) {
527         long msgnum = 0L;
528
529         msgnum = atol(bstr("msgnum"));
530         if (msgnum > 0L) {
531                 /* existing task */
532                 display_using_handler(msgnum, "text/calendar",
533                                 ICAL_VTODO_COMPONENT,
534                                 display_edit_individual_task);
535         }
536         else {
537                 /* new task */
538                 display_edit_individual_task(NULL, 0L);
539         }
540 }
541
542 void save_task(void) {
543         long msgnum = 0L;
544
545         msgnum = atol(bstr("msgnum"));
546         if (msgnum > 0L) {
547                 display_using_handler(msgnum, "text/calendar",
548                                 ICAL_VTODO_COMPONENT,
549                                 save_individual_task);
550         }
551         else {
552                 save_individual_task(NULL, 0L);
553         }
554 }
555
556 void display_edit_event(void) {
557         long msgnum = 0L;
558
559         msgnum = atol(bstr("msgnum"));
560         if (msgnum > 0L) {
561                 /* existing event */
562                 display_using_handler(msgnum, "text/calendar",
563                                 ICAL_VEVENT_COMPONENT,
564                                 display_edit_individual_event);
565         }
566         else {
567                 /* new event */
568                 display_edit_individual_event(NULL, 0L);
569         }
570 }
571
572 void save_event(void) {
573         long msgnum = 0L;
574
575         msgnum = atol(bstr("msgnum"));
576
577         if (msgnum > 0L) {
578                 display_using_handler(msgnum, "text/calendar",
579                                 ICAL_VEVENT_COMPONENT,
580                                 save_individual_event);
581         }
582         else {
583                 save_individual_event(NULL, 0L);
584         }
585 }
586
587 #endif /* HAVE_ICAL_H */