1 /* ical_support.c -- Helper functions for libical
2  *
3  * Copyright (c) 1994-2015 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  *
42  */
43 
44 #include <config.h>
45 
46 #include <string.h>
47 #include <sysexits.h>
48 #include <syslog.h>
49 
50 
51 #include "assert.h"
52 #include "caldav_db.h"
53 #include "global.h"
54 #include "ical_support.h"
55 #include "message.h"
56 #include "strhash.h"
57 #include "util.h"
58 
59 #ifdef HAVE_ICAL
60 
ical_support_init(void)61 EXPORTED void ical_support_init(void)
62 {
63     /* Initialize timezones path */
64     const char *tzpath = config_getstring(IMAPOPT_ZONEINFO_DIR);
65     icalarray *timezones;
66 
67     if (tzpath) {
68         syslog(LOG_NOTICE, "using timezone data from zoneinfo_dir=%s", tzpath);
69         icaltimezone_set_zone_directory((char *) tzpath);
70         icaltimezone_set_tzid_prefix("");
71         icaltimezone_set_builtin_tzdata(1);
72     }
73     else {
74         syslog(LOG_NOTICE, "zoneinfo_dir is unset, libical will find "
75                            "its own timezone data");
76     }
77 
78     /* make sure libical actually finds some timezone data! */
79     assert(icalerrno == 0);
80     timezones = icaltimezone_get_builtin_timezones();
81     if (icalerrno != 0) {
82         syslog(LOG_ERR, "libical error while loading timezones: %s",
83                         icalerror_strerror(icalerrno));
84     }
85 
86     if (timezones->num_elements == 0) {
87         fatal("No timezones found! Please check/set zoneinfo_dir in imapd.conf",
88               EX_CONFIG);
89     }
90 
91     if (config_debug) {
92         size_t i;
93 
94         syslog(LOG_DEBUG, "%s: found " SIZE_T_FMT " timezones",
95                           __func__, timezones->num_elements);
96         for (i = 0; i < timezones->num_elements; i++) {
97             icaltimezone *tz = icalarray_element_at(timezones, i);
98             char *tzid = xstrdupsafe(icaltimezone_get_tzid(tz));
99             char *location = xstrdupsafe(icaltimezone_get_location(tz));
100             syslog(LOG_DEBUG, "%s: [" SIZE_T_FMT "] tzid=%s location=%s",
101                               __func__, i, tzid, location);
102             free(location);
103             free(tzid);
104         }
105     }
106 }
107 
cyrus_icalrestriction_check(icalcomponent * ical)108 EXPORTED int cyrus_icalrestriction_check(icalcomponent *ical)
109 {
110     icalcomponent *vtz;
111 
112     /* Strip COMMENT properties from VTIMEZONEs */
113     /* XXX  These were added by KSM in a previous version of vzic,
114        but libical doesn't allow them in its restrictions checks */
115     for (vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT);
116          vtz;
117          vtz = icalcomponent_get_next_component(ical, ICAL_VTIMEZONE_COMPONENT)) {
118         icalproperty *prop =
119             icalcomponent_get_first_property(vtz, ICAL_COMMENT_PROPERTY);
120 
121         if (prop) {
122             icalcomponent_remove_property(vtz, prop);
123             icalproperty_free(prop);
124         }
125     }
126 
127     return icalrestriction_check(ical);
128 }
129 
130 #if (SIZEOF_TIME_T > 4)
131 static time_t epoch    = (time_t) LONG_MIN;
132 static time_t eternity = (time_t) LONG_MAX;
133 #else
134 static time_t epoch    = (time_t) INT_MIN;
135 static time_t eternity = (time_t) INT_MAX;
136 #endif
137 
138 struct recurrence_data {
139     icalcomponent *comp;
140     icaltimetype dtstart;
141     icaltimetype dtend;
142     icaltime_span span; /* for sorting, etc */
143 };
144 
icalparameter_get_value_as_string(icalparameter * param)145 EXPORTED const char *icalparameter_get_value_as_string(icalparameter *param)
146 {
147     char *buf;
148 
149     buf = icalparameter_as_ical_string_r(param);
150     icalmemory_add_tmp_buffer(buf);
151 
152     buf = strchr(buf, '=');
153     if (*++buf == '"') *(strchr(++buf, '"')) = '\0';
154     return buf;
155 }
156 
157 EXPORTED struct icaldatetimeperiodtype
icalproperty_get_datetimeperiod(icalproperty * prop)158 icalproperty_get_datetimeperiod(icalproperty *prop)
159 {
160     struct icaldatetimeperiodtype ret = { icaltime_null_time(),
161                                           icalperiodtype_null_period() };
162     if (!prop) return ret;
163 
164     ret = icalvalue_get_datetimeperiod(icalproperty_get_value(prop));
165 
166     icalparameter *param =
167         icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER);
168 
169     if (param) {
170         const char *tzid = icalparameter_get_tzid(param);
171         icaltimezone *tz = NULL;
172 
173         icalcomponent *c;
174         for (c = icalproperty_get_parent(prop); c != NULL;
175              c = icalcomponent_get_parent(c)) {
176             tz = icalcomponent_get_timezone(c, tzid);
177             if (tz != NULL)
178                 break;
179         }
180 
181         if (tz == NULL)
182             tz = icaltimezone_get_builtin_timezone_from_tzid(tzid);
183 
184         if (tz != NULL) {
185             if (icalperiodtype_is_null_period(ret.period))
186                 ret.time = icaltime_set_timezone(&ret.time, tz);
187             else {
188                 ret.period.start = icaltime_set_timezone(&ret.period.start, tz);
189                 if (icaldurationtype_is_null_duration(ret.period.duration))
190                     ret.period.end = icaltime_set_timezone(&ret.period.end, tz);
191             }
192         }
193     }
194 
195     return ret;
196 }
197 
icalcomponent_get_mydatetime(icalcomponent * comp,icalproperty * prop)198 static struct icaltimetype icalcomponent_get_mydatetime(icalcomponent *comp, icalproperty *prop)
199 {
200     icalcomponent *c;
201     icalparameter *param;
202     struct icaltimetype ret;
203 
204     ret = icalvalue_get_datetime(icalproperty_get_value(prop));
205 
206     if ((param = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER)) != NULL) {
207         const char *tzid = icalparameter_get_tzid(param);
208         icaltimezone *tz = NULL;
209 
210         for (c = comp; c != NULL; c = icalcomponent_get_parent(c)) {
211             tz = icalcomponent_get_timezone(c, tzid);
212             if (tz != NULL)
213                 break;
214         }
215 
216         if (tz == NULL)
217             tz = icaltimezone_get_builtin_timezone_from_tzid(tzid);
218 
219         if (tz != NULL)
220             ret = icaltime_set_timezone(&ret, tz);
221     }
222 
223     return ret;
224 }
225 
226 
227 
sort_overrides(const void * ap,const void * bp)228 static int sort_overrides(const void *ap, const void *bp)
229 {
230     struct recurrence_data *a = (struct recurrence_data *)ap;
231     struct recurrence_data *b = (struct recurrence_data *)bp;
232 
233     return (a->span.start - b->span.start);
234 }
235 
_add_override(icalarray * array,icaltimetype dtstart,icaltimetype dtend,icalcomponent * comp,const icaltimezone * floatingtz)236 static struct recurrence_data *_add_override(icalarray *array,
237                                              icaltimetype dtstart,
238                                              icaltimetype dtend,
239                                              icalcomponent *comp,
240                                              const icaltimezone *floatingtz)
241 {
242     struct recurrence_data *data = NULL;
243     size_t i;
244 
245     time_t start = icaltime_to_timet(dtstart, floatingtz);
246     time_t end = icaltime_to_timet(dtend, floatingtz);
247 
248     for (i = 0; i < array->num_elements; i++) {
249         struct recurrence_data *item = icalarray_element_at(array, i);
250         if (item->span.start != start) continue;
251         data = item;
252         break;
253     }
254 
255     if (!data) {
256         struct recurrence_data new;
257         icalarray_append(array, &new);
258         data = icalarray_element_at(array, i);
259     }
260 
261     data->span.start = start;
262     data->dtstart = dtstart;
263     data->span.end = end;
264     data->dtend = dtend;
265     data->comp = comp;
266 
267     return data;
268 }
269 
icaltime_to_timet(icaltimetype t,const icaltimezone * floatingtz)270 EXPORTED time_t icaltime_to_timet(icaltimetype t, const icaltimezone *floatingtz)
271 {
272     if (icaltime_is_null_time(t))
273         return 0;
274 
275     const icaltimezone *zone = floatingtz;
276 
277     if (icaltime_is_utc(t))
278         zone = icaltimezone_get_utc_timezone();
279     else if (t.zone)
280         zone = t.zone;
281 
282     if (!zone) zone = icaltimezone_get_utc_timezone();
283 
284     return icaltime_as_timet_with_zone(t, zone);
285 }
286 
span_compare_range(icaltime_span * span,icaltime_span * range)287 static int span_compare_range(icaltime_span *span, icaltime_span *range)
288 {
289     if (span->start >= range->end) return 1;  /* span starts later than range */
290     if (span->end <= range->start) return -1; /* span ends earlier than range */
291     return 0; /* span overlaps range */
292 }
293 
icalcomponent_myforeach(icalcomponent * ical,struct icalperiodtype range,const icaltimezone * floatingtz,int (* callback)(icalcomponent * comp,icaltimetype start,icaltimetype end,void * data),void * callback_data)294 EXPORTED int icalcomponent_myforeach(icalcomponent *ical,
295                                    struct icalperiodtype range,
296                                    const icaltimezone *floatingtz,
297                                    int (*callback) (icalcomponent *comp,
298                                                     icaltimetype start,
299                                                     icaltimetype end,
300                                                     void *data),
301                                    void *callback_data)
302 {
303     icalarray *overrides = icalarray_new(sizeof(struct recurrence_data), 16);
304     struct icaldurationtype event_length = icaldurationtype_null_duration();
305     struct icaltimetype dtstart = icaltime_null_time();
306     icaltime_span range_span = {
307         icaltime_to_timet(range.start, NULL),
308         icaltime_to_timet(range.end, NULL), 0 /* is_busy */
309     };
310 
311     if (!range_span.start) range_span.start = epoch;
312     if (!range_span.end) {
313         if (!icaldurationtype_is_null_duration(range.duration)) {
314             icaltimetype end = icaltime_add(range.start, range.duration);
315             range_span.end = icaltime_to_timet(end, NULL);
316         }
317         else range_span.end = eternity;
318     }
319 
320     icalcomponent *mastercomp = NULL, *comp;
321 
322     switch (icalcomponent_isa(ical)) {
323     case ICAL_VCALENDAR_COMPONENT:
324         comp = icalcomponent_get_first_real_component(ical);
325         break;
326 
327     case ICAL_VAVAILABILITY_COMPONENT:
328         comp = icalcomponent_get_first_component(ical, ICAL_XAVAILABLE_COMPONENT);
329         break;
330 
331     default:
332         comp = ical;
333         break;
334     }
335 
336     icalcomponent_kind kind = icalcomponent_isa(comp);
337 
338     /* find the master component */
339     for (; comp; comp = icalcomponent_get_next_component(ical, kind)) {
340         icalproperty *prop =
341             icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY);
342         if (prop) continue;
343         mastercomp = comp;
344         break;
345     }
346 
347     /* find event length first, we'll need it for overrides */
348     if (mastercomp) {
349         dtstart = icalcomponent_get_dtstart(mastercomp);
350         event_length = icalcomponent_get_duration(mastercomp);
351         if (icaldurationtype_is_null_duration(event_length) &&
352             icaltime_is_date(dtstart)) {
353             event_length = icaldurationtype_from_int(60 * 60 * 24);  /* P1D */
354         }
355 
356         /* add any RDATEs first, since EXDATE items can override them */
357         icalproperty *prop;
358         for (prop = icalcomponent_get_first_property(mastercomp,
359                                                      ICAL_RDATE_PROPERTY);
360              prop;
361              prop = icalcomponent_get_next_property(mastercomp,
362                                                     ICAL_RDATE_PROPERTY)) {
363             struct icaldatetimeperiodtype rdate =
364                 icalproperty_get_datetimeperiod(prop);
365             icaltimetype mystart = rdate.time;
366             icaltimetype myend = rdate.time;
367             if (icalperiodtype_is_null_period(rdate.period)) {
368                 myend = icaltime_add(mystart, event_length);
369             }
370             else {
371                 mystart = rdate.period.start;
372                 if (icaldurationtype_is_null_duration(rdate.period.duration))
373                     myend = rdate.period.end;
374                 else
375                     myend = icaltime_add(mystart, rdate.period.duration);
376             }
377             if (icaltime_is_null_time(mystart))
378                 continue;
379 
380             _add_override(overrides, mystart, myend, mastercomp, floatingtz);
381         }
382 
383         /* track any EXDATEs */
384         for (prop = icalcomponent_get_first_property(mastercomp,
385                                                      ICAL_EXDATE_PROPERTY);
386              prop;
387              prop = icalcomponent_get_next_property(mastercomp,
388                                                     ICAL_EXDATE_PROPERTY)) {
389             struct icaltimetype exdate = icalcomponent_get_mydatetime(mastercomp, prop);
390             _add_override(overrides, exdate, exdate, NULL, floatingtz);
391         }
392     }
393 
394     /* finally, add any RECURRENCE-ID overrides
395        Per Cyrus Daboo, these should probably supercede EXDATEs
396        (don't throw away potentially valid data)
397     */
398     for (comp = icalcomponent_get_first_component(ical, kind);
399          comp;
400          comp = icalcomponent_get_next_component(ical, kind)) {
401         struct icaltimetype recur =
402             icalcomponent_get_recurrenceid_with_zone(comp);
403         if (icaltime_is_null_time(recur)) continue;
404 
405         /* this is definitely a recurrence override */
406         struct icaltimetype mystart = icalcomponent_get_dtstart(comp);
407         struct icaltimetype myend = icalcomponent_get_dtend(comp);
408 
409         if (icaltime_compare(mystart, recur)) {
410             /* DTSTART has changed: add an exception for RECURRENCE-ID */
411             _add_override(overrides, recur, recur, NULL, floatingtz);
412         }
413         _add_override(overrides, mystart, myend, comp, floatingtz);
414     }
415 
416     /* sort all overrides in order */
417     icalarray_sort(overrides, sort_overrides);
418 
419     /* now we can do the RRULE, because we have all overrides */
420     icalrecur_iterator *rrule_itr = NULL;
421     if (mastercomp) {
422         icalproperty *rrule =
423             icalcomponent_get_first_property(mastercomp, ICAL_RRULE_PROPERTY);
424         if (rrule) {
425             struct icalrecurrencetype recur = icalproperty_get_rrule(rrule);
426 
427             /* check if span of RRULE overlaps range */
428             icaltime_span recur_span = {
429                 icaltime_to_timet(dtstart, floatingtz),
430                 icaltime_to_timet(recur.until, NULL), 0 /* is_busy */
431             };
432             if (!recur_span.end) recur_span.end = eternity;
433 
434             if (!span_compare_range(&recur_span, &range_span)) {
435                 rrule_itr = icalrecur_iterator_new(recur, dtstart);
436                 if (rrule_itr && (recur.count > 0)) {
437                     icalrecur_iterator_set_start(rrule_itr, range.start);
438                 }
439             }
440         }
441     }
442 
443     size_t onum = 0;
444     struct recurrence_data *data = overrides->num_elements ?
445         icalarray_element_at(overrides, onum) : NULL;
446     struct icaltimetype ritem = rrule_itr ?
447         icalrecur_iterator_next(rrule_itr) : dtstart;
448 
449     while (data || !icaltime_is_null_time(ritem)) {
450         time_t otime = data ? data->span.start : eternity;
451         time_t rtime = icaltime_to_timet(ritem, floatingtz);
452 
453         if (icaltime_is_null_time(ritem) || (data && otime <= rtime)) {
454             /* an overridden recurrence */
455             if (data->comp &&
456                 !span_compare_range(&data->span, &range_span) &&
457                 !callback(data->comp, data->dtstart, data->dtend, callback_data))
458                 goto done;
459 
460             /* if they're both the same time, it's a precisely overridden
461              * recurrence, so increment both */
462             if (rtime == otime) {
463                 /* incr recurrences */
464                 ritem = rrule_itr ?
465                     icalrecur_iterator_next(rrule_itr) : icaltime_null_time();
466             }
467 
468             /* incr overrides */
469             onum++;
470             data = (onum < overrides->num_elements) ?
471                 icalarray_element_at(overrides, onum) : NULL;
472         }
473         else {
474             /* a non-overridden recurrence */
475             struct icaltimetype thisend = icaltime_add(ritem, event_length);
476             icaltime_span this_span = {
477                 rtime, icaltime_to_timet(thisend, floatingtz), 0 /* is_busy */
478             };
479             int r = span_compare_range(&this_span, &range_span);
480 
481             if (r > 0 || /* gone past the end of range */
482                 (!r && !callback(mastercomp, ritem, thisend, callback_data)))
483                 goto done;
484 
485             /* incr recurrences */
486             ritem = rrule_itr ?
487                 icalrecur_iterator_next(rrule_itr) : icaltime_null_time();
488         }
489     }
490 
491  done:
492     if (rrule_itr) icalrecur_iterator_free(rrule_itr);
493 
494     icalarray_free(overrides);
495 
496     return 0;
497 }
498 
icalcomponent_new_stream(struct mailbox * mailbox,const char * prodid,const char * name,const char * desc,const char * color)499 EXPORTED icalcomponent *icalcomponent_new_stream(struct mailbox *mailbox,
500                                                  const char *prodid,
501                                                  const char *name,
502                                                  const char *desc,
503                                                  const char *color)
504 {
505     struct buf buf = BUF_INITIALIZER;
506     icalcomponent *ical;
507     icalproperty *prop;
508 
509     buf_printf(&buf, "%x-%s-%u", strhash(config_servername),
510                mailbox->uniqueid, mailbox->i.uidvalidity);
511 
512     ical = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
513                                icalproperty_new_version("2.0"),
514                                icalproperty_new_prodid(prodid),
515                                icalproperty_new_uid(buf_cstring(&buf)),
516                                icalproperty_new_lastmodified(
517                                    icaltime_from_timet_with_zone(mailbox->index_mtime,
518                                                                  0, NULL)),
519                                icalproperty_new_name(name),
520                                0);
521 
522     buf_free(&buf);
523 
524     prop = icalproperty_new_x(name);
525     icalproperty_set_x_name(prop, "X-WR-CALNAME");
526     icalcomponent_add_property(ical, prop);
527 
528     if (desc) {
529         prop = icalproperty_new_description(desc);
530         icalcomponent_add_property(ical, prop);
531     }
532 
533     if (color) {
534         prop = icalproperty_new_color(color);
535         icalcomponent_add_property(ical, prop);
536     }
537 
538     return ical;
539 }
540 
ical_string_as_icalcomponent(const struct buf * buf)541 EXPORTED icalcomponent *ical_string_as_icalcomponent(const struct buf *buf)
542 {
543     return icalparser_parse_string(buf_cstring(buf));
544 }
545 
my_icalcomponent_as_ical_string(icalcomponent * comp)546 EXPORTED struct buf *my_icalcomponent_as_ical_string(icalcomponent* comp)
547 {
548     char *str = icalcomponent_as_ical_string_r(comp);
549     struct buf *ret = buf_new();
550 
551     buf_initm(ret, str, strlen(str));
552 
553     return ret;
554 }
555 
record_to_ical(struct mailbox * mailbox,const struct index_record * record,strarray_t * schedule_addresses)556 EXPORTED icalcomponent *record_to_ical(struct mailbox *mailbox,
557                               const struct index_record *record,
558                               strarray_t *schedule_addresses)
559 {
560     icalcomponent *ical = NULL;
561     message_t *m = message_new_from_record(mailbox, record);
562     struct buf buf = BUF_INITIALIZER;
563 
564     /* Load message containing the resource and parse iCal data */
565     if (!message_get_field(m, "rawbody", MESSAGE_RAW, &buf)) {
566         ical = icalparser_parse_string(buf_cstring(&buf));
567     }
568 
569     /* extract the schedule user header */
570     if (schedule_addresses) {
571         buf_reset(&buf);
572         if (!message_get_field(m, "x-schedule-user-address",
573                                MESSAGE_DECODED|MESSAGE_TRIM, &buf)) {
574             if (buf.len) {
575                 strarray_t *vals = strarray_split(buf_cstring(&buf), ",", STRARRAY_TRIM);
576                 int i;
577                 for (i = 0; i < strarray_size(vals); i++) {
578                     const char *email = strarray_nth(vals, i);
579                     if (!strncasecmp(email, "mailto:", 7)) email += 7;
580                     strarray_add(schedule_addresses, email);
581                 }
582                 strarray_free(vals);
583             }
584         }
585     }
586 
587     /* Remove all X-LIC-ERROR properties */
588     if (ical) icalcomponent_strip_errors(ical);
589 
590     buf_free(&buf);
591     message_unref(&m);
592     return ical;
593 }
594 
get_icalcomponent_errstr(icalcomponent * ical)595 EXPORTED const char *get_icalcomponent_errstr(icalcomponent *ical)
596 {
597     icalcomponent *comp;
598 
599     for (comp = icalcomponent_get_first_component(ical, ICAL_ANY_COMPONENT);
600          comp;
601          comp = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT)) {
602         icalproperty *prop;
603 
604         for (prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
605              prop;
606              prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY)) {
607 
608             if (icalproperty_isa(prop) == ICAL_XLICERROR_PROPERTY) {
609                 const char *errstr = icalproperty_get_xlicerror(prop);
610                 char propname[256];
611 
612                 if (!errstr) return "Unknown iCal parsing error";
613 
614                 /* Check if this is an empty property error */
615                 if (sscanf(errstr,
616                            "No value for %255s property", propname) == 1) {
617                     /* Empty LOCATION is OK */
618                     if (!strcasecmp(propname, "LOCATION")) continue;
619                     if (!strcasecmp(propname, "COMMENT")) continue;
620                     if (!strcasecmp(propname, "DESCRIPTION")) continue;
621                     if (!strcasecmp(propname, "SUMMARY")) continue;
622 
623                     /* For iOS 11 */
624                     if (!strcasecmp(propname, "URL")) continue;
625                 }
626                 else {
627                     /* Ignore unknown property errors */
628                     if (!strncmp(errstr, "Parse error in property name", 28))
629                         continue;
630                 }
631 
632                 return errstr;
633             }
634         }
635     }
636 
637     return NULL;
638 }
639 
640 
icalcomponent_remove_invitee(icalcomponent * comp,icalproperty * prop)641 EXPORTED void icalcomponent_remove_invitee(icalcomponent *comp,
642                                            icalproperty *prop)
643 {
644     if (icalcomponent_isa(comp) == ICAL_VPOLL_COMPONENT) {
645         icalcomponent *vvoter = icalproperty_get_parent(prop);
646 
647         icalcomponent_remove_component(comp, vvoter);
648         icalcomponent_free(vvoter);
649     }
650     else {
651         icalcomponent_remove_property(comp, prop);
652         icalproperty_free(prop);
653     }
654 }
655 
656 
icalcomponent_get_first_invitee(icalcomponent * comp)657 EXPORTED icalproperty *icalcomponent_get_first_invitee(icalcomponent *comp)
658 {
659     icalproperty *prop;
660 
661     if (icalcomponent_isa(comp) == ICAL_VPOLL_COMPONENT) {
662         icalcomponent *vvoter =
663             icalcomponent_get_first_component(comp, ICAL_VVOTER_COMPONENT);
664 
665         prop = icalcomponent_get_first_property(vvoter, ICAL_VOTER_PROPERTY);
666     }
667     else {
668         prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
669     }
670 
671     return prop;
672 }
673 
icalcomponent_get_next_invitee(icalcomponent * comp)674 EXPORTED icalproperty *icalcomponent_get_next_invitee(icalcomponent *comp)
675 {
676     icalproperty *prop;
677 
678     if (icalcomponent_isa(comp) == ICAL_VPOLL_COMPONENT) {
679         icalcomponent *vvoter =
680             icalcomponent_get_next_component(comp, ICAL_VVOTER_COMPONENT);
681 
682         prop = icalcomponent_get_first_property(vvoter, ICAL_VOTER_PROPERTY);
683     }
684     else {
685         prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY);
686     }
687 
688     return prop;
689 }
690 
icalproperty_get_invitee(icalproperty * prop)691 EXPORTED const char *icalproperty_get_invitee(icalproperty *prop)
692 {
693     const char *recip;
694 
695     if (icalproperty_isa(prop) == ICAL_VOTER_PROPERTY) {
696         recip = icalproperty_get_voter(prop);
697     }
698     else {
699         recip = icalproperty_get_attendee(prop);
700     }
701 
702     return recip;
703 }
704 
705 
706 EXPORTED icaltimetype
icalcomponent_get_recurrenceid_with_zone(icalcomponent * comp)707 icalcomponent_get_recurrenceid_with_zone(icalcomponent *comp)
708 {
709     icalproperty *prop =
710         icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY);
711 
712     struct icaldatetimeperiodtype dtp = icalproperty_get_datetimeperiod(prop);
713     return dtp.time;
714 }
715 
716 
icalcomponent_get_x_property_by_name(icalcomponent * comp,const char * name)717 EXPORTED icalproperty *icalcomponent_get_x_property_by_name(icalcomponent *comp,
718                                                             const char *name)
719 {
720     icalproperty *prop;
721 
722     for (prop = icalcomponent_get_first_property(comp, ICAL_X_PROPERTY);
723          prop && strcmp(icalproperty_get_x_name(prop), name);
724          prop = icalcomponent_get_next_property(comp, ICAL_X_PROPERTY));
725 
726     return prop;
727 }
728 
icaltime_convert_to_utc(const struct icaltimetype tt,icaltimezone * floating_zone)729 EXPORTED icaltimetype icaltime_convert_to_utc(const struct icaltimetype tt,
730                                               icaltimezone *floating_zone)
731 {
732     icaltimezone *from_zone = (icaltimezone *) tt.zone;
733     icaltimezone *utc = icaltimezone_get_utc_timezone();
734     struct icaltimetype ret = tt;
735 
736     /* If it's a date do nothing */
737     if (tt.is_date) {
738         return ret;
739     }
740 
741     if (tt.zone == utc) {
742         return ret;
743     }
744 
745     /* If it's a floating time use the passed in zone */
746     if (from_zone == NULL) {
747         from_zone = floating_zone;
748 
749         if (from_zone == NULL) {
750             /* Leave the time as floating */
751             return ret;
752         }
753     }
754 
755     icaltimezone_convert_time(&ret, from_zone, utc);
756     ret.zone = utc;
757 
758     return ret;
759 }
760 
761 
762 /* Get time period (start/end) of a component based in RFC 4791 Sec 9.9 */
763 EXPORTED struct icalperiodtype
icalcomponent_get_utc_timespan(icalcomponent * comp,icalcomponent_kind kind,icaltimezone * floating_tz)764 icalcomponent_get_utc_timespan(icalcomponent *comp,
765                                icalcomponent_kind kind,
766                                icaltimezone *floating_tz)
767 {
768     struct icalperiodtype period;
769 
770     period.start = icaltime_convert_to_utc(icalcomponent_get_dtstart(comp),
771                                            floating_tz);
772     period.end   = icaltime_convert_to_utc(icalcomponent_get_dtend(comp),
773                                            floating_tz);
774     period.duration = icaldurationtype_null_duration();
775 
776     switch (kind) {
777     case ICAL_VEVENT_COMPONENT:
778         if (icaltime_is_null_time(period.end)) {
779             /* No DTEND or DURATION */
780             if (icaltime_is_date(period.start)) {
781                 /* DTSTART is not DATE-TIME */
782                 struct icaldurationtype dur = icaldurationtype_null_duration();
783 
784                 dur.days = 1;
785                 period.end = icaltime_add(period.start, dur);
786             }
787             else
788                 memcpy(&period.end, &period.start, sizeof(struct icaltimetype));
789         }
790         break;
791 
792     case ICAL_VPOLL_COMPONENT:
793     case ICAL_VTODO_COMPONENT: {
794         struct icaltimetype due = (kind == ICAL_VPOLL_COMPONENT) ?
795             icalcomponent_get_dtend(comp) : icalcomponent_get_due(comp);
796 
797         if (!icaltime_is_null_time(period.start)) {
798             /* Has DTSTART */
799             if (icaltime_is_null_time(period.end)) {
800                 /* No DURATION */
801                 memcpy(&period.end, &period.start, sizeof(struct icaltimetype));
802 
803                 if (!icaltime_is_null_time(due)) {
804                     /* Has DUE (DTEND for VPOLL) */
805                     if (icaltime_compare(due, period.start) < 0)
806                         memcpy(&period.start, &due, sizeof(struct icaltimetype));
807                     if (icaltime_compare(due, period.end) > 0)
808                         memcpy(&period.end, &due, sizeof(struct icaltimetype));
809                 }
810             }
811         }
812         else {
813             icalproperty *prop;
814 
815             /* No DTSTART */
816             if (!icaltime_is_null_time(due)) {
817                 /* Has DUE (DTEND for VPOLL) */
818                 memcpy(&period.start, &due, sizeof(struct icaltimetype));
819                 memcpy(&period.end, &due, sizeof(struct icaltimetype));
820             }
821             else if ((prop =
822                       icalcomponent_get_first_property(comp, ICAL_COMPLETED_PROPERTY))) {
823                 /* Has COMPLETED */
824                 period.start =
825                     icaltime_convert_to_utc(icalproperty_get_completed(prop), NULL);
826                 memcpy(&period.end, &period.start, sizeof(struct icaltimetype));
827 
828                 if ((prop =
829                      icalcomponent_get_first_property(comp, ICAL_CREATED_PROPERTY))) {
830                     /* Has CREATED */
831                     struct icaltimetype created =
832                         icaltime_convert_to_utc(icalproperty_get_created(prop), NULL);
833                     if (icaltime_compare(created, period.start) < 0)
834                         memcpy(&period.start, &created, sizeof(struct icaltimetype));
835                     if (icaltime_compare(created, period.end) > 0)
836                         memcpy(&period.end, &created, sizeof(struct icaltimetype));
837                 }
838             }
839             else if ((prop =
840                       icalcomponent_get_first_property(comp, ICAL_CREATED_PROPERTY))) {
841                 /* Has CREATED */
842                 period.start =
843                     icaltime_convert_to_utc(icalproperty_get_created(prop), NULL);
844                 period.end = icaltime_from_timet_with_zone(caldav_eternity, 0, NULL);
845             }
846             else {
847                 /* Always */
848                 period.start = icaltime_from_timet_with_zone(caldav_epoch, 0, NULL);
849                 period.end   = icaltime_from_timet_with_zone(caldav_eternity, 0, NULL);
850             }
851         }
852         break;
853     }
854 
855     case ICAL_VJOURNAL_COMPONENT:
856         if (!icaltime_is_null_time(period.start)) {
857             /* Has DTSTART */
858             memcpy(&period.end, &period.start, sizeof(struct icaltimetype));
859 
860             if (icaltime_is_date(period.start)) {
861                 /* DTSTART is not DATE-TIME */
862                 struct icaldurationtype dur;
863 
864                 dur = icaldurationtype_from_int(60*60*24 - 1);  /* P1D */
865                 icaltime_add(period.end, dur);
866             }
867         }
868         else {
869             /* Never */
870             period.start = icaltime_from_timet_with_zone(caldav_eternity, 0, NULL);
871             period.end   = icaltime_from_timet_with_zone(caldav_epoch, 0, NULL);
872         }
873         break;
874 
875     case ICAL_VFREEBUSY_COMPONENT:
876         if (icaltime_is_null_time(period.start) ||
877             icaltime_is_null_time(period.end)) {
878             /* No DTSTART or DTEND */
879             icalproperty *fb =
880                 icalcomponent_get_first_property(comp, ICAL_FREEBUSY_PROPERTY);
881 
882             if (fb) {
883                 /* Has FREEBUSY */
884                 /* XXX  Convert FB period into our period */
885             }
886             else {
887                 /* Never */
888                 period.start = icaltime_from_timet_with_zone(caldav_eternity, 0, NULL);
889                 period.end   = icaltime_from_timet_with_zone(caldav_epoch, 0, NULL);
890             }
891         }
892         break;
893 
894     case ICAL_VAVAILABILITY_COMPONENT:
895         if (icaltime_is_null_time(period.start)) {
896             /* No DTSTART */
897             period.start = icaltime_from_timet_with_zone(caldav_epoch, 0, NULL);
898         }
899         if (icaltime_is_null_time(period.end)) {
900             /* No DTEND */
901             period.end = icaltime_from_timet_with_zone(caldav_eternity, 0, NULL);
902         }
903         break;
904 
905     default:
906         break;
907     }
908 
909     return period;
910 }
911 
912 
913 /* icalcomponent_foreach_recurrence() callback to find earliest/latest time */
utc_timespan_cb(icalcomponent * comp,struct icaltime_span * span,void * rock)914 static void utc_timespan_cb(icalcomponent *comp, struct icaltime_span *span, void *rock)
915 {
916     struct icalperiodtype *period = (struct icalperiodtype *) rock;
917     int is_date = icaltime_is_date(icalcomponent_get_dtstart(comp));
918     icaltimezone *utc = icaltimezone_get_utc_timezone();
919     struct icaltimetype start =
920         icaltime_from_timet_with_zone(span->start, is_date, utc);
921     struct icaltimetype end =
922         icaltime_from_timet_with_zone(span->end, is_date, utc);
923 
924     if (icaltime_compare(start, period->start) < 0)
925         memcpy(&period->start, &start, sizeof(struct icaltimetype));
926 
927     if (icaltime_compare(end, period->end) > 0)
928         memcpy(&period->end, &end, sizeof(struct icaltimetype));
929 }
930 
931 /* Determine the UTC time span of all components within ical of type kind. */
932 EXPORTED struct icalperiodtype
icalrecurrenceset_get_utc_timespan(icalcomponent * ical,icalcomponent_kind kind,icaltimezone * floating_tz,unsigned * is_recurring,void (* comp_cb)(icalcomponent *,void *),void * cb_rock)933 icalrecurrenceset_get_utc_timespan(icalcomponent *ical,
934                                    icalcomponent_kind kind,
935                                    icaltimezone *floating_tz,
936                                    unsigned *is_recurring,
937                                    void (*comp_cb)(icalcomponent*, void*),
938                                    void *cb_rock)
939 {
940     struct icalperiodtype span;
941     icalcomponent *comp = icalcomponent_get_first_component(ical, kind);
942     unsigned recurring = 0;
943 
944     /* Initialize span to be nothing */
945     span.start = icaltime_from_timet_with_zone(caldav_eternity, 0, NULL);
946     span.end = icaltime_from_timet_with_zone(caldav_epoch, 0, NULL);
947     span.duration = icaldurationtype_null_duration();
948 
949     do {
950         struct icalperiodtype period;
951         icalproperty *rrule;
952 
953         /* Get base dtstart and dtend */
954         period = icalcomponent_get_utc_timespan(comp, kind, floating_tz);
955 
956         /* See if its a recurring event */
957         rrule = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
958         if (rrule ||
959             icalcomponent_get_first_property(comp, ICAL_RDATE_PROPERTY) ||
960             icalcomponent_get_first_property(comp, ICAL_EXDATE_PROPERTY)) {
961             /* Recurring - find widest time range that includes events */
962             unsigned expand = recurring = 1;
963 
964             if (rrule) {
965                 struct icalrecurrencetype recur = icalproperty_get_rrule(rrule);
966 
967                 if (!icaltime_is_null_time(recur.until)) {
968                     /* Recurrence ends - calculate dtend of last recurrence */
969                     struct icaldurationtype duration;
970                     icaltimezone *utc = icaltimezone_get_utc_timezone();
971 
972                     duration = icaltime_subtract(period.end, period.start);
973                     period.end =
974                         icaltime_add(icaltime_convert_to_zone(recur.until, utc),
975                                 duration);
976 
977                     /* Do RDATE expansion only */
978                     /* Temporarily remove RRULE to allow for expansion of
979                      * remaining recurrences. */
980                     icalcomponent_remove_property(comp, rrule);
981                 }
982                 else if (!recur.count) {
983                     /* Recurrence never ends - set end of span to eternity */
984                     span.end =
985                         icaltime_from_timet_with_zone(caldav_eternity, 0, NULL);
986 
987                     /* Skip RRULE & RDATE expansion */
988                     expand = 0;
989                 }
990             }
991 
992             /* Expand (remaining) recurrences */
993             if (expand) {
994                 icalcomponent_foreach_recurrence(
995                         comp,
996                         icaltime_from_timet_with_zone(caldav_epoch, 0, NULL),
997                         icaltime_from_timet_with_zone(caldav_eternity, 0, NULL),
998                         utc_timespan_cb, &span);
999 
1000                 /* Add RRULE back, if we had removed it before. */
1001                 if (rrule && !icalproperty_get_parent(rrule)) {
1002                     icalcomponent_add_property(comp, rrule);
1003                 }
1004             }
1005         }
1006 
1007         /* Check our dtstart and dtend against span */
1008         if (icaltime_compare(period.start, span.start) < 0)
1009             memcpy(&span.start, &period.start, sizeof(struct icaltimetype));
1010 
1011         if (icaltime_compare(period.end, span.end) > 0)
1012             memcpy(&span.end, &period.end, sizeof(struct icaltimetype));
1013 
1014         /* Execute callback on this component */
1015         if (comp_cb) comp_cb(comp, cb_rock);
1016 
1017     } while ((comp = icalcomponent_get_next_component(ical, kind)));
1018 
1019     if (is_recurring) *is_recurring = recurring;
1020 
1021     return span;
1022 }
1023 
icaltime_set_utc(struct icaltimetype * t,int set)1024 EXPORTED void icaltime_set_utc(struct icaltimetype *t, int set)
1025 {
1026     icaltime_set_timezone(t, set ? icaltimezone_get_utc_timezone() : NULL);
1027 }
1028 
1029 
1030 enum {
1031     ACTION_UPDATE = 1,
1032     ACTION_DELETE,
1033     ACTION_SETPARAM
1034 };
1035 
1036 enum {
1037     SEGMENT_COMP = 1,
1038     SEGMENT_PROP,
1039     SEGMENT_PARAM
1040 };
1041 
1042 union match_criteria_t {
1043     struct {
1044         char *uid;                /* component UID (optional) */
1045         icaltimetype rid;         /* component RECURRENCE-ID (optional) */
1046     } comp;
1047     struct {
1048         char *param;              /* parameter name (optional) */
1049         char *value;              /* prop/param value (optional) */
1050         unsigned not:1;           /* not equal? */
1051     } prop;
1052 };
1053 
1054 struct path_segment_t {
1055     unsigned type;                    /* Is it comp, prop, or param segment? */
1056     unsigned kind;                    /* libical kind of comp, prop, or param */
1057     union match_criteria_t match;     /* match criteria (depends on 'type') */
1058     unsigned action;                  /* patch action (create,update,setparam)*/
1059     void *data;                       /* patch data (depends on 'action') */
1060 
1061     struct path_segment_t *sibling;
1062     struct path_segment_t *child;
1063 };
1064 
1065 struct patch_data_t {
1066     icalcomponent *patch;             /* component containing patch data */
1067     struct path_segment_t *delete;    /* list of PATCH-DELETE actions */
1068     struct path_segment_t *setparam;  /* list of PATCH-PARAMETER items */
1069 };
1070 
parse_target_path(char * path,struct path_segment_t ** path_seg,unsigned action,void * data,const char ** errstr)1071 static int parse_target_path(char *path, struct path_segment_t **path_seg,
1072                              unsigned action, void *data,
1073                              const char **errstr)
1074 {
1075     char *p, sep;
1076     struct path_segment_t *tail = NULL, *new;
1077 
1078     for (sep = *path++; sep == '/';) {
1079         p = path + strcspn(path, "[/#");
1080         if ((sep = *p)) *p++ = '\0';
1081 
1082         new = xzmalloc(sizeof(struct path_segment_t));
1083         new->type = SEGMENT_COMP;
1084         new->kind = icalcomponent_string_to_kind(path);
1085         /* Initialize RID as invalid time rather than NULL time
1086            since NULL time is used for empty RID (master component) */
1087         new->match.comp.rid.year = -1;
1088 
1089         if (!*path_seg) *path_seg = new;
1090         else tail->child = new;
1091         tail = new;
1092 
1093         path = p;
1094 
1095         if (sep == '[') {
1096             /* Parse comp-match */
1097             const char *prefix = "UID=";
1098             size_t prefix_len = strlen(prefix);
1099 
1100             if (!(p = strchr(path, ']'))) {
1101                 *errstr = "Badly formatted comp-match";
1102                 return -1;
1103             }
1104 
1105             /* Parse uid-match */
1106             if (!strncmp(path, prefix, prefix_len)) {
1107                 path += prefix_len;
1108                 *p++ = '\0';
1109                 new->match.comp.uid = xstrdup(path);
1110                 sep = *p++;
1111                 path = p;
1112             }
1113 
1114             /* Parse rid-match */
1115             if (sep == '[') {
1116                 prefix = "RID=";
1117                 prefix_len = strlen(prefix);
1118 
1119                 if (strncmp(path, prefix, prefix_len) ||
1120                     !(p = strchr(path, ']'))) {
1121                     *errstr = "Badly formatted rid-match";
1122                     return -1;
1123                 }
1124 
1125                 path += prefix_len;
1126                 *p++ = '\0';
1127                 if (*path && strcmp(path, "M")) {
1128                     new->match.comp.rid = icaltime_from_string(path);
1129                     if (icaltime_is_null_time(new->match.comp.rid)) {
1130                         *errstr = "Invalid recurrence-id";
1131                         return -1;
1132                     }
1133                 }
1134                 else new->match.comp.rid = icaltime_null_time();
1135 
1136                 sep = *p++;
1137                 path = p;
1138             }
1139         }
1140     }
1141 
1142     if (sep == '#' && !*path_seg) {
1143         /* Parse prop-segment */
1144         p = path + strcspn(path, "[;=");
1145         if ((sep = *p)) *p++ = '\0';
1146 
1147         new = xzmalloc(sizeof(struct path_segment_t));
1148         new->type = SEGMENT_PROP;
1149         new->kind = icalproperty_string_to_kind(path);
1150 
1151         if (!*path_seg) *path_seg = new;
1152         else tail->child = new;
1153         tail = new;
1154 
1155         path = p;
1156 
1157         if (sep == '[') {
1158             /* Parse prop-match (MUST start with '=' or '!' or '@') */
1159             if (strspn(path, "=!@") != 1 || !(p = strchr(path, ']'))) {
1160                 *errstr = "Badly formatted prop-match";
1161                 return -1;
1162             }
1163 
1164             *p++ = '\0';
1165             if (*path == '@') {
1166                 /* Parse param-match */
1167                 size_t namelen = strcspn(++path, "!=");
1168                 new->match.prop.param = xstrndup(path, namelen);
1169                 path += namelen;
1170             }
1171 
1172             if (*path) {
1173                 /* Parse prop/param [not]equal value */
1174                 if (*path++ == '!') new->match.prop.not = 1;
1175                 new->match.prop.value = xstrdup(path);
1176             }
1177 
1178             sep = *p++;
1179             path = p;
1180         }
1181 
1182         if (sep == ';') {
1183             /* Parse param-segment */
1184             p = path + strcspn(path, "=");
1185             if ((sep = *p)) *p++ = '\0';
1186 
1187             new = xzmalloc(sizeof(struct path_segment_t));
1188             new->type = SEGMENT_PARAM;
1189             new->kind = icalparameter_string_to_kind(path);
1190 
1191             tail->child = new;
1192             tail = new;
1193 
1194             path = p;
1195         }
1196 
1197         if (sep == '=' && action == ACTION_DELETE) {
1198             /* Parse value-segment */
1199             new->data = xstrdup(path);
1200         }
1201         else if (sep != '\0') {
1202             *errstr = "Invalid separator following prop-segment";
1203             return -1;
1204         }
1205     }
1206     else if (sep != '\0') {
1207         *errstr = "Invalid separator following comp-segment";
1208         return -1;
1209     }
1210 
1211     tail->action = action;
1212     if (!tail->data) tail->data = data;
1213 
1214     return 0;
1215 }
1216 
1217 static void apply_patch(struct path_segment_t *path_seg,
1218                         void *parent, int *num_changes);
1219 
remove_single_value(const char * oldstr,const char * single)1220 static char *remove_single_value(const char *oldstr, const char *single)
1221 {
1222     char *newstr = NULL;
1223     strarray_t *values = strarray_split(oldstr, ",", STRARRAY_TRIM);
1224     int idx = strarray_find(values, single, 0);
1225 
1226     if (idx >= 0) {
1227         /* Found the single value, remove it, and create new string */
1228         strarray_remove(values, idx);
1229         newstr = strarray_join(values, ",");
1230     }
1231     strarray_free(values);
1232 
1233     return newstr;
1234 }
1235 
1236 /* Apply a patch action to a parameter segment */
apply_patch_parameter(struct path_segment_t * path_seg,icalproperty * parent,int * num_changes)1237 static void apply_patch_parameter(struct path_segment_t *path_seg,
1238                                   icalproperty *parent, int *num_changes)
1239 {
1240     icalparameter *param =
1241         icalproperty_get_first_parameter(parent, path_seg->kind);
1242     if (!param) return;
1243 
1244     if (path_seg->action == ACTION_DELETE) {
1245         switch (path_seg->kind) {
1246         case ICAL_MEMBER_PARAMETER:
1247             /* Multi-valued parameter */
1248             if (path_seg->data) {
1249                 /* Check if entire parameter value == single value */
1250                 const char *single = (const char *) path_seg->data;
1251                 const char *param_val = icalparameter_get_value_as_string(param);
1252 
1253                 if (strcmp(param_val, single)) {
1254                     /* Not an exact match, try to remove single value */
1255                     char *newval = remove_single_value(param_val, single);
1256                     if (newval) {
1257                         *num_changes += 1;
1258                         icalparameter_set_member(param, newval);
1259                         free(newval);
1260                     }
1261                     break;
1262                 }
1263             }
1264 
1265             /* Fall through and delete entire parameter */
1266             GCC_FALLTHROUGH
1267 
1268         default:
1269             *num_changes += 1;
1270             icalproperty_remove_parameter_by_ref(parent, param);
1271             break;
1272         }
1273     }
1274 }
1275 
apply_param_match(icalproperty * prop,union match_criteria_t * match)1276 static int apply_param_match(icalproperty *prop, union match_criteria_t *match)
1277 {
1278     icalparameter_kind kind;
1279     icalparameter *param;
1280     int ret = 1;
1281 
1282     /* XXX  Need to handle X- parameters */
1283 
1284     kind = icalparameter_string_to_kind(match->prop.param);
1285     param = icalproperty_get_first_parameter(prop, kind);
1286     if (!param) {
1287         /* property doesn't have this parameter */
1288         ret = match->prop.not;
1289     }
1290     else if (match->prop.value) {
1291         const char *param_val = icalparameter_get_value_as_string(param);
1292 
1293         ret = !strcmp(match->prop.value, param_val);
1294         if (match->prop.not) ret = !ret;  /* invert */
1295     }
1296 
1297     return ret;
1298 }
1299 
1300 /* Apply a patch action to a property segment */
apply_patch_property(struct path_segment_t * path_seg,icalcomponent * parent,int * num_changes)1301 static void apply_patch_property(struct path_segment_t *path_seg,
1302                                  icalcomponent *parent, int *num_changes)
1303 {
1304     icalproperty *prop, *nextprop;
1305     icalparameter *param;
1306 
1307     /* Iterate through each property */
1308     for (prop = icalcomponent_get_first_property(parent, path_seg->kind);
1309          prop; prop = nextprop) {
1310         nextprop = icalcomponent_get_next_property(parent, path_seg->kind);
1311 
1312         /* Check prop-match */
1313         int match = 1;
1314         if (path_seg->match.prop.param) {
1315             /* Check param-match */
1316             match = apply_param_match(prop, &path_seg->match);
1317         }
1318         else if (path_seg->match.prop.value) {
1319             /* Check prop-[not-]equal */
1320             const char *prop_val = icalproperty_get_value_as_string(prop);
1321 
1322             match = !strcmp(path_seg->match.prop.value, prop_val);
1323             if (path_seg->match.prop.not) match = !match;  /* invert */
1324         }
1325         if (!match) continue;
1326 
1327         if (path_seg->child) {
1328             /* Recurse into next segment */
1329             apply_patch(path_seg->child, prop, num_changes);
1330         }
1331         else if (path_seg->action == ACTION_DELETE) {
1332             /* Delete existing property */
1333             switch (path_seg->kind) {
1334             case ICAL_RDATE_PROPERTY:
1335             case ICAL_EXDATE_PROPERTY:
1336             case ICAL_FREEBUSY_PROPERTY:
1337             case ICAL_CATEGORIES_PROPERTY:
1338             case ICAL_RESOURCES_PROPERTY:
1339             case ICAL_ACCEPTRESPONSE_PROPERTY:
1340             case ICAL_POLLPROPERTIES_PROPERTY:
1341                 /* Multi-valued property */
1342                 if (path_seg->data) {
1343                     /* Check if entire property value == single value */
1344                     const char *single = (const char *) path_seg->data;
1345                     const char *propval = icalproperty_get_value_as_string(prop);
1346 
1347                     if (strcmp(propval, single)) {
1348                         /* Not an exact match, try to remove single value */
1349                         char *newval = remove_single_value(propval, single);
1350                         if (newval) {
1351                             *num_changes += 1;
1352                             icalproperty_set_value(prop,
1353                                                    icalvalue_new_string(newval));
1354                             free(newval);
1355                         }
1356                         break;
1357                     }
1358                 }
1359 
1360                 /* Fall through and delete entire property */
1361                 GCC_FALLTHROUGH
1362 
1363             default:
1364                 *num_changes += 1;
1365                 icalcomponent_remove_property(parent, prop);
1366                 icalproperty_free(prop);
1367                 break;
1368             }
1369         }
1370         else if (path_seg->action == ACTION_SETPARAM) {
1371             /* Set parameter(s) from those on PATCH-PARAMETER */
1372             icalproperty *pp_prop = (icalproperty *) path_seg->data;
1373 
1374             *num_changes += 1;
1375             for (param = icalproperty_get_first_parameter(pp_prop,
1376                                                           ICAL_ANY_PARAMETER);
1377                  param;
1378                  param = icalproperty_get_next_parameter(pp_prop,
1379                                                          ICAL_ANY_PARAMETER)) {
1380                 icalproperty_set_parameter(prop, icalparameter_clone(param));
1381             }
1382         }
1383     }
1384 }
1385 
create_override(icalcomponent * master,struct icaltime_span * span,void * rock)1386 static void create_override(icalcomponent *master, struct icaltime_span *span,
1387                             void *rock)
1388 {
1389     icalcomponent *new;
1390     icalproperty *prop, *next;
1391     struct icaltimetype dtstart, dtend, now;
1392     const icaltimezone *tz = NULL;
1393     const char *tzid;
1394     int is_date;
1395 
1396     now = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
1397 
1398     new = icalcomponent_clone(master);
1399 
1400     for (prop = icalcomponent_get_first_property(new, ICAL_ANY_PROPERTY);
1401          prop; prop = next) {
1402         next = icalcomponent_get_next_property(new, ICAL_ANY_PROPERTY);
1403 
1404         switch (icalproperty_isa(prop)) {
1405         case ICAL_DTSTART_PROPERTY:
1406             /* Set DTSTART for this recurrence */
1407             dtstart = icalproperty_get_dtstart(prop);
1408             is_date = icaltime_is_date(dtstart);
1409             tz = icaltime_get_timezone(dtstart);
1410 
1411             dtstart = icaltime_from_timet_with_zone(span->start, is_date, tz);
1412             icaltime_set_timezone(&dtstart, tz);
1413             icalproperty_set_dtstart(prop, dtstart);
1414 
1415             /* Add RECURRENCE-ID for this recurrence */
1416             prop = icalproperty_new_recurrenceid(dtstart);
1417             tzid = icaltimezone_get_tzid((icaltimezone *) tz);
1418             if (tzid) {
1419                 icalproperty_add_parameter(prop, icalparameter_new_tzid(tzid));
1420             }
1421             icalcomponent_add_property(new, prop);
1422             break;
1423 
1424         case ICAL_DTEND_PROPERTY:
1425             /* Set DTEND for this recurrence */
1426             dtend = icalproperty_get_dtend(prop);
1427             is_date = icaltime_is_date(dtend);
1428             tz = icaltime_get_timezone(dtend);
1429 
1430             dtend = icaltime_from_timet_with_zone(span->end, is_date, tz);
1431             icaltime_set_timezone(&dtend, tz);
1432             icalproperty_set_dtend(prop, dtend);
1433             break;
1434 
1435         case ICAL_RRULE_PROPERTY:
1436         case ICAL_RDATE_PROPERTY:
1437         case ICAL_EXDATE_PROPERTY:
1438             /* Remove recurrence properties */
1439             icalcomponent_remove_property(new, prop);
1440             icalproperty_free(prop);
1441             break;
1442 
1443         case ICAL_DTSTAMP_PROPERTY:
1444             /* Update DTSTAMP */
1445             icalproperty_set_dtstamp(prop, now);
1446             break;
1447 
1448         case ICAL_CREATED_PROPERTY:
1449             /* Update CREATED */
1450             icalproperty_set_created(prop, now);
1451             break;
1452 
1453         case ICAL_LASTMODIFIED_PROPERTY:
1454             /* Update LASTMODIFIED */
1455             icalproperty_set_lastmodified(prop, now);
1456             break;
1457 
1458         default:
1459             break;
1460         }
1461     }
1462 
1463     *((icalcomponent **) rock) = new;
1464 }
1465 
1466 /* Apply property updates */
apply_property_updates(struct patch_data_t * patch,icalcomponent * parent,int * num_changes)1467 static void apply_property_updates(struct patch_data_t *patch,
1468                                    icalcomponent *parent, int *num_changes)
1469 {
1470     icalproperty *prop = NULL, *nextprop, *newprop;
1471 
1472     for (newprop = icalcomponent_get_first_property(patch->patch,
1473                                                     ICAL_ANY_PROPERTY);
1474          newprop;
1475          newprop = icalcomponent_get_next_property(patch->patch,
1476                                                    ICAL_ANY_PROPERTY)) {
1477         icalproperty_kind kind = icalproperty_isa(newprop);
1478         icalparameter_patchaction action = ICAL_PATCHACTION_BYNAME;
1479         icalparameter *actionp;
1480         union match_criteria_t byparam;
1481 
1482         memset(&byparam, 0, sizeof(union match_criteria_t));
1483         newprop = icalproperty_clone(newprop);
1484 
1485         actionp = icalproperty_get_first_parameter(newprop,
1486                                                    ICAL_PATCHACTION_PARAMETER);
1487         if (actionp) {
1488             action = icalparameter_get_patchaction(actionp);
1489             if (action == ICAL_PATCHACTION_X) {
1490                 /* libical treats DQUOTEd BYPARAM as X value */
1491                 const char *byparam_prefix = "BYPARAM@";
1492                 const char *x_val = icalparameter_get_xvalue(actionp);
1493                 if (!strncmp(x_val, byparam_prefix, strlen(byparam_prefix))) {
1494                     /* Parse param-match */
1495                     const char *p = x_val + strlen(byparam_prefix);
1496                     size_t namelen = strcspn(p, "!=");
1497                     byparam.prop.param = xstrndup(p, namelen);
1498                     p += namelen;
1499 
1500                     if (*p) {
1501                         if (*p++ == '!') byparam.prop.not = 1;
1502                         byparam.prop.value = xstrdup(p);
1503                     }
1504                     action = ICAL_PATCHACTION_BYPARAM;
1505                 }
1506             }
1507 
1508             icalproperty_remove_parameter_by_ref(newprop, actionp);
1509             icalparameter_free(actionp);
1510         }
1511 
1512         if (action != ICAL_PATCHACTION_CREATE) {
1513             /* Delete properties matching those being updated */
1514             const char *value = icalproperty_get_value_as_string(newprop);
1515 
1516             for (prop = icalcomponent_get_first_property(parent, kind);
1517                  prop; prop = nextprop) {
1518                 int match = 1;
1519 
1520                 nextprop = icalcomponent_get_next_property(parent, kind);
1521 
1522                 if (action == ICAL_PATCHACTION_BYVALUE) {
1523                     match = !strcmp(value,
1524                                     icalproperty_get_value_as_string(prop));
1525                 }
1526                 else if (action == ICAL_PATCHACTION_BYPARAM) {
1527                     /* Check param-match */
1528                     match = apply_param_match(prop, &byparam);
1529                     free(byparam.prop.param);
1530                     free(byparam.prop.value);
1531                 }
1532                 if (!match) continue;
1533 
1534                 icalcomponent_remove_property(parent, prop);
1535                 icalproperty_free(prop);
1536             }
1537         }
1538 
1539         *num_changes += 1;
1540         icalcomponent_add_property(parent, newprop);
1541     }
1542 }
1543 
1544 /* Apply property updates */
apply_component_updates(struct patch_data_t * patch,icalcomponent * parent,int * num_changes)1545 static void apply_component_updates(struct patch_data_t *patch,
1546                                     icalcomponent *parent, int *num_changes)
1547 {
1548     icalcomponent *comp, *nextcomp, *newcomp;
1549 
1550     for (newcomp = icalcomponent_get_first_component(patch->patch,
1551                                                      ICAL_ANY_COMPONENT);
1552          newcomp;
1553          newcomp = icalcomponent_get_next_component(patch->patch,
1554                                                     ICAL_ANY_COMPONENT)){
1555         icalcomponent_kind kind = icalcomponent_isa(newcomp);
1556         const char *uid = icalcomponent_get_uid(newcomp);
1557         icaltimetype rid = icalcomponent_get_recurrenceid(newcomp);
1558 
1559         newcomp = icalcomponent_clone(newcomp);
1560 
1561         /* Delete components matching those being updated */
1562         for (comp = icalcomponent_get_first_component(parent, kind);
1563              uid && comp; comp = nextcomp) {
1564             const char *thisuid = icalcomponent_get_uid(comp);
1565 
1566             nextcomp = icalcomponent_get_next_component(parent, kind);
1567 
1568             if (thisuid &&  /* VALARMs make not have a UID */
1569                 (strcmp(uid, thisuid) ||
1570                  icaltime_compare(rid, icalcomponent_get_recurrenceid(comp)))) {
1571                 /* skip */
1572                 continue;
1573             }
1574 
1575             icalcomponent_remove_component(parent, comp);
1576             icalcomponent_free(comp);
1577         }
1578 
1579         *num_changes += 1;
1580         icalcomponent_add_component(parent, newcomp);
1581     }
1582 }
1583 
1584 /* Apply a patch action to a component segment */
apply_patch_component(struct path_segment_t * path_seg,icalcomponent * parent,int * num_changes)1585 static void apply_patch_component(struct path_segment_t *path_seg,
1586                                  icalcomponent *parent, int *num_changes)
1587 {
1588     icalcomponent *comp, *nextcomp, *master = NULL;
1589 
1590     /* Iterate through each component */
1591     if (path_seg->kind == ICAL_VCALENDAR_COMPONENT)
1592         comp = parent;
1593     else
1594         comp = icalcomponent_get_first_component(parent, path_seg->kind);
1595 
1596     for (; comp; comp = nextcomp) {
1597         nextcomp = icalcomponent_get_next_component(parent, path_seg->kind);
1598 
1599         /* Check comp-match */
1600         if (path_seg->match.comp.uid &&
1601             strcmpnull(path_seg->match.comp.uid, icalcomponent_get_uid(comp))) {
1602             continue;  /* UID doesn't match */
1603         }
1604 
1605         if (icaltime_is_valid_time(path_seg->match.comp.rid)) {
1606             icaltimetype recurid =
1607                 icalcomponent_get_recurrenceid_with_zone(comp);
1608 
1609             if (icaltime_is_null_time(recurid)) master = comp;
1610             if (icaltime_compare(recurid, path_seg->match.comp.rid)) {
1611                 if (!nextcomp && master) {
1612                     /* Possibly add an override recurrence.
1613                        Set start and end to coincide with recurrence */
1614                     icalcomponent *override = NULL;
1615                     struct icaltimetype start = path_seg->match.comp.rid;
1616                     struct icaltimetype end =
1617                         icaltime_add(start, icalcomponent_get_duration(master));
1618                     icalcomponent_foreach_recurrence(master, start, end,
1619                                                      &create_override,
1620                                                      &override);
1621                     if (!override) break;  /* Can't override - done */
1622 
1623                     /* Act on new overridden component */
1624                     icalcomponent_add_component(parent, override);
1625                     comp = override;
1626                 }
1627                 else continue;  /* RECURRENCE-ID doesn't match */
1628             }
1629             else {
1630                 /* RECURRENCE-ID matches - done after processing this comp */
1631                 nextcomp = NULL;
1632             }
1633         }
1634 
1635         if (path_seg->child) {
1636             /* Recurse into next segment */
1637             apply_patch(path_seg->child, comp, num_changes);
1638         }
1639         else if (path_seg->action == ACTION_DELETE) {
1640             /* Delete existing component */
1641             *num_changes += 1;
1642             icalcomponent_remove_component(parent, comp);
1643             icalcomponent_free(comp);
1644         }
1645         else if (path_seg->action == ACTION_UPDATE) {
1646             /* Patch existing component */
1647             struct patch_data_t *patch = (struct patch_data_t *) path_seg->data;
1648             struct path_segment_t *path_seg2;
1649 
1650             /* Process all PATCH-DELETEs first */
1651             for (path_seg2 = patch->delete;
1652                  path_seg2; path_seg2 = path_seg2->sibling) {
1653                 apply_patch(path_seg2, comp, num_changes);
1654             }
1655 
1656             /* Process all PATCH-SETPARAMETERs second */
1657             for (path_seg2 = patch->setparam;
1658                  path_seg2; path_seg2 = path_seg2->sibling) {
1659                 apply_patch(path_seg2, comp, num_changes);
1660             }
1661 
1662             /* Process all components updates third */
1663             apply_component_updates(patch, comp, num_changes);
1664 
1665             /* Process all property updates last */
1666             apply_property_updates(patch, comp, num_changes);
1667         }
1668     }
1669 }
1670 
1671 /* Apply a patch action to a target segment */
apply_patch(struct path_segment_t * path_seg,void * parent,int * num_changes)1672 static void apply_patch(struct path_segment_t *path_seg,
1673                         void *parent, int *num_changes)
1674 {
1675     switch (path_seg->type) {
1676     case SEGMENT_COMP:
1677         apply_patch_component(path_seg, parent, num_changes);
1678         break;
1679 
1680     case SEGMENT_PROP:
1681         apply_patch_property(path_seg, parent, num_changes);
1682         break;
1683 
1684     case SEGMENT_PARAM:
1685         apply_patch_parameter(path_seg, parent, num_changes);
1686         break;
1687     }
1688 }
1689 
path_segment_free(struct path_segment_t * path_seg)1690 static void path_segment_free(struct path_segment_t *path_seg)
1691 {
1692     struct path_segment_t *next;
1693 
1694     for (; path_seg; path_seg = next) {
1695         next = path_seg->child;
1696 
1697         switch (path_seg->type) {
1698         case SEGMENT_COMP:
1699             free(path_seg->match.comp.uid);
1700             break;
1701 
1702         case SEGMENT_PROP:
1703             free(path_seg->match.prop.param);
1704             free(path_seg->match.prop.value);
1705             break;
1706 
1707         case SEGMENT_PARAM:
1708             break;
1709         }
1710 
1711         free(path_seg);
1712     }
1713 }
1714 
icalcomponent_apply_vpatch(icalcomponent * ical,icalcomponent * vpatch,int * num_changes,const char ** errstr)1715 EXPORTED int icalcomponent_apply_vpatch(icalcomponent *ical,
1716                                         icalcomponent *vpatch,
1717                                         int *num_changes, const char **errstr)
1718 {
1719     icalcomponent *patch;
1720     icalproperty *prop, *nextprop;
1721     int r, junkcount;
1722     const char *junkerr;
1723 
1724     if (!num_changes) num_changes = &junkcount;
1725     if (!errstr) errstr = &junkerr;
1726 
1727     /* Process each patch sub-component */
1728     for (patch = icalcomponent_get_first_component(vpatch, ICAL_ANY_COMPONENT);
1729          patch;
1730          patch = icalcomponent_get_next_component(vpatch, ICAL_ANY_COMPONENT)) {
1731         struct path_segment_t *target = NULL;
1732         struct patch_data_t patch_data = { patch, NULL, NULL };
1733         r = 0;
1734 
1735         if (icalcomponent_isa(patch) != ICAL_XPATCH_COMPONENT) {
1736             /* Unknown patch action */
1737             *errstr = "Unsupported patch action";
1738             r = -1;
1739             goto done;
1740         }
1741 
1742         /* This function is destructive of PATCH components, make a clone */
1743         patch = icalcomponent_clone(patch);
1744 
1745         prop = icalcomponent_get_first_property(patch,
1746                                                 ICAL_PATCHTARGET_PROPERTY);
1747         if (!prop) {
1748             *errstr = "Missing TARGET";
1749             r = -1;
1750             goto done;
1751         }
1752 
1753         /* Parse PATCH-TARGET */
1754         char *path = xstrdup(icalproperty_get_patchtarget(prop));
1755 
1756         icalcomponent_remove_property(patch, prop);
1757         icalproperty_free(prop);
1758 
1759         r = parse_target_path(path, &target, ACTION_UPDATE, &patch_data, errstr);
1760         free(path);
1761 
1762         if (r) goto done;
1763         else if (!target || target->type != SEGMENT_COMP ||
1764                  target->kind != ICAL_VCALENDAR_COMPONENT ||
1765                  target->match.comp.uid) {
1766             *errstr = "Initial segment of PATCH-TARGET"
1767                 " MUST be an unmatched VCALENDAR";
1768             r = -1;
1769             goto done;
1770         }
1771 
1772         /* Parse and remove all PATCH-DELETEs and PATCH-PARAMETERs */
1773         for (prop = icalcomponent_get_first_property(patch, ICAL_ANY_PROPERTY);
1774              prop; prop = nextprop) {
1775 
1776             icalproperty_kind kind = icalproperty_isa(prop);
1777             struct path_segment_t *ppath = NULL;
1778 
1779             nextprop = icalcomponent_get_next_property(patch, ICAL_ANY_PROPERTY);
1780 
1781             if (kind == ICAL_PATCHDELETE_PROPERTY) {
1782                 path = xstrdup(icalproperty_get_patchdelete(prop));
1783 
1784                 icalcomponent_remove_property(patch, prop);
1785                 icalproperty_free(prop);
1786 
1787                 r = parse_target_path(path, &ppath, ACTION_DELETE, NULL, errstr);
1788                 free(path);
1789 
1790                 if (r) goto done;
1791                 else if (!ppath ||
1792                          (ppath->type == SEGMENT_COMP &&
1793                           ppath->kind == ICAL_VCALENDAR_COMPONENT)) {
1794                     *errstr = "Initial segment of PATCH-DELETE"
1795                         " MUST NOT be VCALENDAR";
1796                     r = -1;
1797                     goto done;
1798                 }
1799                 else {
1800                     /* Add this delete path to our list */
1801                     ppath->sibling = patch_data.delete;
1802                     patch_data.delete = ppath;
1803                 }
1804             }
1805             else if (kind == ICAL_PATCHPARAMETER_PROPERTY) {
1806                 path = xstrdup(icalproperty_get_patchparameter(prop));
1807 
1808                 icalcomponent_remove_property(patch, prop);
1809 
1810                 r = parse_target_path(path, &ppath,
1811                                       ACTION_SETPARAM, prop, errstr);
1812                 free(path);
1813 
1814                 if (r) goto done;
1815                 else if (!ppath || ppath->type != SEGMENT_PROP) {
1816                     *errstr = "Initial segment of PATCH-PARAMETER"
1817                         " MUST be a property";
1818                     r = -1;
1819                     goto done;
1820                 }
1821                 else {
1822                     /* Add this setparam path to our list */
1823                     ppath->sibling = patch_data.setparam;
1824                     patch_data.setparam = ppath;
1825                 }
1826             }
1827         }
1828 
1829         /* Apply this patch to the target component */
1830         apply_patch(target, ical, num_changes);
1831 
1832       done:
1833         if (patch) icalcomponent_free(patch);
1834         if (target) {
1835             struct path_segment_t *next;
1836 
1837             /* Cleanup target paths */
1838             path_segment_free(target);
1839             for (target = patch_data.delete; target; target = next) {
1840                 next = target->sibling;
1841                 if (target->data) free(target->data);
1842                 path_segment_free(target);
1843             }
1844             for (target = patch_data.setparam; target; target = next) {
1845                 next = target->sibling;
1846                 if (target->data) icalproperty_free(target->data);
1847                 path_segment_free(target);
1848             }
1849         }
1850 
1851         if (r) return r;
1852     }
1853 
1854     return 0;
1855 }
1856 
1857 
1858 #ifndef HAVE_RFC7986_COLOR
1859 
1860 /* Replacement for missing function in 3.0.0 <= libical < 3.0.5 */
1861 
icalproperty_new_color(const char * v)1862 EXPORTED icalproperty *icalproperty_new_color(const char *v)
1863 {
1864     icalproperty *prop = icalproperty_new_x(v);
1865     icalproperty_set_x_name(prop, "COLOR");
1866     return prop;
1867 }
1868 
1869 #endif /* HAVE_RFC7986_COLOR */
1870 
1871 #endif /* HAVE_ICAL */
1872