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