]> code.citadel.org Git - citadel.git/blob - webcit/calendar.c
* Save an incoming meeting request into the user's calendar.
[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, long msgnum, char *cal_partnum) {
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 calendar object
72  * ...at this point it's already been deserialized by cal_process_attachment()
73  */
74 void cal_process_object(icalcomponent *cal,
75                         int recursion_level,
76                         long msgnum,
77                         char *cal_partnum
78 ) {
79         icalcomponent *c;
80         icalproperty *method = NULL;
81         icalproperty_method the_method;
82         icalproperty *p;
83         struct icaltimetype t;
84         time_t tt;
85         char buf[SIZ];
86
87         /* Leading HTML for the display of this object */
88         if (recursion_level == 0) {
89                 wprintf("<CENTER><TABLE border=0 cellpadding=5>\n");
90         }
91
92         /* Look for a method */
93         method = icalcomponent_get_first_property(cal, ICAL_METHOD_PROPERTY);
94
95         /* See what we need to do with this */
96         if (method != NULL) {
97                 the_method = icalproperty_get_method(method);
98                 switch(the_method) {
99                     case ICAL_METHOD_REQUEST:
100                         wprintf("<TR><TD COLSPAN=2>\n"
101                                 "<IMG ALIGN=CENTER "
102                                 "SRC=\"/static/vcalendar.gif\">"
103                                 "&nbsp;&nbsp;"  
104                                 "<B>Meeting invitation</B>
105                                 </TD></TR>\n"
106                         );
107                         break;
108                     default:
109                         wprintf("<TR><TD COLSPAN=2>"
110                                 "I don't know what to do with this.</TD></TR>"
111                                 "\n");
112                         break;
113                 }
114         }
115
116         p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
117         if (p != NULL) {
118                 wprintf("<TR><TD><B>Summary:</B></TD><TD>");
119                 escputs((char *)icalproperty_get_comment(p));
120                 wprintf("</TD></TR>\n");
121         }
122
123         p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
124         if (p != NULL) {
125                 wprintf("<TR><TD><B>Location:</B></TD><TD>");
126                 escputs((char *)icalproperty_get_comment(p));
127                 wprintf("</TD></TR>\n");
128         }
129
130         /*
131          * Only show start/end times if we're actually looking at the VEVENT
132          * component.  Otherwise it shows bogus dates for things like timezone.
133          */
134         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
135
136                 p = icalcomponent_get_first_property(cal,
137                                                 ICAL_DTSTART_PROPERTY);
138                 if (p != NULL) {
139                         t = icalproperty_get_dtstart(p);
140                         tt = icaltime_as_timet(t);
141                         fmt_date(buf, tt);
142                         wprintf("<TR><TD><B>Starting date/time:</B></TD><TD>"
143                                 "%s</TD></TR>", buf
144                         );
145                 }
146         
147                 p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY);
148                 if (p != NULL) {
149                         t = icalproperty_get_dtend(p);
150                         tt = icaltime_as_timet(t);
151                         fmt_date(buf, tt);
152                         wprintf("<TR><TD><B>Ending date/time:</B></TD><TD>"
153                                 "%s</TD></TR>", buf
154                         );
155                 }
156
157         }
158
159         p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
160         if (p != NULL) {
161                 wprintf("<TR><TD><B>Description:</B></TD><TD>");
162                 escputs((char *)icalproperty_get_comment(p));
163                 wprintf("</TD></TR>\n");
164         }
165
166         /* If the component has subcomponents, recurse through them. */
167         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
168             (c != 0);
169             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
170                 /* Recursively process subcomponent */
171                 cal_process_object(c, recursion_level+1, msgnum, cal_partnum);
172         }
173
174         /* Trailing HTML for the display of this object */
175         if (recursion_level == 0) {
176                 wprintf("<TR><TD COLSPAN=2>"
177                         "<FORM METHOD=\"GET\" "
178                         "ACTION=\"/respond_to_request\">\n"
179                         "<INPUT TYPE=\"submit\" NAME=\"sc\" "
180                                 "VALUE=\"Accept\">\n"
181                         "&nbsp;&nbsp;"
182                         "<INPUT TYPE=\"submit\" NAME=\"sc\" "
183                                 "VALUE=\"Decline\">\n"
184                         "<INPUT TYPE=\"hidden\" NAME=\"msgnum\" "
185                                 "VALUE=\"%ld\">"
186                         "<INPUT TYPE=\"hidden\" NAME=\"cal_partnum\" "
187                                 "VALUE=\"%s\">"
188                         "</FORM>"
189                         "</TD></TR></TABLE></CENTER>\n",
190                         msgnum, cal_partnum
191                 );
192         }
193 }
194
195
196 /*
197  * Back end for cal_add() -- this writes it to the message base
198  */
199 void pencil_it_in(icalcomponent *cal) {
200         char hold_rm[SIZ];
201         char buf[SIZ];
202         char *serialized_event;
203
204         /* Save the name of the room we're in */
205         strcpy(hold_rm, WC->wc_roomname);
206
207         /* Go find the user's calendar */
208         serv_printf("GOTO %s", CALENDAR_ROOM_NAME);
209         serv_gets(buf);
210         if (buf[0] != '2') return;
211
212         /* Enter the message */
213         serialized_event = icalcomponent_as_ical_string(cal);
214         if (serialized_event != NULL) {
215                 sprintf(buf, "ENT0 1|||4||");
216                 serv_puts(buf);
217                 serv_gets(buf);
218                 if (buf[0] == '4') {
219                         serv_puts("Content-type: text/calendar");
220                         serv_puts("");
221                         serv_write(serialized_event, strlen(serialized_event));
222                         serv_puts("");
223                         serv_puts("000");
224                 }
225         }
226
227         /* Return to the room we were in */
228         serv_printf("GOTO %s", hold_rm);
229         serv_gets(buf);
230 }
231
232
233 /*
234  * Add a calendar object to the user's calendar
235  */
236 void cal_add(icalcomponent *cal, int recursion_level) {
237         icalcomponent *c;
238
239         /*
240          * The VEVENT subcomponent is the one we're interested in saving.
241          */
242         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
243                 /* Save to the message base */
244                 pencil_it_in(cal);
245
246         }
247
248         /* If the component has subcomponents, recurse through them. */
249         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
250             (c != 0);
251             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
252                 /* Recursively process subcomponent */
253                 cal_add(c, recursion_level+1);
254         }
255
256 }
257
258
259 /*
260  * Deserialize a calendar object in a message so it can be processed.
261  * (This is the main entry point for these things)
262  */
263 void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) {
264         icalcomponent *cal;
265
266         cal = icalcomponent_new_from_string(part_source);
267
268         if (cal == NULL) {
269                 wprintf("Error parsing calendar object: %s<BR>\n",
270                         icalerror_strerror(icalerrno));
271                 return;
272         }
273
274         cal_process_object(cal, 0, msgnum, cal_partnum);
275
276         /* Free the memory we obtained from libical's constructor */
277         icalcomponent_free(cal);
278 }
279
280
281
282
283 /*
284  * Respond to a meeting request
285  */
286 void respond_to_request(void) {
287         char buf[SIZ];
288         size_t total_len;
289         char *serialized_cal;
290         icalcomponent *cal;
291
292         output_headers(3);
293
294         wprintf("<TABLE WIDTH=100%% BORDER=0 BGCOLOR=007700><TR><TD>"
295                 "<FONT SIZE=+1 COLOR=\"FFFFFF\""
296                 "<B>Respond to meeting request</B>"
297                 "</FONT></TD></TR></TABLE><BR>\n"
298         );
299
300         sprintf(buf, "OPNA %s|%s", bstr("msgnum"), bstr("cal_partnum"));
301         serv_puts(buf);
302         serv_gets(buf);
303         if (buf[0] != '2') {
304                 wprintf("Error: %s<BR>\n", &buf[4]);
305                 wDumpContent(1);
306                 return;
307         }
308
309         total_len = atoi(&buf[4]);
310         serialized_cal = malloc(total_len + 1);
311
312         read_server_binary(serialized_cal, total_len);
313
314         serv_puts("CLOS");
315         serv_gets(buf);
316         serialized_cal[total_len + 1] = 0;
317
318         /* Deserialize it */
319         cal = icalcomponent_new_from_string(serialized_cal);
320         free(serialized_cal);
321
322         if (cal == NULL) {
323                 wprintf("Error parsing calendar object: %s<BR>\n",
324                         icalerror_strerror(icalerrno));
325                 wDumpContent(1);
326                 return;
327         }
328
329         /* Save this in the user's calendar if necessary */
330         if (!strcasecmp(bstr("sc"), "Accept")) {
331                 cal_add(cal, 0);
332         }
333
334         /* Send a reply if necessary */
335         /* FIXME ... do this */
336
337         /* Free the memory we obtained from libical's constructor */
338         icalcomponent_free(cal);
339
340         /* Delete the message from the inbox */
341         /* FIXME ... do this */
342
343
344         wprintf("Done!<BR>\n");
345
346         /* ...and now we're done. */
347         wDumpContent(1);
348 }
349
350
351
352
353 /*****************************************************************************/
354
355
356
357 /*
358  * Display handlers for message reading
359  */
360
361
362
363 /*
364  * If we're reading calendar items, just store them for now.  We have to
365  * sort and re-output them later when we draw the calendar.
366  */
367 void display_individual_cal(icalcomponent *cal, long msgnum) {
368
369         WC->num_cal += 1;
370
371         WC->disp_cal = realloc(WC->disp_cal,
372                         (sizeof(icalcomponent *) * WC->num_cal) );
373         WC->disp_cal[WC->num_cal - 1] = icalcomponent_new_clone(cal);
374
375         WC->cal_msgnum = realloc(WC->cal_msgnum,
376                         (sizeof(long) * WC->num_cal) );
377         WC->cal_msgnum[WC->num_cal - 1] = msgnum;
378 }
379
380
381
382 /*
383  * Display a task in the task list
384  */
385 void display_individual_task(icalcomponent *vtodo, long msgnum) {
386         icalproperty *p;
387
388         p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY);
389         wprintf("<LI><A HREF=\"/display_edit_task?msgnum=%ld\">", msgnum);
390         if (p != NULL) {
391                 escputs((char *)icalproperty_get_comment(p));
392         }
393         wprintf("</A>\n");
394 }
395
396
397 /*
398  * Display a task by itself (for editing)
399  */
400 void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum) {
401         icalcomponent *vtodo;
402         icalproperty *p;
403         struct icaltimetype t;
404         time_t now;
405         int created_new_vtodo = 0;
406
407         now = time(NULL);
408
409         if (supplied_vtodo != NULL) {
410                 vtodo = supplied_vtodo;
411         }
412         else {
413                 vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
414                 created_new_vtodo = 1;
415         }
416
417         output_headers(3);
418         wprintf("<TABLE WIDTH=100%% BORDER=0 BGCOLOR=007700><TR><TD>"
419                 "<FONT SIZE=+1 COLOR=\"FFFFFF\""
420                 "<B>Edit task</B>"
421                 "</FONT></TD></TR></TABLE><BR>\n"
422         );
423
424         wprintf("<FORM METHOD=\"POST\" ACTION=\"/save_task\">\n");
425         wprintf("<INPUT TYPE=\"hidden\" NAME=\"msgnum\" VALUE=\"%ld\">\n",
426                 msgnum);
427
428         wprintf("Summary: "
429                 "<INPUT TYPE=\"text\" NAME=\"summary\" "
430                 "MAXLENGTH=\"64\" SIZE=\"64\" VALUE=\"");
431         p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY);
432         if (p != NULL) {
433                 escputs((char *)icalproperty_get_comment(p));
434         }
435         wprintf("\"><BR>\n");
436
437         wprintf("Start date: ");
438         p = icalcomponent_get_first_property(vtodo, ICAL_DTSTART_PROPERTY);
439         if (p != NULL) {
440                 t = icalproperty_get_dtstart(p);
441         }
442         else {
443                 t = icaltime_from_timet(now, 0);
444         }
445         display_icaltimetype_as_webform(&t, "dtstart");
446         wprintf("<BR>\n");
447
448         wprintf("Due date: ");
449         p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY);
450         if (p != NULL) {
451                 t = icalproperty_get_due(p);
452         }
453         else {
454                 t = icaltime_from_timet(now, 0);
455         }
456         display_icaltimetype_as_webform(&t, "due");
457         wprintf("<BR>\n");
458
459         wprintf("<CENTER><TEXTAREA NAME=\"description\" wrap=soft "
460                 "ROWS=10 COLS=80 WIDTH=80>\n"
461         );
462         p = icalcomponent_get_first_property(vtodo, ICAL_DESCRIPTION_PROPERTY);
463         if (p != NULL) {
464                 escputs((char *)icalproperty_get_comment(p));
465         }
466         wprintf("</TEXTAREA><BR>\n");
467
468         wprintf("<INPUT TYPE=\"submit\" NAME=\"sc\" VALUE=\"Save\">"
469                 "&nbsp;&nbsp;"
470                 "<INPUT TYPE=\"submit\" NAME=\"sc\" VALUE=\"Delete\">\n"
471                 "&nbsp;&nbsp;"
472                 "<INPUT TYPE=\"submit\" NAME=\"sc\" VALUE=\"Cancel\">\n"
473                 "</CENTER>\n"
474         );
475
476         wprintf("</FORM>\n");
477
478         wDumpContent(1);
479
480         if (created_new_vtodo) {
481                 icalcomponent_free(vtodo);
482         }
483 }
484
485 /*
486  * Save an edited task
487  */
488 void save_individual_task(icalcomponent *supplied_vtodo, long msgnum) {
489         char buf[SIZ];
490         int delete_existing = 0;
491         icalproperty *prop;
492         icalcomponent *vtodo;
493         int created_new_vtodo = 0;
494
495         if (supplied_vtodo != NULL) {
496                 vtodo = supplied_vtodo;
497         }
498         else {
499                 vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
500                 created_new_vtodo = 1;
501         }
502
503         if (!strcasecmp(bstr("sc"), "Save")) {
504
505                 /* Replace values in the component with ones from the form */
506
507                 while (prop = icalcomponent_get_first_property(vtodo,
508                       ICAL_SUMMARY_PROPERTY), prop != NULL) {
509                         icalcomponent_remove_property(vtodo, prop);
510                 }
511                 icalcomponent_add_property(vtodo,
512                         icalproperty_new_summary(bstr("summary")));
513                 
514                 while (prop = icalcomponent_get_first_property(vtodo,
515                       ICAL_DESCRIPTION_PROPERTY), prop != NULL) {
516                         icalcomponent_remove_property(vtodo, prop);
517                 }
518                 icalcomponent_add_property(vtodo,
519                         icalproperty_new_description(bstr("description")));
520         
521                 while (prop = icalcomponent_get_first_property(vtodo,
522                       ICAL_DTSTART_PROPERTY), prop != NULL) {
523                         icalcomponent_remove_property(vtodo, prop);
524                 }
525                 icalcomponent_add_property(vtodo,
526                         icalproperty_new_dtstart(
527                                 icaltime_from_webform("dtstart")
528                         )
529                 );
530         
531                 while (prop = icalcomponent_get_first_property(vtodo,
532                       ICAL_DUE_PROPERTY), prop != NULL) {
533                         icalcomponent_remove_property(vtodo, prop);
534                 }
535                 icalcomponent_add_property(vtodo,
536                         icalproperty_new_due(
537                                 icaltime_from_webform("due")
538                         )
539                 );
540         
541                 /* Serialize it and save it to the message base */
542                 serv_puts("ENT0 1|||4");
543                 serv_gets(buf);
544                 if (buf[0] == '4') {
545                         serv_puts("Content-type: text/calendar");
546                         serv_puts("");
547                         serv_puts(icalcomponent_as_ical_string(vtodo));
548                         serv_puts("000");
549                         delete_existing = 1;
550                 }
551         }
552
553         /*
554          * If the user clicked 'Delete' then delete it, period.
555          */
556         if (!strcasecmp(bstr("sc"), "Delete")) {
557                 delete_existing = 1;
558         }
559
560         if ( (delete_existing) && (msgnum > 0L) ) {
561                 serv_printf("DELE %ld", atol(bstr("msgnum")));
562                 serv_gets(buf);
563         }
564
565         if (created_new_vtodo) {
566                 icalcomponent_free(vtodo);
567         }
568
569         /* Go back to the task list */
570         readloop("readfwd");
571 }
572
573
574
575 /*
576  * Code common to all display handlers.  Given a message number and a MIME
577  * type, we load the message and hunt for that MIME type.  If found, we load
578  * the relevant part, deserialize it into a libical component, filter it for
579  * the requested object type, and feed it to the specified handler.
580  */
581 void display_using_handler(long msgnum,
582                         char *mimetype,
583                         icalcomponent_kind which_kind,
584                         void (*callback)(icalcomponent *, long)
585         ) {
586         char buf[SIZ];
587         char mime_partnum[SIZ];
588         char mime_filename[SIZ];
589         char mime_content_type[SIZ];
590         char mime_disposition[SIZ];
591         int mime_length;
592         char relevant_partnum[SIZ];
593         char *relevant_source = NULL;
594         icalcomponent *cal, *c;
595
596         sprintf(buf, "MSG0 %ld|1", msgnum);     /* ask for headers only */
597         serv_puts(buf);
598         serv_gets(buf);
599         if (buf[0] != '1') return;
600
601         while (serv_gets(buf), strcmp(buf, "000")) {
602                 if (!strncasecmp(buf, "part=", 5)) {
603                         extract(mime_filename, &buf[5], 1);
604                         extract(mime_partnum, &buf[5], 2);
605                         extract(mime_disposition, &buf[5], 3);
606                         extract(mime_content_type, &buf[5], 4);
607                         mime_length = extract_int(&buf[5], 5);
608
609                         if (!strcasecmp(mime_content_type, "text/calendar")) {
610                                 strcpy(relevant_partnum, mime_partnum);
611                         }
612
613                 }
614         }
615
616         if (strlen(relevant_partnum) > 0) {
617                 relevant_source = load_mimepart(msgnum, relevant_partnum);
618                 if (relevant_source != NULL) {
619
620                         cal = icalcomponent_new_from_string(relevant_source);
621                         if (cal != NULL) {
622
623                                 /* Simple components of desired type */
624                                 if (icalcomponent_isa(cal) == which_kind) {
625                                         callback(cal, msgnum);
626                                 }
627
628                                 /* Subcomponents of desired type */
629                                 for (c = icalcomponent_get_first_component(cal,
630                                     which_kind);
631                                     (c != 0);
632                                     c = icalcomponent_get_next_component(cal,
633                                     which_kind)) {
634                                         callback(c, msgnum);
635                                 }
636                                 icalcomponent_free(cal);
637                         }
638                         free(relevant_source);
639                 }
640         }
641
642 }
643
644 void display_calendar(long msgnum) {
645         display_using_handler(msgnum, "text/calendar",
646                                 ICAL_VEVENT_COMPONENT,
647                                 display_individual_cal);
648 }
649
650 void display_task(long msgnum) {
651         display_using_handler(msgnum, "text/calendar",
652                                 ICAL_VTODO_COMPONENT,
653                                 display_individual_task);
654 }
655
656 void display_edit_task(void) {
657         long msgnum = 0L;
658
659         msgnum = atol(bstr("msgnum"));
660         if (msgnum > 0L) {
661                 /* existing task */
662                 display_using_handler(msgnum, "text/calendar",
663                                 ICAL_VTODO_COMPONENT,
664                                 display_edit_individual_task);
665         }
666         else {
667                 /* new task */
668                 display_edit_individual_task(NULL, 0L);
669         }
670 }
671
672 void save_task(void) {
673         long msgnum = 0L;
674
675         msgnum = atol(bstr("msgnum"));
676         if (msgnum > 0L) {
677                 display_using_handler(msgnum, "text/calendar",
678                                 ICAL_VTODO_COMPONENT,
679                                 save_individual_task);
680         }
681         else {
682                 save_individual_task(NULL, 0L);
683         }
684 }
685
686 void display_edit_event(void) {
687         long msgnum = 0L;
688
689         msgnum = atol(bstr("msgnum"));
690         if (msgnum > 0L) {
691                 /* existing event */
692                 display_using_handler(msgnum, "text/calendar",
693                                 ICAL_VEVENT_COMPONENT,
694                                 display_edit_individual_event);
695         }
696         else {
697                 /* new event */
698                 display_edit_individual_event(NULL, 0L);
699         }
700 }
701
702 void save_event(void) {
703         long msgnum = 0L;
704
705         msgnum = atol(bstr("msgnum"));
706
707         if (msgnum > 0L) {
708                 display_using_handler(msgnum, "text/calendar",
709                                 ICAL_VEVENT_COMPONENT,
710                                 save_individual_event);
711         }
712         else {
713                 save_individual_event(NULL, 0L);
714         }
715 }
716
717 #endif /* HAVE_ICAL_H */