* added some missing license declarations
[citadel.git] / webcit / calendar.c
1 /*
2  * $Id$
3  *
4  * Functions which handle calendar objects and their processing/display.
5  *
6  * Copyright (c) 1996-2010 by the citadel.org team
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include "webcit.h"
24 #include "webserver.h"
25 #include "calendar.h"
26
27 /*
28  * Process a calendar object.  At this point it's already been deserialized by cal_process_attachment()
29  *
30  * cal:                 the calendar object
31  * recursion_level:     Number of times we've recursed into this function
32  * msgnum:              Message number on the Citadel server
33  * cal_partnum:         MIME part number within that message containing the calendar object
34  */
35 void cal_process_object(StrBuf *Target,
36                         icalcomponent *cal,
37                         int recursion_level,
38                         long msgnum,
39                         const char *cal_partnum) 
40 {
41         icalcomponent *c;
42         icalproperty *method = NULL;
43         icalproperty_method the_method = ICAL_METHOD_NONE;
44         icalproperty *p;
45         struct icaltimetype t;
46         time_t tt;
47         char buf[256];
48         char conflict_name[256];
49         char conflict_message[256];
50         int is_update = 0;
51         char divname[32];
52         static int divcount = 0;
53
54         sprintf(divname, "rsvp%04x", ++divcount);
55
56         /* Convert timezones to something easy to display.
57          * It's safe to do this in memory because we're only changing it on the
58          * display side -- when we tell the server to do something with the object,
59          * the server will be working with its original copy in the database.
60          */
61         if ((cal) && (recursion_level == 0)) {
62                 ical_dezonify(cal);
63         }
64
65         /* Leading HTML for the display of this object */
66         if (recursion_level == 0) {
67                 StrBufAppendPrintf(Target, "<div class=\"mimepart\">\n");
68         }
69
70         /* Look for a method */
71         method = icalcomponent_get_first_property(cal, ICAL_METHOD_PROPERTY);
72
73         /* See what we need to do with this */
74         if (method != NULL) {
75                 char *title;
76                 the_method = icalproperty_get_method(method);
77
78                 StrBufAppendPrintf(Target, "<div id=\"%s_title\">", divname);
79                 StrBufAppendPrintf(Target, "<img src=\"static/calarea_48x.gif\">");
80                 StrBufAppendPrintf(Target, "<span>");
81                 switch(the_method) {
82                 case ICAL_METHOD_REQUEST:
83                         title = _("Meeting invitation");
84                         break;
85                 case ICAL_METHOD_REPLY:
86                         title = _("Attendee's reply to your invitation");
87                         break;
88                 case ICAL_METHOD_PUBLISH:
89                         title = _("Published event");
90                         break;
91                 default:
92                         title = _("This is an unknown type of calendar item.");
93                         break;
94                 }
95                 StrBufAppendPrintf(Target, "</span>");
96
97                 StrBufAppendPrintf(Target, "&nbsp;&nbsp;%s",title);
98                 StrBufAppendPrintf(Target, "</div>");
99         }
100
101         StrBufAppendPrintf(Target, "<dl>");
102         p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
103         if (p != NULL) {
104                 StrBufAppendPrintf(Target, "<dt>");
105                 StrBufAppendPrintf(Target, _("Summary:"));
106                 StrBufAppendPrintf(Target, "</dt><dd>");
107                 StrEscAppend(Target, NULL, (char *)icalproperty_get_comment(p), 0, 0);
108                 StrBufAppendPrintf(Target, "</dd>\n");
109         }
110
111         p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
112         if (p != NULL) {
113                 StrBufAppendPrintf(Target, "<dt>");
114                 StrBufAppendPrintf(Target, _("Location:"));
115                 StrBufAppendPrintf(Target, "</dt><dd>");
116                 StrEscAppend(Target, NULL, (char *)icalproperty_get_comment(p), 0, 0);
117                 StrBufAppendPrintf(Target, "</dd>\n");
118         }
119
120         /*
121          * Only show start/end times if we're actually looking at the VEVENT
122          * component.  Otherwise it shows bogus dates for things like timezone.
123          */
124         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
125
126                 p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
127                 if (p != NULL) {
128                         t = icalproperty_get_dtstart(p);
129
130                         if (t.is_date) {
131                                 struct tm d_tm;
132                                 char d_str[32];
133                                 memset(&d_tm, 0, sizeof d_tm);
134                                 d_tm.tm_year = t.year - 1900;
135                                 d_tm.tm_mon = t.month - 1;
136                                 d_tm.tm_mday = t.day;
137                                 wc_strftime(d_str, sizeof d_str, "%x", &d_tm);
138                                 StrBufAppendPrintf(Target, "<dt>");
139                                 StrBufAppendPrintf(Target, _("Date:"));
140                                 StrBufAppendPrintf(Target, "</dt><dd>%s</dd>", d_str);
141                         }
142                         else {
143                                 tt = icaltime_as_timet(t);
144                                 webcit_fmt_date(buf, 256, tt, DATEFMT_FULL);
145                                 StrBufAppendPrintf(Target, "<dt>");
146                                 StrBufAppendPrintf(Target, _("Starting date/time:"));
147                                 StrBufAppendPrintf(Target, "</dt><dd>%s</dd>", buf);
148                         }
149                 }
150         
151                 p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY);
152                 if (p != NULL) {
153                         t = icalproperty_get_dtend(p);
154                         tt = icaltime_as_timet(t);
155                         webcit_fmt_date(buf, 256, tt, DATEFMT_FULL);
156                         StrBufAppendPrintf(Target, "<dt>");
157                         StrBufAppendPrintf(Target, _("Ending date/time:"));
158                         StrBufAppendPrintf(Target, "</dt><dd>%s</dd>", buf);
159                 }
160
161         }
162
163         p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
164         if (p != NULL) {
165                 StrBufAppendPrintf(Target, "<dt>");
166                 StrBufAppendPrintf(Target, _("Description:"));
167                 StrBufAppendPrintf(Target, "</dt><dd>");
168                 StrEscAppend(Target, NULL, (char *)icalproperty_get_comment(p), 0, 0);
169                 StrBufAppendPrintf(Target, "</dd>\n");
170         }
171
172         if (icalcomponent_get_first_property(cal, ICAL_RRULE_PROPERTY)) {
173                 /* Unusual string syntax used here in order to re-use existing translations */
174                 StrBufAppendPrintf(Target, "<dt>%s:</dt><dd>%s.</dd>\n",
175                         _("Recurrence"),
176                         _("This is a recurring event")
177                 );
178         }
179
180         /* If the component has attendees, iterate through them. */
181         for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); 
182              (p != NULL); 
183              p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
184                 StrBufAppendPrintf(Target, "<dt>");
185                 StrBufAppendPrintf(Target, _("Attendee:"));
186                 StrBufAppendPrintf(Target, "</dt><dd>");
187                 safestrncpy(buf, icalproperty_get_attendee(p), sizeof buf);
188                 if (!strncasecmp(buf, "MAILTO:", 7)) {
189
190                         /** screen name or email address */
191                         strcpy(buf, &buf[7]);
192                         striplt(buf);
193                         StrEscAppend(Target, NULL, buf, 0, 0);
194                         StrBufAppendPrintf(Target, " ");
195
196                         /** participant status */
197                         partstat_as_string(buf, p);
198                         StrEscAppend(Target, NULL, buf, 0, 0);
199                 }
200                 StrBufAppendPrintf(Target, "</dd>\n");
201         }
202
203         /* If the component has subcomponents, recurse through them. */
204         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
205              (c != 0);
206              c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
207                 /* Recursively process subcomponent */
208                 cal_process_object(Target, c, recursion_level+1, msgnum, cal_partnum);
209         }
210
211         /* If this is a REQUEST, display conflicts and buttons */
212         if (the_method == ICAL_METHOD_REQUEST) {
213
214                 /* Check for conflicts */
215                 lprintf(9, "Checking server calendar for conflicts...\n");
216                 serv_printf("ICAL conflicts|%ld|%s|", msgnum, cal_partnum);
217                 serv_getln(buf, sizeof buf);
218                 if (buf[0] == '1') {
219                         while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
220                                 extract_token(conflict_name, buf, 3, '|', sizeof conflict_name);
221                                 is_update = extract_int(buf, 4);
222
223                                 if (is_update) {
224                                         snprintf(conflict_message, sizeof conflict_message,
225                                                  _("This is an update of '%s' which is already in your calendar."), conflict_name);
226                                 }
227                                 else {
228                                         snprintf(conflict_message, sizeof conflict_message,
229                                                  _("This event would conflict with '%s' which is already in your calendar."), conflict_name);
230                                 }
231
232                                 StrBufAppendPrintf(Target, "<dt>%s",
233                                         (is_update ?
234                                          _("Update:") :
235                                          _("CONFLICT:")
236                                                 )
237                                         );
238                                 StrBufAppendPrintf(Target, "</dt><dd>");
239                                 StrEscAppend(Target, NULL, conflict_message, 0, 0);
240                                 StrBufAppendPrintf(Target, "</dd>\n");
241                         }
242                 }
243                 lprintf(9, "...done.\n");
244
245                 StrBufAppendPrintf(Target, "</dl>");
246
247                 /* Display the Accept/Decline buttons */
248                 StrBufAppendPrintf(Target, "<p id=\"%s_question\">"
249                         "%s "
250                         "&nbsp;&nbsp;&nbsp;<span class=\"button_link\"> "
251                         "<a href=\"javascript:RespondToInvitation('%s_question','%s_title','%ld','%s','Accept');\">%s</a>"
252                         "</span>&nbsp;&nbsp;&nbsp;<span class=\"button_link\">"
253                         "<a href=\"javascript:RespondToInvitation('%s_question','%s_title','%ld','%s','Tentative');\">%s</a>"
254                         "</span>&nbsp;&nbsp;&nbsp;<span class=\"button_link\">"
255                         "<a href=\"javascript:RespondToInvitation('%s_question','%s_title','%ld','%s','Decline');\">%s</a>"
256                         "</span></p>\n",
257                         divname,
258                         _("How would you like to respond to this invitation?"),
259                         divname, divname, msgnum, cal_partnum, _("Accept"),
260                         divname, divname, msgnum, cal_partnum, _("Tentative"),
261                         divname, divname, msgnum, cal_partnum, _("Decline")
262                         );
263
264         }
265
266         /* If this is a REPLY, display update button */
267         if (the_method == ICAL_METHOD_REPLY) {
268
269                 /* Display the update buttons */
270                 StrBufAppendPrintf(Target, "<p id=\"%s_question\" >"
271                         "%s "
272                         "&nbsp;&nbsp;&nbsp;<span class=\"button_link\"> "
273                         "<a href=\"javascript:HandleRSVP('%s_question','%s_title','%ld','%s','Update');\">%s</a>"
274                         "</span>&nbsp;&nbsp;&nbsp;<span class=\"button_link\">"
275                         "<a href=\"javascript:HandleRSVP('%s_question','%s_title','%ld','%s','Ignore');\">%s</a>"
276                         "</span></p>\n",
277                         divname,
278                         _("Click <i>Update</i> to accept this reply and update your calendar."),
279                         divname, divname, msgnum, cal_partnum, _("Update"),
280                         divname, divname, msgnum, cal_partnum, _("Ignore")
281                         );
282         
283         }
284         
285         /* Trailing HTML for the display of this object */
286         if (recursion_level == 0) {
287                 StrBufAppendPrintf(Target, "<p>&nbsp;</p></div>\n");
288         }
289 }
290
291
292 /*
293  * Deserialize a calendar object in a message so it can be displayed.
294  */
295 void cal_process_attachment(wc_mime_attachment *Mime) 
296 {
297         icalcomponent *cal;
298
299         cal = icalcomponent_new_from_string(ChrPtr(Mime->Data));
300         FlushStrBuf(Mime->Data);
301         if (cal == NULL) {
302                 StrBufAppendPrintf(Mime->Data, _("There was an error parsing this calendar item."));
303                 StrBufAppendPrintf(Mime->Data, "<br />\n");
304                 return;
305         }
306
307         cal_process_object(Mime->Data, cal, 0, Mime->msgnum, ChrPtr(Mime->PartNum));
308
309         /* Free the memory we obtained from libical's constructor */
310         icalcomponent_free(cal);
311 }
312
313
314
315
316 /*
317  * Respond to a meeting request - accept/decline meeting
318  */
319 void respond_to_request(void) 
320 {
321         char buf[1024];
322
323         begin_ajax_response();
324
325         serv_printf("ICAL respond|%s|%s|%s|",
326                 bstr("msgnum"),
327                 bstr("cal_partnum"),
328                 bstr("sc")
329         );
330         serv_getln(buf, sizeof buf);
331
332         if (buf[0] == '2') {
333                 wc_printf("<img src=\"static/calarea_48x.gif\"><span>");
334                 if (!strcasecmp(bstr("sc"), "accept")) {
335                         wc_printf(_("You have accepted this meeting invitation.  "
336                                 "It has been entered into your calendar.")
337                         );
338                 } else if (!strcasecmp(bstr("sc"), "tentative")) {
339                         wc_printf(_("You have tentatively accepted this meeting invitation.  "
340                                 "It has been 'pencilled in' to your calendar.")
341                         );
342                 } else if (!strcasecmp(bstr("sc"), "decline")) {
343                         wc_printf(_("You have declined this meeting invitation.  "
344                                   "It has <b>not</b> been entered into your calendar.")
345                                 );
346                 }
347                 wc_printf(" ");
348                 wc_printf(_("A reply has been sent to the meeting organizer."));
349                 wc_printf("</span>");
350         } else {
351                 wc_printf("<img align=\"center\" src=\"static/error.gif\"><span>");
352                 wc_printf("%s\n", &buf[4]);
353                 wc_printf("</span>");
354         }
355
356         end_ajax_response();
357 }
358
359
360
361 /*
362  * Handle an incoming RSVP
363  */
364 void handle_rsvp(void) 
365 {
366         char buf[1024];
367
368         begin_ajax_response();
369
370         serv_printf("ICAL handle_rsvp|%s|%s|%s|",
371                 bstr("msgnum"),
372                 bstr("cal_partnum"),
373                 bstr("sc")
374         );
375         serv_getln(buf, sizeof buf);
376
377         if (buf[0] == '2') {
378                 wc_printf("<img src=\"static/calarea_48x.gif\"><span>");
379                 if (!strcasecmp(bstr("sc"), "update")) {
380                         wc_printf(_("Your calendar has been updated to reflect this RSVP."));
381                 } else if (!strcasecmp(bstr("sc"), "ignore")) {
382                         wc_printf(_("You have chosen to ignore this RSVP. "
383                                   "Your calendar has <b>not</b> been updated.")
384                                 );
385                 }
386                 wc_printf("</span>");
387         } else {
388                 wc_printf("<img src=\"static/error.gif\"><span> %s\n", &buf[4]);
389                 wc_printf("</span>");
390         }
391
392         end_ajax_response();
393 }
394
395
396
397
398 /*
399  * free memory allocated using libical
400  */
401 void delete_cal(void *vCal)
402 {
403         disp_cal *Cal = (disp_cal*) vCal;
404         icalcomponent_free(Cal->cal);
405         free(Cal->from);
406         free(Cal);
407 }
408
409 /*
410  * This is the meat-and-bones of the first part of our two-phase calendar display.
411  * As we encounter calendar items in messages being read from the server, we break out
412  * any iCalendar objects and store them in a hash table.  Later on, the second phase will
413  * use this hash table to render the calendar for display.
414  */
415 void display_individual_cal(icalcomponent *cal, long msgnum, char *from, int unread, calview *calv)
416 {
417         icalproperty *ps = NULL;
418         struct icaltimetype dtstart, dtend;
419         struct icaldurationtype dur;
420         wcsession *WCC = WC;
421         disp_cal *Cal;
422         size_t len;
423         time_t final_recurrence = 0;
424         icalcomponent *cptr = NULL;
425
426         /* recur variables */
427         icalproperty *rrule = NULL;
428         struct icalrecurrencetype recur;
429         icalrecur_iterator *ritr = NULL;
430         struct icaltimetype next;
431         int num_recur = 0;
432         int stop_rr = 0;
433
434         dtstart = icaltime_null_time();
435         dtend = icaltime_null_time();
436         
437         if (WCC->disp_cal_items == NULL)
438                 WCC->disp_cal_items = NewHash(0, Flathash);
439
440         /* Note: anything we do here, we also have to do below for the recurrences. */
441         Cal = (disp_cal*) malloc(sizeof(disp_cal));
442         memset(Cal, 0, sizeof(disp_cal));
443         Cal->cal = icalcomponent_new_clone(cal);
444
445         /* Dezonify and decapsulate at the very last moment */
446         /* lprintf(9, "INITIAL: %s\n", icaltime_as_ical_string(icalproperty_get_dtstart(
447                 icalcomponent_get_first_property(icalcomponent_get_first_component(
448                 Cal->cal, ICAL_VEVENT_COMPONENT), ICAL_DTSTART_PROPERTY)))
449         ); */
450         ical_dezonify(Cal->cal);
451         if (icalcomponent_isa(Cal->cal) != ICAL_VEVENT_COMPONENT) {
452                 cptr = icalcomponent_get_first_component(Cal->cal, ICAL_VEVENT_COMPONENT);
453                 if (cptr) {
454                         cptr = icalcomponent_new_clone(cptr);
455                         icalcomponent_free(Cal->cal);
456                         Cal->cal = cptr;
457                 }
458         }
459
460         Cal->unread = unread;
461         len = strlen(from);
462         Cal->from = (char*)malloc(len+ 1);
463         memcpy(Cal->from, from, len + 1);
464         Cal->cal_msgnum = msgnum;
465
466         /* Precalculate the starting date and time of this event, and store it in our top-level
467          * structure.  Later, when we are rendering the calendar, we can just peek at these values
468          * without having to break apart every calendar item.
469          */
470         ps = icalcomponent_get_first_property(Cal->cal, ICAL_DTSTART_PROPERTY);
471         if (ps != NULL) {
472                 dtstart = icalproperty_get_dtstart(ps);
473                 Cal->event_start = icaltime_as_timet(dtstart);
474         }
475
476         /* Do the same for the ending date and time.  It makes the day view much easier to render. */
477         ps = icalcomponent_get_first_property(Cal->cal, ICAL_DTEND_PROPERTY);
478         if (ps != NULL) {
479                 dtend = icalproperty_get_dtend(ps);
480                 Cal->event_end = icaltime_as_timet(dtend);
481         }
482
483         /* Store it in the hash list. */
484         Put(WCC->disp_cal_items, 
485             (char*) &Cal->event_start,
486             sizeof(Cal->event_start), 
487             Cal, 
488             delete_cal);
489
490         /****************************** handle recurring events ******************************/
491
492         if (icaltime_is_null_time(dtstart)) return;     /* Can't recur without a start time */
493
494         if (!icaltime_is_null_time(dtend)) {            /* Need duration for recurrences */
495                 dur = icaltime_subtract(dtend, dtstart);
496         }
497
498         /*
499          * Just let libical iterate the recurrence, and keep looping back to the top of this function,
500          * adding new hash entries that all point back to the same msgnum, until either the iteration
501          * stops or some outer bound is reached.  The display code will automatically do the Right Thing.
502          */
503         cptr = cal;
504         if (icalcomponent_isa(cptr) != ICAL_VEVENT_COMPONENT) {
505                 cptr = icalcomponent_get_first_component(cptr, ICAL_VEVENT_COMPONENT);
506         }
507         if (!cptr) return;
508         ps = icalcomponent_get_first_property(cptr, ICAL_DTSTART_PROPERTY);
509         if (ps == NULL) return;
510         dtstart = icalproperty_get_dtstart(ps);
511         rrule = icalcomponent_get_first_property(cptr, ICAL_RRULE_PROPERTY);
512         if (!rrule) return;
513         recur = icalproperty_get_rrule(rrule);
514         ritr = icalrecur_iterator_new(recur, dtstart);
515         if (!ritr) return;
516
517         while (next = icalrecur_iterator_next(ritr), ((!icaltime_is_null_time(next))&&(!stop_rr)) ) {
518                 ++num_recur;
519                 if (num_recur > 1) {            /* Skip the first one.  We already did it at the root. */
520                         icalcomponent *cptr;
521                         /* lprintf(9, "REPEATS: %s\n", icaltime_as_ical_string(next)); */
522
523                         /* Note: anything we do here, we also have to do above for the root event. */
524                         Cal = (disp_cal*) malloc(sizeof(disp_cal));
525                         memset(Cal, 0, sizeof(disp_cal));
526                         Cal->cal = icalcomponent_new_clone(cal);
527                         Cal->unread = unread;
528                         len = strlen(from);
529                         Cal->from = (char*)malloc(len+ 1);
530                         memcpy(Cal->from, from, len + 1);
531                         Cal->cal_msgnum = msgnum;
532
533                         if (icalcomponent_isa(Cal->cal) == ICAL_VEVENT_COMPONENT) {
534                                 cptr = Cal->cal;
535                         }
536                         else {
537                                 cptr = icalcomponent_get_first_component(Cal->cal, ICAL_VEVENT_COMPONENT);
538                         }
539                         if (cptr) {
540                                 ps = icalcomponent_get_first_property(cptr, ICAL_DTSTART_PROPERTY);
541                                 if (ps != NULL) {
542                                         icalcomponent_remove_property(cptr, ps);
543                                         ps = icalproperty_new_dtstart(next);
544                                         icalcomponent_add_property(cptr, ps);
545         
546                                         Cal->event_start = icaltime_as_timet(next);
547                                         final_recurrence = Cal->event_start;
548                                 }
549
550                                 ps = icalcomponent_get_first_property(cptr, ICAL_DTEND_PROPERTY);
551                                 if (ps != NULL) {
552                                         icalcomponent_remove_property(cptr, ps);
553         
554                                         /* Make a new dtend */
555                                         ps = icalproperty_new_dtend(icaltime_add(next, dur));
556                 
557                                         /* and stick it somewhere */
558                                         icalcomponent_add_property(cptr, ps);
559                                 }
560
561                         }
562
563                         /* Dezonify and decapsulate at the very last moment */
564                         ical_dezonify(Cal->cal);
565                         if (icalcomponent_isa(Cal->cal) != ICAL_VEVENT_COMPONENT) {
566                                 cptr = icalcomponent_get_first_component(Cal->cal, ICAL_VEVENT_COMPONENT);
567                                 if (cptr) {
568                                         cptr = icalcomponent_new_clone(cptr);
569                                         icalcomponent_free(Cal->cal);
570                                         Cal->cal = cptr;
571                                 }
572                         }
573
574                         if ( (Cal->event_start > calv->lower_bound)
575                            && (Cal->event_start < calv->upper_bound) ) {
576                                 Put(WCC->disp_cal_items, 
577                                         (char*) &Cal->event_start,
578                                         sizeof(Cal->event_start), 
579                                         Cal, 
580                                         delete_cal
581                                 );
582                         }
583                         else {
584                                 delete_cal(Cal);
585                         }
586
587                         /* If an upper bound is set, stop when we go out of scope */
588                         if (final_recurrence > calv->upper_bound) stop_rr = 1;
589                 }
590         }
591         icalrecur_iterator_free(ritr);
592         /* lprintf(9, "Performed %d recurrences; final one is %s", num_recur, ctime(&final_recurrence)); */
593
594 }
595
596
597
598
599
600
601 void process_ical_object(long msgnum, int unread,
602                          char *from, 
603                          char *FlatIcal, 
604                          icalcomponent_kind which_kind,
605                          IcalCallbackFunc CallBack,
606                          calview *calv
607         ) 
608 {
609         icalcomponent *cal, *c;
610
611         cal = icalcomponent_new_from_string(FlatIcal);
612         if (cal != NULL) {
613
614                 /* A which_kind of (-1) means just load the whole thing */
615                 if (which_kind == (-1)) {
616                         CallBack(cal, msgnum, from, unread, calv);
617                 }
618                 
619                 /* Otherwise recurse and hunt */
620                 else {
621                         
622                         /* Simple components of desired type */
623                         if (icalcomponent_isa(cal) == which_kind) {
624                                 CallBack(cal, msgnum, from, unread, calv);
625                         }
626                         
627                         /* Subcomponents of desired type */
628                         for (c = icalcomponent_get_first_component(cal, which_kind);
629                              (c != 0);
630                              c = icalcomponent_get_next_component(cal, which_kind)) {
631                                 CallBack(c, msgnum, from, unread, calv);
632                         }
633                         
634                 }
635                 
636                 icalcomponent_free(cal);
637         }
638 }
639
640 /*
641  * Code common to all icalendar display handlers.  Given a message number and a MIME
642  * type, we load the message and hunt for that MIME type.  If found, we load
643  * the relevant part, deserialize it into a libical component, filter it for
644  * the requested object type, and feed it to the specified handler.
645  */
646 void load_ical_object(long msgnum, int unread,
647                       icalcomponent_kind which_kind,
648                       IcalCallbackFunc CallBack,
649                       calview *calv,
650                       int RenderAsync
651         ) 
652 {
653         StrBuf *Buf;
654         StrBuf *Data = NULL;
655         const char *bptr;
656         int Done = 0;
657         char from[128] = "";
658         char mime_partnum[256];
659         char mime_filename[256];
660         char mime_content_type[256];
661         char mime_disposition[256];
662         int mime_length;
663         char relevant_partnum[256];
664         char *relevant_source = NULL;
665         int phase = 0;                          /* 0 = citadel headers, 1 = mime headers, 2 = body */
666         char msg4_content_type[256] = "";
667         char msg4_content_encoding[256] = "";
668         int msg4_content_length = 0;
669
670         relevant_partnum[0] = '\0';
671         serv_printf("MSG4 %ld", msgnum);        /* we need the mime headers */
672         Buf = NewStrBuf();
673         StrBuf_ServGetln(Buf);
674         if (GetServerStatus(Buf, NULL) != 1) {
675                 FreeStrBuf (&Buf);
676                 return;
677         }
678         while (!Done && (StrBuf_ServGetln(Buf)>=0)) {
679                 if ( (StrLength(Buf)==3) && 
680                      !strcmp(ChrPtr(Buf), "000")) {
681                         Done = 1;
682                         break;
683                 }
684                 bptr = ChrPtr(Buf);
685                 switch (phase) {
686                 case 0:
687                         if (!strncasecmp(bptr, "part=", 5)) {
688                                 extract_token(mime_filename, &bptr[5], 1, '|', sizeof mime_filename);
689                                 extract_token(mime_partnum, &bptr[5], 2, '|', sizeof mime_partnum);
690                                 extract_token(mime_disposition, &bptr[5], 3, '|', sizeof mime_disposition);
691                                 extract_token(mime_content_type, &bptr[5], 4, '|', sizeof mime_content_type);
692                                 mime_length = extract_int(&bptr[5], 5);
693
694                                 if (  (!strcasecmp(mime_content_type, "text/calendar"))
695                                       || (!strcasecmp(mime_content_type, "application/ics"))
696                                       || (!strcasecmp(mime_content_type, "text/vtodo"))
697                                       || (!strcasecmp(mime_content_type, "text/todo"))
698                                         ) {
699                                         strcpy(relevant_partnum, mime_partnum);
700                                 }
701                         }
702                         else if (!strncasecmp(bptr, "from=", 4)) {
703                                 extract_token(from, bptr, 1, '=', sizeof(from));
704                         }
705                         else if ((phase == 0) && (!strncasecmp(bptr, "text", 4))) {
706                                 phase = 1;
707                         }
708                 break;
709                 case 1:
710                         if (!IsEmptyStr(bptr)) {
711                                 if (!strncasecmp(bptr, "Content-type: ", 14)) {
712                                         safestrncpy(msg4_content_type, &bptr[14], sizeof msg4_content_type);
713                                         striplt(msg4_content_type);
714                                 }
715                                 else if (!strncasecmp(bptr, "Content-transfer-encoding: ", 27)) {
716                                         safestrncpy(msg4_content_encoding, &bptr[27], sizeof msg4_content_encoding);
717                                         striplt(msg4_content_type);
718                                 }
719                                 else if ((!strncasecmp(bptr, "Content-length: ", 16))) {
720                                         msg4_content_length = atoi(&bptr[16]);
721                                 }
722                                 break;
723                         }
724                         else {
725                                 phase++;
726                                 
727                                 if ((msg4_content_length > 0)
728                                     && ( !strcasecmp(msg4_content_encoding, "7bit"))
729                                     && ((!strcasecmp(mime_content_type, "text/calendar"))
730                                         || (!strcasecmp(mime_content_type, "application/ics"))
731                                         || (!strcasecmp(mime_content_type, "text/vtodo"))
732                                         || (!strcasecmp(mime_content_type, "text/todo"))
733                                             )
734                                         ) 
735                                 {
736                                 }
737                         }
738                 case 2:
739                         if (Data == NULL)
740                                 Data = NewStrBufPlain(NULL, msg4_content_length * 2);
741                         if (msg4_content_length > 0) {
742                                 StrBuf_ServGetBLOBBuffered(Data, msg4_content_length);
743                                 phase ++;
744                         }
745                         else {
746                                 StrBufAppendBuf(Data, Buf, 0);
747                                 StrBufAppendBufPlain(Data, "\r\n", 1, 0);
748                         }
749                 case 3:
750                         StrBufAppendBuf(Data, Buf, 0);
751                 }
752         }
753         FreeStrBuf(&Buf);
754
755         /* If MSG4 didn't give us the part we wanted, but we know that we can find it
756          * as one of the other MIME parts, attempt to load it now.
757          */
758         if ((Data == NULL) && (!IsEmptyStr(relevant_partnum))) {
759                 Data = load_mimepart(msgnum, relevant_partnum);
760         }
761
762         if (Data != NULL) {
763                 relevant_source = (char*) ChrPtr(Data);
764                 process_ical_object(msgnum, unread,
765                                     from, 
766                                     relevant_source, 
767                                     which_kind,
768                                     CallBack,
769                                     calv);
770         }
771         FreeStrBuf (&Data);
772
773         icalmemory_free_ring();
774 }
775
776 /*
777  * Display a calendar item
778  */
779 int calendar_LoadMsgFromServer(SharedMessageStatus *Stat, 
780                                void **ViewSpecific, 
781                                message_summary* Msg, 
782                                int is_new, 
783                                int i)
784 {
785         calview *c = (calview*) *ViewSpecific;
786         load_ical_object(Msg->msgnum, is_new, (-1), display_individual_cal, c, 1);
787         return 0;
788 }
789
790 /*
791  * display the editor component for an event
792  */
793 void display_edit_event(void) {
794         long msgnum = 0L;
795
796         msgnum = lbstr("msgnum");
797         if (msgnum > 0L) {
798                 /* existing event */
799                 load_ical_object(msgnum, 0, ICAL_VEVENT_COMPONENT, display_edit_individual_event, NULL, 0);
800         }
801         else {
802                 /* new event */
803                 display_edit_individual_event(NULL, 0L, "", 0, NULL);
804         }
805 }
806
807 /*
808  * save an edited event
809  */
810 void save_event(void) {
811         long msgnum = 0L;
812
813         msgnum = lbstr("msgnum");
814
815         if (msgnum > 0L) {
816                 load_ical_object(msgnum, 0, (-1), save_individual_event, NULL, 0);
817         }
818         else {
819                 save_individual_event(NULL, 0L, "", 0, NULL);
820         }
821 }
822
823
824
825
826
827 /*
828  * Anonymous request of freebusy data for a user
829  */
830 void do_freebusy(void)
831 {
832         const char *req = ChrPtr(WC->Hdr->HR.ReqLine);
833         char who[SIZ];
834         char buf[SIZ];
835         int len;
836         long lines;
837
838         extract_token(who, req, 0, ' ', sizeof who);
839         if (!strncasecmp(who, "/freebusy/", 10)) {
840                 strcpy(who, &who[10]);
841         }
842         unescape_input(who);
843
844         len = strlen(who);
845         if ( (!strcasecmp(&who[len-4], ".vcf"))
846              || (!strcasecmp(&who[len-4], ".ifb"))
847              || (!strcasecmp(&who[len-4], ".vfb")) ) {
848                 who[len-4] = 0;
849         }
850
851         lprintf(9, "freebusy requested for <%s>\n", who);
852         serv_printf("ICAL freebusy|%s", who);
853         serv_getln(buf, sizeof buf);
854
855         if (buf[0] != '1') {
856                 hprintf("HTTP/1.1 404 %s\n", &buf[4]);
857                 output_headers(0, 0, 0, 0, 0, 0);
858                 hprintf("Content-Type: text/plain\r\n");
859                 wc_printf("%s\n", &buf[4]);
860                 end_burst();
861                 return;
862         }
863
864         read_server_text(WC->WBuf, &lines);
865         http_transmit_thing("text/calendar", 0);
866 }
867
868
869
870 int calendar_Cleanup(void **ViewSpecific)
871 {
872         calview *c;
873         
874         c = (calview *) *ViewSpecific;
875
876         wDumpContent(1);
877         free (c);
878         *ViewSpecific = NULL;
879
880         return 0;
881 }
882
883 int __calendar_Cleanup(void **ViewSpecific)
884 {
885         calview *c;
886         
887         c = (calview *) *ViewSpecific;
888
889         free (c);
890         *ViewSpecific = NULL;
891
892         return 0;
893 }
894
895
896 void 
897 InitModule_CALENDAR
898 (void)
899 {
900         RegisterReadLoopHandlerset(
901                 VIEW_CALENDAR,
902                 calendar_GetParamsGetServerCall,
903                 NULL,
904                 NULL,
905                 calendar_LoadMsgFromServer,
906                 calendar_RenderView_or_Tail,
907                 calendar_Cleanup);
908
909         RegisterReadLoopHandlerset(
910                 VIEW_CALBRIEF,
911                 calendar_GetParamsGetServerCall,
912                 NULL,
913                 NULL,
914                 calendar_LoadMsgFromServer,
915                 calendar_RenderView_or_Tail,
916                 calendar_Cleanup);
917
918
919
920         RegisterPreference("daystart", _("Calendar day view begins at:"), PRF_INT, NULL);
921         RegisterPreference("dayend", _("Calendar day view ends at:"), PRF_INT, NULL);
922         RegisterPreference("weekstart", _("Week starts on:"), PRF_INT, NULL);
923
924         WebcitAddUrlHandler(HKEY("freebusy"), "", 0, do_freebusy, COOKIEUNNEEDED|ANONYMOUS|FORCE_SESSIONCLOSE);
925         WebcitAddUrlHandler(HKEY("display_edit_task"), "", 0, display_edit_task, 0);
926         WebcitAddUrlHandler(HKEY("display_edit_event"), "", 0, display_edit_event, 0);
927         WebcitAddUrlHandler(HKEY("save_event"), "", 0, save_event, 0);
928         WebcitAddUrlHandler(HKEY("respond_to_request"), "", 0, respond_to_request, 0);
929         WebcitAddUrlHandler(HKEY("handle_rsvp"), "", 0, handle_rsvp, 0);
930 }