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