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