1 /*======================================================================
2  FILE: icalcomponent.c
3  CREATOR: eric 28 April 1999
4 
5  (C) COPYRIGHT 2000, Eric Busboom <eric@civicknowledge.com>
6 
7  This library is free software; you can redistribute it and/or modify
8  it under the terms of either:
9 
10     The LGPL as published by the Free Software Foundation, version
11     2.1, available at: https://www.gnu.org/licenses/lgpl-2.1.html
12 
13  Or:
14 
15     The Mozilla Public License Version 2.0. You may obtain a copy of
16     the License at https://www.mozilla.org/MPL/
17 ======================================================================*/
18 
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include "icalcomponent.h"
24 #include "icalerror.h"
25 #include "icalmemory.h"
26 #include "icalparser.h"
27 #include "icalproperty_p.h"
28 #include "icalrestriction.h"
29 #include "icaltimezone.h"
30 
31 #include <assert.h>
32 #include <stdlib.h>
33 #include <limits.h>
34 
35 struct icalcomponent_impl
36 {
37     char id[5];
38     icalcomponent_kind kind;
39     char *x_name;
40     pvl_list properties;
41     pvl_elem property_iterator;
42     pvl_list components;
43     pvl_elem component_iterator;
44     struct icalcomponent_impl *parent;
45 
46         /** An array of icaltimezone structs. We use this so we can do fast
47            lookup of timezones using binary searches. timezones_sorted is
48            set to 0 whenever we add a timezone, so we remember to sort the
49            array before doing a binary search. */
50     icalarray *timezones;
51     int timezones_sorted;
52 };
53 
54 static void icalcomponent_add_children(icalcomponent *impl, va_list args);
55 static icalcomponent *icalcomponent_new_impl(icalcomponent_kind kind);
56 
57 static void icalcomponent_merge_vtimezone(icalcomponent *comp,
58                                           icalcomponent *vtimezone, icalarray *tzids_to_rename);
59 static void icalcomponent_handle_conflicting_vtimezones(icalcomponent *comp,
60                                                         icalcomponent *vtimezone,
61                                                         icalproperty *tzid_prop,
62                                                         const char *tzid,
63                                                         icalarray *tzids_to_rename);
64 static size_t icalcomponent_get_tzid_prefix_len(const char *tzid);
65 static void icalcomponent_rename_tzids(icalcomponent *comp, icalarray *rename_table);
66 static void icalcomponent_rename_tzids_callback(icalparameter *param, void *data);
67 static int icalcomponent_compare_vtimezones(icalcomponent *vtimezone1, icalcomponent *vtimezone2);
68 static int icalcomponent_compare_timezone_fn(const void *elem1, const void *elem2);
69 
icalcomponent_add_children(icalcomponent * impl,va_list args)70 void icalcomponent_add_children(icalcomponent *impl, va_list args)
71 {
72     void *vp;
73 
74     while ((vp = va_arg(args, void *)) != 0)
75     {
76         assert(icalcomponent_isa_component(vp) != 0 || icalproperty_isa_property(vp) != 0);
77 
78         if (icalcomponent_isa_component(vp) != 0) {
79             icalcomponent_add_component(impl, (icalcomponent *) vp);
80 
81         } else if (icalproperty_isa_property(vp) != 0) {
82             icalcomponent_add_property(impl, (icalproperty *) vp);
83         }
84     }
85 }
86 
icalcomponent_new_impl(icalcomponent_kind kind)87 static icalcomponent *icalcomponent_new_impl(icalcomponent_kind kind)
88 {
89     icalcomponent *comp;
90 
91     if (!icalcomponent_kind_is_valid(kind))
92         return NULL;
93 
94     if ((comp = (icalcomponent *) malloc(sizeof(icalcomponent))) == 0) {
95         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
96         return 0;
97     }
98 
99     memset(comp, 0, sizeof(icalcomponent));
100 
101     strcpy(comp->id, "comp");
102 
103     comp->kind = kind;
104     comp->properties = pvl_newlist();
105     comp->components = pvl_newlist();
106     comp->timezones_sorted = 1;
107 
108     return comp;
109 }
110 
111 /** @brief Constructor
112  */
icalcomponent_new(icalcomponent_kind kind)113 icalcomponent *icalcomponent_new(icalcomponent_kind kind)
114 {
115     return icalcomponent_new_impl(kind);
116 }
117 
118 /** @brief Constructor
119  */
120 #pragma clang diagnostic push
121 #pragma clang diagnostic ignored "-Wvarargs"
icalcomponent_vanew(icalcomponent_kind kind,...)122 icalcomponent *icalcomponent_vanew(icalcomponent_kind kind, ...)
123 {
124     va_list args;
125 
126     icalcomponent *impl = icalcomponent_new_impl(kind);
127 
128     if (impl == 0) {
129         return 0;
130     }
131 
132     va_start(args, kind);
133     icalcomponent_add_children(impl, args);
134     va_end(args);
135 
136     return impl;
137 }
138 #pragma clang diagnostic pop
139 
140 /** @brief Constructor
141  */
icalcomponent_new_from_string(const char * str)142 icalcomponent *icalcomponent_new_from_string(const char *str)
143 {
144     return icalparser_parse_string(str);
145 }
146 
147 /** @brief Constructor
148  */
icalcomponent_new_clone(icalcomponent * old)149 icalcomponent *icalcomponent_new_clone(icalcomponent *old)
150 {
151     icalcomponent *new;
152     icalproperty *p;
153     icalcomponent *c;
154     pvl_elem itr;
155 
156     icalerror_check_arg_rz((old != 0), "component");
157 
158     new = icalcomponent_new_impl(old->kind);
159 
160     if (new == 0) {
161         return 0;
162     }
163 
164     for (itr = pvl_head(old->properties); itr != 0; itr = pvl_next(itr)) {
165         p = (icalproperty *) pvl_data(itr);
166         icalcomponent_add_property(new, icalproperty_new_clone(p));
167     }
168 
169     for (itr = pvl_head(old->components); itr != 0; itr = pvl_next(itr)) {
170         c = (icalcomponent *) pvl_data(itr);
171         icalcomponent_add_component(new, icalcomponent_new_clone(c));
172     }
173 
174     return new;
175 }
176 
177 /** @brief Constructor
178  */
icalcomponent_new_x(const char * x_name)179 icalcomponent *icalcomponent_new_x(const char *x_name)
180 {
181     icalcomponent *comp = icalcomponent_new_impl(ICAL_X_COMPONENT);
182 
183     if (!comp) {
184         return 0;
185     }
186     comp->x_name = icalmemory_strdup(x_name);
187     return comp;
188 }
189 
190 /*** @brief Destructor
191  */
icalcomponent_free(icalcomponent * c)192 void icalcomponent_free(icalcomponent *c)
193 {
194     icalproperty *prop;
195     icalcomponent *comp;
196 
197     icalerror_check_arg_rv((c != 0), "component");
198 
199     if (c != 0) {
200         if (c->parent != 0) {
201             return;
202         }
203 
204         if (c->properties != 0) {
205             while ((prop = pvl_pop(c->properties)) != 0) {
206                 icalproperty_set_parent(prop, 0);
207                 icalproperty_free(prop);
208             }
209             pvl_free(c->properties);
210         }
211 
212         while ((comp = pvl_data(pvl_head(c->components))) != 0) {
213             icalcomponent_remove_component(c, comp);
214             icalcomponent_free(comp);
215         }
216 
217         pvl_free(c->components);
218 
219         if (c->x_name != 0) {
220             free(c->x_name);
221         }
222 
223         if (c->timezones) {
224             icaltimezone_array_free(c->timezones);
225             c->timezones = 0;
226         }
227 
228         c->kind = ICAL_NO_COMPONENT;
229         c->properties = 0;
230         c->property_iterator = 0;
231         c->components = 0;
232         c->component_iterator = 0;
233         c->x_name = 0;
234         c->id[0] = 'X';
235         c->timezones = NULL;
236 
237         free(c);
238     }
239 }
240 
icalcomponent_as_ical_string(icalcomponent * impl)241 char *icalcomponent_as_ical_string(icalcomponent *impl)
242 {
243     char *buf;
244 
245     buf = icalcomponent_as_ical_string_r(impl);
246     icalmemory_add_tmp_buffer(buf);
247     return buf;
248 }
249 
icalcomponent_as_ical_string_r(icalcomponent * impl)250 char *icalcomponent_as_ical_string_r(icalcomponent *impl)
251 {
252     char *buf;
253     char *tmp_buf;
254     size_t buf_size = 1024;
255     char *buf_ptr = 0;
256     pvl_elem itr;
257 
258     /* RFC5545 explicitly says that the newline is *ALWAYS* a \r\n (CRLF)!!!! */
259     const char newline[] = "\r\n";
260 
261     icalcomponent *c;
262     icalproperty *p;
263     icalcomponent_kind kind = icalcomponent_isa(impl);
264 
265     const char *kind_string;
266 
267     icalerror_check_arg_rz((impl != 0), "component");
268     icalerror_check_arg_rz((kind != ICAL_NO_COMPONENT), "component kind is ICAL_NO_COMPONENT");
269 
270     if (kind != ICAL_X_COMPONENT) {
271         kind_string = icalcomponent_kind_to_string(kind);
272     } else {
273         kind_string = impl->x_name;
274     }
275 
276     icalerror_check_arg_rz((kind_string != 0), "Unknown kind of component");
277 
278     buf = icalmemory_new_buffer(buf_size);
279     buf_ptr = buf;
280 
281     icalmemory_append_string(&buf, &buf_ptr, &buf_size, "BEGIN:");
282     icalmemory_append_string(&buf, &buf_ptr, &buf_size, kind_string);
283     icalmemory_append_string(&buf, &buf_ptr, &buf_size, newline);
284 
285     for (itr = pvl_head(impl->properties); itr != 0; itr = pvl_next(itr)) {
286         p = (icalproperty *) pvl_data(itr);
287 
288         icalerror_assert((p != 0), "Got a null property");
289         tmp_buf = icalproperty_as_ical_string_r(p);
290 
291         icalmemory_append_string(&buf, &buf_ptr, &buf_size, tmp_buf);
292         free(tmp_buf);
293     }
294 
295     for (itr = pvl_head(impl->components); itr != 0; itr = pvl_next(itr)) {
296         c = (icalcomponent *) pvl_data(itr);
297 
298         tmp_buf = icalcomponent_as_ical_string_r(c);
299 
300         icalmemory_append_string(&buf, &buf_ptr, &buf_size, tmp_buf);
301         free(tmp_buf);
302     }
303 
304     icalmemory_append_string(&buf, &buf_ptr, &buf_size, "END:");
305     icalmemory_append_string(&buf, &buf_ptr, &buf_size, icalcomponent_kind_to_string(kind));
306     icalmemory_append_string(&buf, &buf_ptr, &buf_size, newline);
307 
308     return buf;
309 }
310 
icalcomponent_is_valid(icalcomponent * component)311 int icalcomponent_is_valid(icalcomponent *component)
312 {
313     if ((strcmp(component->id, "comp") == 0) && component->kind != ICAL_NO_COMPONENT) {
314         return 1;
315     } else {
316         return 0;
317     }
318 }
319 
icalcomponent_isa(const icalcomponent * component)320 icalcomponent_kind icalcomponent_isa(const icalcomponent *component)
321 {
322     icalerror_check_arg_rx((component != 0), "component", ICAL_NO_COMPONENT);
323 
324     return component->kind;
325 }
326 
icalcomponent_isa_component(void * component)327 int icalcomponent_isa_component(void *component)
328 {
329     icalcomponent *impl = component;
330 
331     icalerror_check_arg_rz((component != 0), "component");
332 
333     if (strcmp(impl->id, "comp") == 0) {
334         return 1;
335     } else {
336         return 0;
337     }
338 }
339 
icalcomponent_add_property(icalcomponent * component,icalproperty * property)340 void icalcomponent_add_property(icalcomponent *component, icalproperty *property)
341 {
342     icalerror_check_arg_rv((component != 0), "component");
343     icalerror_check_arg_rv((property != 0), "property");
344 
345     icalerror_assert((!icalproperty_get_parent(property)),
346                      "The property has already been added to a component. "
347                      "Remove the property with icalcomponent_remove_property "
348                      "before calling icalcomponent_add_property");
349 
350     icalproperty_set_parent(property, component);
351 
352     pvl_push(component->properties, property);
353 }
354 
icalcomponent_remove_property(icalcomponent * component,icalproperty * property)355 void icalcomponent_remove_property(icalcomponent *component, icalproperty *property)
356 {
357     pvl_elem itr, next_itr;
358 
359     icalerror_check_arg_rv((component != 0), "component");
360     icalerror_check_arg_rv((property != 0), "property");
361 
362 #if defined(ICAL_REMOVE_NONMEMBER_COMPONENT_IS_ERROR)
363     icalerror_assert((icalproperty_get_parent(property)),
364                      "The property is not a member of a component");
365 #else
366     if (icalproperty_get_parent(property) == 0) {
367         return;
368     }
369 #endif
370 
371     for (itr = pvl_head(component->properties); itr != 0; itr = next_itr) {
372         next_itr = pvl_next(itr);
373 
374         if (pvl_data(itr) == (void *)property) {
375 
376             if (component->property_iterator == itr) {
377                 component->property_iterator = pvl_next(itr);
378             }
379 
380             (void)pvl_remove(component->properties, itr);
381             icalproperty_set_parent(property, 0);
382         }
383     }
384 }
385 
icalcomponent_count_properties(icalcomponent * component,icalproperty_kind kind)386 int icalcomponent_count_properties(icalcomponent *component, icalproperty_kind kind)
387 {
388     int count = 0;
389     pvl_elem itr;
390 
391     icalerror_check_arg_rz((component != 0), "component");
392 
393     for (itr = pvl_head(component->properties); itr != 0; itr = pvl_next(itr)) {
394         if (kind == icalproperty_isa((icalproperty *) pvl_data(itr)) || kind == ICAL_ANY_PROPERTY) {
395             count++;
396         }
397     }
398 
399     return count;
400 }
401 
icalcomponent_get_current_property(icalcomponent * component)402 icalproperty *icalcomponent_get_current_property(icalcomponent *component)
403 {
404     icalerror_check_arg_rz((component != 0), "component");
405 
406     if (component->property_iterator == 0) {
407         return 0;
408     }
409 
410     return (icalproperty *) pvl_data(component->property_iterator);
411 }
412 
icalcomponent_get_first_property(icalcomponent * c,icalproperty_kind kind)413 icalproperty *icalcomponent_get_first_property(icalcomponent *c, icalproperty_kind kind)
414 {
415     icalerror_check_arg_rz((c != 0), "component");
416 
417     for (c->property_iterator = pvl_head(c->properties);
418          c->property_iterator != 0; c->property_iterator = pvl_next(c->property_iterator)) {
419 
420         icalproperty *p = (icalproperty *) pvl_data(c->property_iterator);
421 
422         if (icalproperty_isa(p) == kind || kind == ICAL_ANY_PROPERTY) {
423 
424             return p;
425         }
426     }
427     return 0;
428 }
429 
icalcomponent_get_next_property(icalcomponent * c,icalproperty_kind kind)430 icalproperty *icalcomponent_get_next_property(icalcomponent *c, icalproperty_kind kind)
431 {
432     icalerror_check_arg_rz((c != 0), "component");
433 
434     if (c->property_iterator == 0) {
435         return 0;
436     }
437 
438     for (c->property_iterator = pvl_next(c->property_iterator);
439          c->property_iterator != 0; c->property_iterator = pvl_next(c->property_iterator)) {
440 
441         icalproperty *p = (icalproperty *) pvl_data(c->property_iterator);
442 
443         if (icalproperty_isa(p) == kind || kind == ICAL_ANY_PROPERTY) {
444 
445             return p;
446         }
447     }
448 
449     return 0;
450 }
451 
452 icalproperty **icalcomponent_get_properties(icalcomponent *component, icalproperty_kind kind);
453 
icalcomponent_add_component(icalcomponent * parent,icalcomponent * child)454 void icalcomponent_add_component(icalcomponent *parent, icalcomponent *child)
455 {
456     icalerror_check_arg_rv((parent != 0), "parent");
457     icalerror_check_arg_rv((child != 0), "child");
458 
459     if (child->parent != 0) {
460         icalerror_set_errno(ICAL_USAGE_ERROR);
461     }
462 
463     child->parent = parent;
464 
465     /* Fix for Mozilla - bug 327602 */
466     if (child->kind != ICAL_VTIMEZONE_COMPONENT) {
467         pvl_push(parent->components, child);
468     } else {
469         /* VTIMEZONES should be first in the resulting VCALENDAR. */
470         pvl_unshift(parent->components, child);
471 
472         /* Add the VTIMEZONE to our array. */
473         /* FIXME: Currently we are also creating this array when loading in
474            a builtin VTIMEZONE, when we don't need it. */
475         if (!parent->timezones)
476             parent->timezones = icaltimezone_array_new();
477 
478         icaltimezone_array_append_from_vtimezone(parent->timezones, child);
479 
480         /* Flag that we need to sort it before doing any binary searches. */
481         parent->timezones_sorted = 0;
482     }
483 }
484 
icalcomponent_remove_component(icalcomponent * parent,icalcomponent * child)485 void icalcomponent_remove_component(icalcomponent *parent, icalcomponent *child)
486 {
487     pvl_elem itr, next_itr;
488 
489     icalerror_check_arg_rv((parent != 0), "parent");
490     icalerror_check_arg_rv((child != 0), "child");
491 
492     /* If the component is a VTIMEZONE, remove it from our array as well. */
493     if (child->kind == ICAL_VTIMEZONE_COMPONENT) {
494         icaltimezone *zone;
495         size_t i, num_elements;
496 
497         num_elements = parent->timezones ? parent->timezones->num_elements : 0;
498         for (i = 0; i < num_elements; i++) {
499             zone = icalarray_element_at(parent->timezones, i);
500             if (icaltimezone_get_component(zone) == child) {
501                 icaltimezone_free(zone, 0);
502                 icalarray_remove_element_at(parent->timezones, i);
503                 break;
504             }
505         }
506     }
507 
508     for (itr = pvl_head(parent->components); itr != 0; itr = next_itr) {
509         next_itr = pvl_next(itr);
510 
511         if (pvl_data(itr) == (void *)child) {
512 
513             if (parent->component_iterator == itr) {
514                 /* Don't let the current iterator become invalid */
515 
516                 /* HACK. The semantics for this are troubling. */
517                 parent->component_iterator = pvl_next(parent->component_iterator);
518             }
519             (void)pvl_remove(parent->components, itr);
520             child->parent = 0;
521             break;
522         }
523     }
524 }
525 
icalcomponent_count_components(icalcomponent * component,icalcomponent_kind kind)526 int icalcomponent_count_components(icalcomponent *component, icalcomponent_kind kind)
527 {
528     int count = 0;
529     pvl_elem itr;
530 
531     icalerror_check_arg_rz((component != 0), "component");
532 
533     for (itr = pvl_head(component->components); itr != 0; itr = pvl_next(itr)) {
534         if (kind == icalcomponent_isa((icalcomponent *) pvl_data(itr)) ||
535             kind == ICAL_ANY_COMPONENT) {
536             count++;
537         }
538     }
539 
540     return count;
541 }
542 
icalcomponent_get_current_component(icalcomponent * component)543 icalcomponent *icalcomponent_get_current_component(icalcomponent *component)
544 {
545     icalerror_check_arg_rz((component != 0), "component");
546 
547     if (component->component_iterator == 0) {
548         return 0;
549     }
550 
551     return (icalcomponent *) pvl_data(component->component_iterator);
552 }
553 
icalcomponent_get_first_component(icalcomponent * c,icalcomponent_kind kind)554 icalcomponent *icalcomponent_get_first_component(icalcomponent *c, icalcomponent_kind kind)
555 {
556     icalerror_check_arg_rz((c != 0), "component");
557 
558     for (c->component_iterator = pvl_head(c->components);
559          c->component_iterator != 0; c->component_iterator = pvl_next(c->component_iterator)) {
560 
561         icalcomponent *p = (icalcomponent *) pvl_data(c->component_iterator);
562 
563         if (icalcomponent_isa(p) == kind || kind == ICAL_ANY_COMPONENT) {
564 
565             return p;
566         }
567     }
568 
569     return 0;
570 }
571 
icalcomponent_get_next_component(icalcomponent * c,icalcomponent_kind kind)572 icalcomponent *icalcomponent_get_next_component(icalcomponent *c, icalcomponent_kind kind)
573 {
574     icalerror_check_arg_rz((c != 0), "component");
575 
576     if (c->component_iterator == 0) {
577         return 0;
578     }
579 
580     for (c->component_iterator = pvl_next(c->component_iterator);
581          c->component_iterator != 0; c->component_iterator = pvl_next(c->component_iterator)) {
582 
583         icalcomponent *p = (icalcomponent *) pvl_data(c->component_iterator);
584 
585         if (icalcomponent_isa(p) == kind || kind == ICAL_ANY_COMPONENT) {
586 
587             return p;
588         }
589     }
590 
591     return 0;
592 }
593 
icalcomponent_get_first_real_component(icalcomponent * c)594 icalcomponent *icalcomponent_get_first_real_component(icalcomponent *c)
595 {
596     icalcomponent *comp;
597 
598     for (comp = icalcomponent_get_first_component(c, ICAL_ANY_COMPONENT);
599          comp != 0; comp = icalcomponent_get_next_component(c, ICAL_ANY_COMPONENT)) {
600 
601         icalcomponent_kind kind = icalcomponent_isa(comp);
602 
603         if (kind == ICAL_VEVENT_COMPONENT ||
604             kind == ICAL_VTODO_COMPONENT ||
605             kind == ICAL_VJOURNAL_COMPONENT ||
606             kind == ICAL_VFREEBUSY_COMPONENT ||
607             kind == ICAL_VAVAILABILITY_COMPONENT ||
608             kind == ICAL_VPOLL_COMPONENT ||
609             kind == ICAL_VPATCH_COMPONENT ||
610             kind == ICAL_VQUERY_COMPONENT || kind == ICAL_VAGENDA_COMPONENT) {
611             return comp;
612         }
613     }
614     return 0;
615 }
616 
617 /**     @brief Get the timespan covered by this component, in UTC
618  *
619  *      see icalcomponent_foreach_recurrence() for a better way to
620  *      extract spans from an component.
621  *
622  *      This method can be called on either a VCALENDAR or any real
623  *      component. If the VCALENDAR contains no real component, but
624  *      contains a VTIMEZONE, we return that span instead.
625  *      This might not be a desirable behavior; we keep it for now
626  *      for backward compatibility, but it might be deprecated at a
627  *      future time.
628  *
629  *      FIXME this API needs to be clarified. DTEND is defined as the
630  *      first available time after the end of this event, so the span
631  *      should actually end 1 second before DTEND.
632  */
633 
icalcomponent_get_span(icalcomponent * comp)634 icaltime_span icalcomponent_get_span(icalcomponent *comp)
635 {
636     icalcomponent *inner;
637     icalcomponent_kind kind;
638     icaltime_span span;
639     struct icaltimetype start, end;
640 
641     span.start = 0;
642     span.end = 0;
643     span.is_busy = 1;
644 
645     /* initial Error checking */
646     if (comp == NULL) {
647         return span;
648     }
649 
650     /* FIXME this might go away */
651     kind = icalcomponent_isa(comp);
652     if (kind == ICAL_VCALENDAR_COMPONENT) {
653         inner = icalcomponent_get_first_real_component(comp);
654 
655         /* Maybe there is a VTIMEZONE in there */
656         if (inner == 0) {
657             inner = icalcomponent_get_first_component(comp, ICAL_VTIMEZONE_COMPONENT);
658         }
659 
660     } else {
661         inner = comp;
662     }
663 
664     if (inner == 0) {
665         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
666         /*icalerror_warn("icalcomponent_get_span: no component specified, \
667 or empty VCALENDAR component"); */
668         return span;
669     }
670 
671     kind = icalcomponent_isa(inner);
672 
673     if (!(kind == ICAL_VEVENT_COMPONENT ||
674           kind == ICAL_VJOURNAL_COMPONENT ||
675           kind == ICAL_VTODO_COMPONENT || kind == ICAL_VFREEBUSY_COMPONENT)) {
676         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
677         /*icalerror_warn("icalcomponent_get_span: no component specified, \
678 or empty VCALENDAR component"); */
679         return span;
680     }
681 
682     /* Get to work. starting with DTSTART */
683     start = icalcomponent_get_dtstart(comp);
684     if (icaltime_is_null_time(start)) {
685         return span;
686     }
687     span.start = icaltime_as_timet_with_zone(start, icaltimezone_get_utc_timezone());
688 
689     /* The end time could be specified as either a DTEND or a DURATION */
690     /* icalcomponent_get_dtend takes care of these cases. */
691     end = icalcomponent_get_dtend(comp);
692     if (icaltime_is_null_time(end)) {
693         if (!icaltime_is_date(start)) {
694             /* If dtstart is a DATE-TIME and there is no DTEND nor DURATION
695                it takes no time */
696             span.start = 0;
697             return span;
698         } else {
699             end = start;
700         }
701     }
702 
703     span.end = icaltime_as_timet_with_zone(end, icaltimezone_get_utc_timezone());
704     if (icaltime_is_date(start)) {
705         /* Until the end of the day */
706         span.end += 60 * 60 * 24 - 1;
707     }
708 
709     return span;
710 }
711 
712 /**
713  * Decide if this recurrance is acceptable
714  *
715  * @param comp       A valid icalcomponent.
716  * @param dtstart    The base dtstart value for this component.
717  * @param recurtime  The time to test against.
718  *
719  * @return true if the recurrence value is excluded, false otherwise.
720  *
721  * This function decides if a specific recurrence value is
722  * excluded by EXRULE or EXDATE properties.
723  *
724  * It's not the most efficient code.  You might get better performance
725  * if you assume that recurtime is always increasing for each
726  * call. Then you could:
727  *
728  *   - sort the EXDATE values
729  *   - save the state of each EXRULE iterator for the next call.
730  *
731  * In this case though you don't need to worry how you call this
732  * function.  It will always return the correct result.
733  */
734 
icalproperty_recurrence_is_excluded(icalcomponent * comp,struct icaltimetype * dtstart,struct icaltimetype * recurtime)735 int icalproperty_recurrence_is_excluded(icalcomponent *comp,
736                                         struct icaltimetype *dtstart,
737                                         struct icaltimetype *recurtime)
738 {
739     icalproperty *exdate, *exrule;
740     pvl_elem property_iterator;
741 
742     if (comp == NULL || dtstart == NULL || recurtime == NULL || icaltime_is_null_time(*recurtime)) {
743         /* BAD DATA */
744         return 1;
745     }
746 
747     property_iterator = comp->property_iterator;
748 
749   /** first test against the exdate values **/
750     for (exdate = icalcomponent_get_first_property(comp, ICAL_EXDATE_PROPERTY);
751          exdate != NULL; exdate = icalcomponent_get_next_property(comp, ICAL_EXDATE_PROPERTY)) {
752 
753         struct icaltimetype exdatetime = icalproperty_get_datetime_with_component(exdate, comp);
754 
755         if ((icaltime_is_date(exdatetime) &&
756              icaltime_compare_date_only(*recurtime, exdatetime) == 0) ||
757             (icaltime_compare(*recurtime, exdatetime) == 0)) {
758             /** MATCHED **/
759             comp->property_iterator = property_iterator;
760             return 1;
761         }
762     }
763 
764   /** Now test against the EXRULEs **/
765     for (exrule = icalcomponent_get_first_property(comp, ICAL_EXRULE_PROPERTY);
766          exrule != NULL; exrule = icalcomponent_get_next_property(comp, ICAL_EXRULE_PROPERTY)) {
767 
768         struct icalrecurrencetype recur = icalproperty_get_exrule(exrule);
769         icalrecur_iterator *exrule_itr = icalrecur_iterator_new(recur, *dtstart);
770         struct icaltimetype exrule_time;
771 
772         while (exrule_itr) {
773             int result;
774 
775             exrule_time = icalrecur_iterator_next(exrule_itr);
776 
777             if (icaltime_is_null_time(exrule_time))
778                 break;
779 
780             result = icaltime_compare(*recurtime, exrule_time);
781             if (result == 0) {
782                 icalrecur_iterator_free(exrule_itr);
783                 comp->property_iterator = property_iterator;
784                 return 1;
785                   /** MATCH **/
786             }
787             if (result == 1)
788                 break;
789                   /** exrule_time > recurtime **/
790         }
791 
792         icalrecur_iterator_free(exrule_itr);
793     }
794     comp->property_iterator = property_iterator;
795 
796     return 0;/** no matches **/
797 }
798 
799 /**
800  * @brief Return the busy status based on the TRANSP property.
801  *
802  * @param comp   A valid icalcomponent.
803  *
804  * @return 1 if the event is a busy item, 0 if it is not.
805  */
806 
icalcomponent_is_busy(icalcomponent * comp)807 static int icalcomponent_is_busy(icalcomponent *comp)
808 {
809     icalproperty *transp;
810     enum icalproperty_status status;
811     int ret = 1;
812 
813   /** @todo check access control here, converting busy->free if the
814      permissions do not allow access... */
815 
816     /* Is this a busy time?  Check the TRANSP property */
817     transp = icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY);
818 
819     if (transp) {
820         icalvalue *transp_val = icalproperty_get_value(transp);
821 
822         switch (icalvalue_get_transp(transp_val)) {
823         case ICAL_TRANSP_OPAQUE:
824         case ICAL_TRANSP_OPAQUENOCONFLICT:
825         case ICAL_TRANSP_NONE:
826             ret = 1;
827             break;
828         case ICAL_TRANSP_TRANSPARENT:
829         case ICAL_TRANSP_TRANSPARENTNOCONFLICT:
830             ret = 0;
831             break;
832         default:
833             ret = 0;
834             break;
835         }
836     }
837     status = icalcomponent_get_status(comp);
838     if (ret && status) {
839         switch (status) {
840         case ICAL_STATUS_CANCELLED:
841         case ICAL_STATUS_TENTATIVE:
842             ret = 0;
843             break;
844         default:
845             break;
846         }
847     }
848     return (ret);
849 }
850 
851 /**
852  * @brief cycle through all recurrances of an event
853  *
854  * @param comp           A valid VEVENT component
855  * @param start          Ignore timespans before this
856  * @param end            Ignore timespans after this
857  * @param callback       Function called for each timespan within the range
858  * @param callback_data  Pointer passed back to the callback function
859  *
860  * This function will call the specified callback function for once
861  * for the base value of DTSTART, and foreach recurring date/time
862  * value.
863  *
864  * It will filter out events that are specified as an EXDATE or an EXRULE.
865  *
866  * @todo We do not filter out duplicate RRULES/RDATES
867  * @todo We do not handle RDATEs with explicit periods
868  */
869 
icalcomponent_foreach_recurrence(icalcomponent * comp,struct icaltimetype start,struct icaltimetype end,void (* callback)(icalcomponent * comp,struct icaltime_span * span,void * data),void * callback_data)870 void icalcomponent_foreach_recurrence(icalcomponent *comp,
871                                       struct icaltimetype start,
872                                       struct icaltimetype end,
873                                       void (*callback) (icalcomponent *comp,
874                                                         struct icaltime_span *span,
875                                                         void *data), void *callback_data)
876 {
877     struct icaltimetype dtstart, dtend;
878     icaltime_span recurspan, basespan, limit_span;
879     time_t limit_start, limit_end;
880     time_t dtduration;
881     icalproperty *rrule, *rdate;
882     pvl_elem property_iterator; /* for saving the iterator */
883 
884     if (comp == NULL || callback == NULL)
885         return;
886 
887     dtstart = icalcomponent_get_dtstart(comp);
888 
889     if (icaltime_is_null_time(dtstart) &&
890         icalcomponent_isa(comp) == ICAL_VTODO_COMPONENT) {
891         /* VTODO with no DTSTART - use DUE */
892         dtstart = icalcomponent_get_due(comp);
893     }
894     if (icaltime_is_null_time(dtstart))
895         return;
896 
897     /* The end time could be specified as either a DTEND or a DURATION */
898     /* icalcomponent_get_dtend takes care of these cases. */
899     dtend = icalcomponent_get_dtend(comp);
900     if (icaltime_is_null_time(dtend) && icaltime_is_date(dtstart)) {
901         /* No DTEND or DURATION and DTSTART is DATE - duration is 1 day */
902         struct icaldurationtype dur = icaldurationtype_null_duration();
903 
904         dur.days = 1;
905         dtend = icaltime_add(dtstart, dur);
906     }
907 
908     /* Now set up the base span for this item, corresponding to the
909        base DTSTART and DTEND */
910     basespan = icaltime_span_new(dtstart, dtend, 1);
911 
912     basespan.is_busy = icalcomponent_is_busy(comp);
913 
914     /** Calculate the ceiling and floor values.. **/
915     limit_start = icaltime_as_timet_with_zone(start,
916                                               icaltimezone_get_utc_timezone());
917     if (!icaltime_is_null_time(end)) {
918         limit_end = icaltime_as_timet_with_zone(end,
919                                                 icaltimezone_get_utc_timezone());
920     } else {
921 #if (SIZEOF_TIME_T > 4)
922         limit_end = (time_t) LONG_MAX;
923 #else
924         limit_end = (time_t) INT_MAX;
925 #endif
926     }
927     limit_span.start = limit_start;
928     limit_span.end = limit_end;
929 
930     /* Do the callback for the DTSTART entry, ONLY if there is no RRULE.
931        Otherwise, the initial occurrence will be handled by the RRULE. */
932     rrule = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
933     if ((rrule == NULL) &&
934         !icalproperty_recurrence_is_excluded(comp, &dtstart, &dtstart)) {
935         /** call callback action **/
936         if (icaltime_span_overlaps(&basespan, &limit_span))
937             (*callback) (comp, &basespan, callback_data);
938     }
939 
940     recurspan = basespan;
941     dtduration = basespan.end - basespan.start;
942 
943     /* Now cycle through the rrule entries */
944     for (; rrule != NULL;
945          rrule = icalcomponent_get_next_property(comp, ICAL_RRULE_PROPERTY)) {
946 
947         struct icalrecurrencetype recur = icalproperty_get_rrule(rrule);
948         icalrecur_iterator *rrule_itr = icalrecur_iterator_new(recur, dtstart);
949         struct icaltimetype rrule_time;
950 
951         if (!rrule_itr) continue;
952 
953         if (recur.count == 0) {
954             icaltimetype mystart = start;
955 
956             /* make sure we include any recurrence that ends in timespan */
957             icaltime_adjust(&mystart, 0, 0, 0, -(int)dtduration);
958             icalrecur_iterator_set_start(rrule_itr, mystart);
959         }
960 
961         for (rrule_time = icalrecur_iterator_next(rrule_itr);
962              !icaltime_is_null_time(rrule_time);
963              rrule_time = icalrecur_iterator_next(rrule_itr)) {
964 
965             /* if we have iterated past end time,
966                then no need to check any further */
967             if (icaltime_compare(rrule_time, end) > 0)
968                 break;
969 
970             recurspan.start =
971                 icaltime_as_timet_with_zone(rrule_time,
972                                             rrule_time.zone ? rrule_time.zone :
973                                             icaltimezone_get_utc_timezone());
974             recurspan.end = recurspan.start + dtduration;
975 
976             /** save the iterator ICK! **/
977             property_iterator = comp->property_iterator;
978 
979             if (!icalproperty_recurrence_is_excluded(comp,
980                                                      &dtstart, &rrule_time)) {
981                 /** call callback action **/
982                 if (icaltime_span_overlaps(&recurspan, &limit_span))
983                     (*callback) (comp, &recurspan, callback_data);
984             }
985             comp->property_iterator = property_iterator;
986         }   /* end of iteration over a specific RRULE */
987 
988         icalrecur_iterator_free(rrule_itr);
989     }   /* end of RRULE loop */
990 
991     /** Now process RDATE entries **/
992     for (rdate = icalcomponent_get_first_property(comp, ICAL_RDATE_PROPERTY);
993          rdate != NULL;
994          rdate = icalcomponent_get_next_property(comp, ICAL_RDATE_PROPERTY)) {
995 
996         struct icaldatetimeperiodtype rdate_period =
997             icalproperty_get_rdate(rdate);
998 
999         /** RDATES can specify raw datetimes, periods, or dates.
1000             we only support raw datetimes for now..
1001 
1002             @todo Add support for other types **/
1003 
1004         if (icaltime_is_null_time(rdate_period.time))
1005             continue;
1006 
1007         recurspan.start =
1008             icaltime_as_timet_with_zone(rdate_period.time,
1009                                         rdate_period.time.zone ?
1010                                         rdate_period.time.zone :
1011                                         icaltimezone_get_utc_timezone());
1012         recurspan.end = recurspan.start + dtduration;
1013 
1014         /** save the iterator ICK! **/
1015         property_iterator = comp->property_iterator;
1016 
1017         if (!icalproperty_recurrence_is_excluded(comp,
1018                                                  &dtstart, &rdate_period.time)) {
1019             /** call callback action **/
1020             if (icaltime_span_overlaps(&recurspan, &limit_span))
1021                 (*callback) (comp, &recurspan, callback_data);
1022         }
1023         comp->property_iterator = property_iterator;
1024     }
1025 }
1026 
icalcomponent_check_restrictions(icalcomponent * comp)1027 int icalcomponent_check_restrictions(icalcomponent *comp)
1028 {
1029     icalerror_check_arg_rz(comp != 0, "comp");
1030     return icalrestriction_check(comp);
1031 }
1032 
1033 /** @brief returns the number of errors encountered parsing the data
1034  *
1035  * This function counts the number times the X-LIC-ERROR occurs
1036  * in the data structure.
1037  */
1038 
icalcomponent_count_errors(icalcomponent * component)1039 int icalcomponent_count_errors(icalcomponent *component)
1040 {
1041     int errors = 0;
1042     icalproperty *p;
1043     icalcomponent *c;
1044     pvl_elem itr;
1045 
1046     icalerror_check_arg_rz((component != 0), "component");
1047 
1048     for (itr = pvl_head(component->properties); itr != 0; itr = pvl_next(itr)) {
1049         p = (icalproperty *) pvl_data(itr);
1050 
1051         if (icalproperty_isa(p) == ICAL_XLICERROR_PROPERTY) {
1052             errors++;
1053         }
1054     }
1055 
1056     for (itr = pvl_head(component->components); itr != 0; itr = pvl_next(itr)) {
1057         c = (icalcomponent *) pvl_data(itr);
1058 
1059         errors += icalcomponent_count_errors(c);
1060     }
1061 
1062     return errors;
1063 }
1064 
icalcomponent_strip_errors(icalcomponent * component)1065 void icalcomponent_strip_errors(icalcomponent *component)
1066 {
1067     icalproperty *p;
1068     icalcomponent *c;
1069     pvl_elem itr, next_itr;
1070 
1071     icalerror_check_arg_rv((component != 0), "component");
1072 
1073     for (itr = pvl_head(component->properties); itr != 0; itr = next_itr) {
1074         p = (icalproperty *) pvl_data(itr);
1075         next_itr = pvl_next(itr);
1076 
1077         if (icalproperty_isa(p) == ICAL_XLICERROR_PROPERTY) {
1078             icalcomponent_remove_property(component, p);
1079             icalproperty_free(p);
1080             p = NULL;
1081         }
1082     }
1083 
1084     for (itr = pvl_head(component->components); itr != 0; itr = pvl_next(itr)) {
1085         c = (icalcomponent *) pvl_data(itr);
1086         icalcomponent_strip_errors(c);
1087     }
1088 }
1089 
1090 /* Hack. This will change the state of the iterators */
icalcomponent_convert_errors(icalcomponent * component)1091 void icalcomponent_convert_errors(icalcomponent *component)
1092 {
1093     icalproperty *p, *next_p;
1094     icalcomponent *c;
1095 
1096     for (p = icalcomponent_get_first_property(component, ICAL_ANY_PROPERTY); p != 0; p = next_p) {
1097 
1098         next_p = icalcomponent_get_next_property(component, ICAL_ANY_PROPERTY);
1099 
1100         if (icalproperty_isa(p) == ICAL_XLICERROR_PROPERTY) {
1101             struct icalreqstattype rst;
1102             icalparameter *param =
1103                 icalproperty_get_first_parameter(p, ICAL_XLICERRORTYPE_PARAMETER);
1104 
1105             rst.code = ICAL_UNKNOWN_STATUS;
1106             rst.desc = 0;
1107 
1108             switch (icalparameter_get_xlicerrortype(param)) {
1109 
1110             case ICAL_XLICERRORTYPE_PARAMETERNAMEPARSEERROR:{
1111                     rst.code = ICAL_3_2_INVPARAM_STATUS;
1112                     break;
1113                 }
1114             case ICAL_XLICERRORTYPE_PARAMETERVALUEPARSEERROR:{
1115                     rst.code = ICAL_3_3_INVPARAMVAL_STATUS;
1116                     break;
1117                 }
1118             case ICAL_XLICERRORTYPE_PROPERTYPARSEERROR:{
1119                     rst.code = ICAL_3_0_INVPROPNAME_STATUS;
1120                     break;
1121                 }
1122             case ICAL_XLICERRORTYPE_VALUEPARSEERROR:{
1123                     rst.code = ICAL_3_1_INVPROPVAL_STATUS;
1124                     break;
1125                 }
1126             case ICAL_XLICERRORTYPE_COMPONENTPARSEERROR:{
1127                     rst.code = ICAL_3_4_INVCOMP_STATUS;
1128                     break;
1129                 }
1130 
1131             default:{
1132                     break;
1133                 }
1134             }
1135             if (rst.code != ICAL_UNKNOWN_STATUS) {
1136 
1137                 rst.debug = icalproperty_get_xlicerror(p);
1138                 icalcomponent_add_property(component, icalproperty_new_requeststatus(rst));
1139 
1140                 icalcomponent_remove_property(component, p);
1141                 icalproperty_free(p);
1142                 p = NULL;
1143             }
1144         }
1145     }
1146 
1147     for (c = icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT);
1148          c != 0; c = icalcomponent_get_next_component(component, ICAL_ANY_COMPONENT)) {
1149 
1150         icalcomponent_convert_errors(c);
1151     }
1152 }
1153 
icalcomponent_get_parent(icalcomponent * component)1154 icalcomponent *icalcomponent_get_parent(icalcomponent *component)
1155 {
1156     return component->parent;
1157 }
1158 
icalcomponent_set_parent(icalcomponent * component,icalcomponent * parent)1159 void icalcomponent_set_parent(icalcomponent *component, icalcomponent *parent)
1160 {
1161     component->parent = parent;
1162 }
1163 
1164 static icalcompiter icalcompiter_null = { ICAL_NO_COMPONENT, 0 };
1165 
1166 struct icalcomponent_kind_map
1167 {
1168     icalcomponent_kind kind;
1169     char name[20];
1170 };
1171 
1172 static const struct icalcomponent_kind_map component_map[] = {
1173     {ICAL_VEVENT_COMPONENT, "VEVENT"},
1174     {ICAL_VTODO_COMPONENT, "VTODO"},
1175     {ICAL_VJOURNAL_COMPONENT, "VJOURNAL"},
1176     {ICAL_VCALENDAR_COMPONENT, "VCALENDAR"},
1177     {ICAL_VAGENDA_COMPONENT, "VAGENDA"},
1178     {ICAL_VFREEBUSY_COMPONENT, "VFREEBUSY"},
1179     {ICAL_VTIMEZONE_COMPONENT, "VTIMEZONE"},
1180     {ICAL_VALARM_COMPONENT, "VALARM"},
1181     {ICAL_XSTANDARD_COMPONENT, "STANDARD"}, /*These are part of RFC5545 */
1182     {ICAL_XDAYLIGHT_COMPONENT, "DAYLIGHT"}, /*but are not really components */
1183     {ICAL_X_COMPONENT, "X"},
1184     {ICAL_VSCHEDULE_COMPONENT, "SCHEDULE"},
1185 
1186     /* CAP components */
1187     {ICAL_VCAR_COMPONENT, "VCAR"},
1188     {ICAL_VCOMMAND_COMPONENT, "VCOMMAND"},
1189     {ICAL_VQUERY_COMPONENT, "VQUERY"},
1190     {ICAL_VREPLY_COMPONENT, "VREPLY"},
1191 
1192     /* libical private components */
1193     {ICAL_XLICINVALID_COMPONENT, "X-LIC-UNKNOWN"},
1194     {ICAL_XLICMIMEPART_COMPONENT, "X-LIC-MIME-PART"},
1195     {ICAL_ANY_COMPONENT, "ANY"},
1196     {ICAL_XROOT_COMPONENT, "XROOT"},
1197 
1198     /* Calendar Availability components */
1199     {ICAL_VAVAILABILITY_COMPONENT, "VAVAILABILITY"},
1200     {ICAL_XAVAILABLE_COMPONENT, "AVAILABLE"},
1201 
1202     /* Consensus Scheduling components */
1203     {ICAL_VPOLL_COMPONENT, "VPOLL"},
1204     {ICAL_VVOTER_COMPONENT, "VVOTER"},
1205     {ICAL_XVOTE_COMPONENT, "VOTE"},
1206 
1207     /* VPATCH components */
1208     {ICAL_VPATCH_COMPONENT, "VPATCH"},
1209     {ICAL_XPATCH_COMPONENT, "PATCH"},
1210 
1211     /* End of list */
1212     {ICAL_NO_COMPONENT, ""},
1213 };
1214 
icalcomponent_kind_is_valid(const icalcomponent_kind kind)1215 int icalcomponent_kind_is_valid(const icalcomponent_kind kind)
1216 {
1217     int i = 0;
1218 
1219     do {
1220         if (component_map[i].kind == kind) {
1221             return 1;
1222         }
1223     } while (component_map[i++].kind != ICAL_NO_COMPONENT);
1224 
1225     return 0;
1226 }
1227 
icalcomponent_kind_to_string(icalcomponent_kind kind)1228 const char *icalcomponent_kind_to_string(icalcomponent_kind kind)
1229 {
1230     int i;
1231 
1232     for (i = 0; component_map[i].kind != ICAL_NO_COMPONENT; i++) {
1233         if (component_map[i].kind == kind) {
1234             return component_map[i].name;
1235         }
1236     }
1237 
1238     return 0;
1239 }
1240 
icalcomponent_string_to_kind(const char * string)1241 icalcomponent_kind icalcomponent_string_to_kind(const char *string)
1242 {
1243     int i;
1244 
1245     if (string == 0) {
1246         return ICAL_NO_COMPONENT;
1247     }
1248 
1249     for (i = 0; component_map[i].kind != ICAL_NO_COMPONENT; i++) {
1250         if (strncasecmp(string, component_map[i].name, strlen(component_map[i].name)) == 0) {
1251             return component_map[i].kind;
1252         }
1253     }
1254 
1255     return ICAL_NO_COMPONENT;
1256 }
1257 
icalcomponent_begin_component(icalcomponent * component,icalcomponent_kind kind)1258 icalcompiter icalcomponent_begin_component(icalcomponent *component, icalcomponent_kind kind)
1259 {
1260     icalcompiter itr;
1261     pvl_elem i;
1262 
1263     itr.kind = kind;
1264     itr.iter = NULL;
1265 
1266     icalerror_check_arg_re(component != 0, "component", icalcompiter_null);
1267 
1268     for (i = pvl_head(component->components); i != 0; i = pvl_next(i)) {
1269 
1270         icalcomponent *c = (icalcomponent *) pvl_data(i);
1271 
1272         if (icalcomponent_isa(c) == kind || kind == ICAL_ANY_COMPONENT) {
1273 
1274             itr.iter = i;
1275 
1276             return itr;
1277         }
1278     }
1279 
1280     return icalcompiter_null;
1281 }
1282 
icalcomponent_end_component(icalcomponent * component,icalcomponent_kind kind)1283 icalcompiter icalcomponent_end_component(icalcomponent *component, icalcomponent_kind kind)
1284 {
1285     icalcompiter itr;
1286     pvl_elem i;
1287 
1288     itr.kind = kind;
1289 
1290     icalerror_check_arg_re(component != 0, "component", icalcompiter_null);
1291 
1292     for (i = pvl_tail(component->components); i != 0; i = pvl_prior(i)) {
1293 
1294         icalcomponent *c = (icalcomponent *) pvl_data(i);
1295 
1296         if (icalcomponent_isa(c) == kind || kind == ICAL_ANY_COMPONENT) {
1297 
1298             itr.iter = pvl_next(i);
1299 
1300             return itr;
1301         }
1302     }
1303 
1304     return icalcompiter_null;
1305 }
1306 
icalcompiter_next(icalcompiter * i)1307 icalcomponent *icalcompiter_next(icalcompiter *i)
1308 {
1309     if (i->iter == 0) {
1310         return 0;
1311     }
1312 
1313     icalerror_check_arg_rz((i != 0), "i");
1314 
1315     for (i->iter = pvl_next(i->iter); i->iter != 0; i->iter = pvl_next(i->iter)) {
1316 
1317         icalcomponent *c = (icalcomponent *) pvl_data(i->iter);
1318 
1319         if (icalcomponent_isa(c) == i->kind || i->kind == ICAL_ANY_COMPONENT) {
1320 
1321             return icalcompiter_deref(i);
1322         }
1323     }
1324 
1325     return 0;
1326 }
1327 
icalcompiter_prior(icalcompiter * i)1328 icalcomponent *icalcompiter_prior(icalcompiter *i)
1329 {
1330     if (i->iter == 0) {
1331         return 0;
1332     }
1333 
1334     for (i->iter = pvl_prior(i->iter); i->iter != 0; i->iter = pvl_prior(i->iter)) {
1335 
1336         icalcomponent *c = (icalcomponent *) pvl_data(i->iter);
1337 
1338         if (icalcomponent_isa(c) == i->kind || i->kind == ICAL_ANY_COMPONENT) {
1339 
1340             return icalcompiter_deref(i);
1341         }
1342     }
1343 
1344     return 0;
1345 }
1346 
icalcompiter_deref(icalcompiter * i)1347 icalcomponent *icalcompiter_deref(icalcompiter *i)
1348 {
1349     if (i->iter == 0) {
1350         return 0;
1351     }
1352 
1353     return pvl_data(i->iter);
1354 }
1355 
icalcomponent_get_inner(icalcomponent * comp)1356 icalcomponent *icalcomponent_get_inner(icalcomponent *comp)
1357 {
1358     if (icalcomponent_isa(comp) == ICAL_VCALENDAR_COMPONENT) {
1359         return icalcomponent_get_first_real_component(comp);
1360     } else {
1361         return comp;
1362     }
1363 }
1364 
1365 /** @brief sets the METHOD property to the given method
1366  */
1367 
icalcomponent_set_method(icalcomponent * comp,icalproperty_method method)1368 void icalcomponent_set_method(icalcomponent *comp, icalproperty_method method)
1369 {
1370     icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_METHOD_PROPERTY);
1371 
1372     if (prop == 0) {
1373         prop = icalproperty_new_method(method);
1374         icalcomponent_add_property(comp, prop);
1375     }
1376 
1377     icalproperty_set_method(prop, method);
1378 }
1379 
1380 /** @brief returns the METHOD property
1381  */
1382 
icalcomponent_get_method(icalcomponent * comp)1383 icalproperty_method icalcomponent_get_method(icalcomponent *comp)
1384 {
1385     icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_METHOD_PROPERTY);
1386 
1387     if (prop == 0) {
1388         return ICAL_METHOD_NONE;
1389     }
1390 
1391     return icalproperty_get_method(prop);
1392 }
1393 
1394 #define ICALSETUPSET(p_kind) \
1395 icalcomponent *inner; \
1396 icalproperty *prop; \
1397 icalerror_check_arg_rv(comp != 0,"comp"); \
1398 inner = icalcomponent_get_inner(comp); \
1399 if (inner == 0) { \
1400     icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); \
1401     return; \
1402 } \
1403 prop = icalcomponent_get_first_property(inner, p_kind);
1404 
1405 /**     @brief Set DTSTART property to given icaltime
1406  *
1407  *      This method respects the icaltime type (DATE vs DATE-TIME) and
1408  *      timezone (or lack thereof).
1409  */
icalcomponent_set_dtstart(icalcomponent * comp,struct icaltimetype v)1410 void icalcomponent_set_dtstart(icalcomponent *comp, struct icaltimetype v)
1411 {
1412     const char *tzid;
1413 
1414     ICALSETUPSET(ICAL_DTSTART_PROPERTY);
1415 
1416     if (prop == 0) {
1417         prop = icalproperty_new_dtstart(v);
1418         icalcomponent_add_property(inner, prop);
1419     } else {
1420         icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER);
1421     }
1422 
1423     icalproperty_set_dtstart(prop, v);
1424 
1425     if ((tzid = icaltime_get_tzid(v)) != NULL && !icaltime_is_utc(v)) {
1426         icalproperty_add_parameter(prop, icalparameter_new_tzid(tzid));
1427     }
1428 }
1429 
1430 /**     @brief Get DTSTART property as an icaltime
1431  *
1432  *      If DTSTART is a DATE-TIME with a timezone parameter and a
1433  *      corresponding VTIMEZONE is present in the component, the
1434  *      returned component will already be in the correct timezone;
1435  *      otherwise the caller is responsible for converting it.
1436  *
1437  *      FIXME this is useless until we can flag the failure
1438  */
icalcomponent_get_dtstart(icalcomponent * comp)1439 struct icaltimetype icalcomponent_get_dtstart(icalcomponent *comp)
1440 {
1441     icalcomponent *inner = icalcomponent_get_inner(comp);
1442     icalproperty *prop;
1443 
1444     prop = icalcomponent_get_first_property(inner, ICAL_DTSTART_PROPERTY);
1445     if (prop == 0) {
1446         return icaltime_null_time();
1447     }
1448 
1449     return icalproperty_get_datetime_with_component(prop, comp);
1450 }
1451 
1452 /**     @brief Get DTEND property as an icaltime
1453  *
1454  *      If a DTEND property is not present but a DURATION is, we use
1455  *      that to determine the proper end.
1456  *
1457  *      If DTSTART is a DATE-TIME with a timezone parameter and a
1458  *      corresponding VTIMEZONE is present in the component, the
1459  *      returned component will already be in the correct timezone;
1460  *      otherwise the caller is responsible for converting it.
1461  *
1462  *      FIXME this is useless until we can flag the failure
1463  */
icalcomponent_get_dtend(icalcomponent * comp)1464 struct icaltimetype icalcomponent_get_dtend(icalcomponent *comp)
1465 {
1466     icalcomponent *inner = icalcomponent_get_inner(comp);
1467     icalproperty *end_prop = icalcomponent_get_first_property(inner, ICAL_DTEND_PROPERTY);
1468     icalproperty *dur_prop = icalcomponent_get_first_property(inner, ICAL_DURATION_PROPERTY);
1469     struct icaltimetype ret = icaltime_null_time();
1470 
1471     if (end_prop != 0) {
1472         ret = icalproperty_get_datetime_with_component(end_prop, comp);
1473     } else if (dur_prop != 0) {
1474 
1475         struct icaltimetype start = icalcomponent_get_dtstart(inner);
1476         struct icaldurationtype duration;
1477 
1478         //extra check to prevent empty durations from crashing
1479         if (icalproperty_get_value(dur_prop)) {
1480             duration = icalproperty_get_duration(dur_prop);
1481         } else {
1482             duration = icaldurationtype_null_duration();
1483         }
1484 
1485         ret = icaltime_add(start, duration);
1486     }
1487 
1488     return ret;
1489 }
1490 
1491 /**     @brief Set DTEND property to given icaltime
1492  *
1493  *      This method respects the icaltime type (DATE vs DATE-TIME) and
1494  *      timezone (or lack thereof).
1495  *
1496  *      This also checks that a DURATION property isn't already there,
1497  *      and returns an error if it is. It's the caller's responsibility
1498  *      to remove it.
1499  */
icalcomponent_set_dtend(icalcomponent * comp,struct icaltimetype v)1500 void icalcomponent_set_dtend(icalcomponent *comp, struct icaltimetype v)
1501 {
1502     const char *tzid;
1503 
1504     ICALSETUPSET(ICAL_DTEND_PROPERTY);
1505 
1506     if (icalcomponent_get_first_property(inner, ICAL_DURATION_PROPERTY) != NULL) {
1507         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1508         return;
1509     }
1510 
1511     if (prop == 0) {
1512         prop = icalproperty_new_dtend(v);
1513         icalcomponent_add_property(inner, prop);
1514     } else {
1515         icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER);
1516     }
1517 
1518     icalproperty_set_dtend(prop, v);
1519 
1520     if ((tzid = icaltime_get_tzid(v)) != NULL && !icaltime_is_utc(v)) {
1521         icalproperty_add_parameter(prop, icalparameter_new_tzid(tzid));
1522     }
1523 }
1524 
1525 /**     @brief Set DURATION property to given icalduration
1526  *
1527  *      This method respects the icaltime type (DATE vs DATE-TIME) and
1528  *      timezone (or lack thereof).
1529  *
1530  *      This also checks that a DTEND property isn't already there,
1531  *      and returns an error if it is. It's the caller's responsibility
1532  *      to remove it.
1533  */
icalcomponent_set_duration(icalcomponent * comp,struct icaldurationtype v)1534 void icalcomponent_set_duration(icalcomponent *comp, struct icaldurationtype v)
1535 {
1536     ICALSETUPSET(ICAL_DURATION_PROPERTY);
1537 
1538     if (icalcomponent_get_first_property(inner, ICAL_DTEND_PROPERTY) != NULL) {
1539         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1540         return;
1541     }
1542 
1543     if (prop == 0) {
1544         prop = icalproperty_new_duration(v);
1545         icalcomponent_add_property(inner, prop);
1546     } else {
1547         icalproperty_set_duration(prop, v);
1548     }
1549 }
1550 
1551 /**     @brief Get DURATION property as an icalduration
1552  *
1553  *      If a DURATION property is not present but a DTEND is, we use
1554  *      that to determine the proper end.
1555  */
icalcomponent_get_duration(icalcomponent * comp)1556 struct icaldurationtype icalcomponent_get_duration(icalcomponent *comp)
1557 {
1558     icalcomponent *inner = icalcomponent_get_inner(comp);
1559 
1560     icalproperty *end_prop = icalcomponent_get_first_property(inner, ICAL_DTEND_PROPERTY);
1561 
1562     icalproperty *dur_prop = icalcomponent_get_first_property(inner, ICAL_DURATION_PROPERTY);
1563 
1564     struct icaldurationtype ret = icaldurationtype_null_duration();
1565 
1566     if (dur_prop != 0 && end_prop == 0) {
1567         ret = icalproperty_get_duration(dur_prop);
1568 
1569     } else if (end_prop != 0 && dur_prop == 0) {
1570         /**
1571          * FIXME
1572          * We assume DTSTART and DTEND are not in different time zones.
1573          * Does the standard actually guarantee this?
1574          */
1575         struct icaltimetype start = icalcomponent_get_dtstart(inner);
1576         struct icaltimetype end = icalcomponent_get_dtend(inner);
1577 
1578         ret = icaltime_subtract(end, start);
1579     } else {
1580         /* Error, both duration and dtend have been specified */
1581         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1582     }
1583     return ret;
1584 }
1585 
icalcomponent_set_dtstamp(icalcomponent * comp,struct icaltimetype v)1586 void icalcomponent_set_dtstamp(icalcomponent *comp, struct icaltimetype v)
1587 {
1588     ICALSETUPSET(ICAL_DTSTAMP_PROPERTY);
1589 
1590     if (prop == 0) {
1591         prop = icalproperty_new_dtstamp(v);
1592         icalcomponent_add_property(inner, prop);
1593     }
1594 
1595     icalproperty_set_dtstamp(prop, v);
1596 }
1597 
icalcomponent_get_dtstamp(icalcomponent * comp)1598 struct icaltimetype icalcomponent_get_dtstamp(icalcomponent *comp)
1599 {
1600     icalcomponent *inner = icalcomponent_get_inner(comp);
1601     icalproperty *prop = icalcomponent_get_first_property(inner, ICAL_DTSTAMP_PROPERTY);
1602 
1603     if (prop == 0) {
1604         return icaltime_null_time();
1605     }
1606 
1607     return icalproperty_get_dtstamp(prop);
1608 }
1609 
icalcomponent_set_summary(icalcomponent * comp,const char * v)1610 void icalcomponent_set_summary(icalcomponent *comp, const char *v)
1611 {
1612     ICALSETUPSET(ICAL_SUMMARY_PROPERTY)
1613 
1614     if (prop == 0) {
1615         prop = icalproperty_new_summary(v);
1616         icalcomponent_add_property(inner, prop);
1617     }
1618 
1619     icalproperty_set_summary(prop, v);
1620 }
1621 
icalcomponent_get_summary(icalcomponent * comp)1622 const char *icalcomponent_get_summary(icalcomponent *comp)
1623 {
1624     icalcomponent *inner;
1625     icalproperty *prop;
1626 
1627     icalerror_check_arg_rz(comp != 0, "comp");
1628 
1629     inner = icalcomponent_get_inner(comp);
1630 
1631     if (inner == 0) {
1632         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1633         return 0;
1634     }
1635 
1636     prop = icalcomponent_get_first_property(inner, ICAL_SUMMARY_PROPERTY);
1637 
1638     if (prop == 0) {
1639         return 0;
1640     }
1641 
1642     return icalproperty_get_summary(prop);
1643 }
1644 
icalcomponent_set_comment(icalcomponent * comp,const char * v)1645 void icalcomponent_set_comment(icalcomponent *comp, const char *v)
1646 {
1647     ICALSETUPSET(ICAL_COMMENT_PROPERTY);
1648 
1649     if (prop == 0) {
1650         prop = icalproperty_new_comment(v);
1651         icalcomponent_add_property(inner, prop);
1652     }
1653 
1654     icalproperty_set_comment(prop, v);
1655 }
1656 
icalcomponent_get_comment(icalcomponent * comp)1657 const char *icalcomponent_get_comment(icalcomponent *comp)
1658 {
1659     icalcomponent *inner;
1660     icalproperty *prop;
1661 
1662     icalerror_check_arg_rz(comp != 0, "comp");
1663 
1664     inner = icalcomponent_get_inner(comp);
1665 
1666     if (inner == 0) {
1667         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1668         return 0;
1669     }
1670 
1671     prop = icalcomponent_get_first_property(inner, ICAL_COMMENT_PROPERTY);
1672 
1673     if (prop == 0) {
1674         return 0;
1675     }
1676 
1677     return icalproperty_get_comment(prop);
1678 }
1679 
icalcomponent_set_uid(icalcomponent * comp,const char * v)1680 void icalcomponent_set_uid(icalcomponent *comp, const char *v)
1681 {
1682     ICALSETUPSET(ICAL_UID_PROPERTY);
1683 
1684     if (prop == 0) {
1685         prop = icalproperty_new_uid(v);
1686         icalcomponent_add_property(inner, prop);
1687     }
1688 
1689     icalproperty_set_uid(prop, v);
1690 }
1691 
icalcomponent_get_uid(icalcomponent * comp)1692 const char *icalcomponent_get_uid(icalcomponent *comp)
1693 {
1694     icalcomponent *inner;
1695     icalproperty *prop;
1696 
1697     icalerror_check_arg_rz(comp != 0, "comp");
1698 
1699     inner = icalcomponent_get_inner(comp);
1700 
1701     if (inner == 0) {
1702         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1703         return 0;
1704     }
1705 
1706     prop = icalcomponent_get_first_property(inner, ICAL_UID_PROPERTY);
1707 
1708     if (prop == 0) {
1709         return 0;
1710     }
1711 
1712     return icalproperty_get_uid(prop);
1713 }
1714 
icalcomponent_set_recurrenceid(icalcomponent * comp,struct icaltimetype v)1715 void icalcomponent_set_recurrenceid(icalcomponent *comp, struct icaltimetype v)
1716 {
1717     ICALSETUPSET(ICAL_RECURRENCEID_PROPERTY);
1718 
1719     if (prop == 0) {
1720         prop = icalproperty_new_recurrenceid(v);
1721         icalcomponent_add_property(inner, prop);
1722     }
1723 
1724     icalproperty_set_recurrenceid(prop, v);
1725 }
1726 
icalcomponent_get_recurrenceid(icalcomponent * comp)1727 struct icaltimetype icalcomponent_get_recurrenceid(icalcomponent *comp)
1728 {
1729     icalcomponent *inner;
1730     icalproperty *prop;
1731 
1732     if (comp == 0) {
1733         icalerror_set_errno(ICAL_BADARG_ERROR);
1734         return icaltime_null_time();
1735     }
1736 
1737     inner = icalcomponent_get_inner(comp);
1738 
1739     if (inner == 0) {
1740         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1741         return icaltime_null_time();
1742     }
1743 
1744     prop = icalcomponent_get_first_property(inner, ICAL_RECURRENCEID_PROPERTY);
1745 
1746     if (prop == 0) {
1747         return icaltime_null_time();
1748     }
1749 
1750     return icalproperty_get_recurrenceid(prop);
1751 }
1752 
icalcomponent_set_description(icalcomponent * comp,const char * v)1753 void icalcomponent_set_description(icalcomponent *comp, const char *v)
1754 {
1755     ICALSETUPSET(ICAL_DESCRIPTION_PROPERTY);
1756 
1757     if (prop == 0) {
1758         prop = icalproperty_new_description(v);
1759         icalcomponent_add_property(inner, prop);
1760     }
1761 
1762     icalproperty_set_description(prop, v);
1763 }
1764 
icalcomponent_get_description(icalcomponent * comp)1765 const char *icalcomponent_get_description(icalcomponent *comp)
1766 {
1767     icalcomponent *inner;
1768     icalproperty *prop;
1769 
1770     icalerror_check_arg_rz(comp != 0, "comp");
1771 
1772     inner = icalcomponent_get_inner(comp);
1773 
1774     if (inner == 0) {
1775         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1776         return 0;
1777     }
1778 
1779     prop = icalcomponent_get_first_property(inner, ICAL_DESCRIPTION_PROPERTY);
1780 
1781     if (prop == 0) {
1782         return 0;
1783     }
1784 
1785     return icalproperty_get_description(prop);
1786 }
1787 
icalcomponent_set_location(icalcomponent * comp,const char * v)1788 void icalcomponent_set_location(icalcomponent *comp, const char *v)
1789 {
1790     ICALSETUPSET(ICAL_LOCATION_PROPERTY)
1791 
1792     if (prop == 0) {
1793         prop = icalproperty_new_location(v);
1794         icalcomponent_add_property(inner, prop);
1795     }
1796 
1797     icalproperty_set_location(prop, v);
1798 }
1799 
icalcomponent_get_location(icalcomponent * comp)1800 const char *icalcomponent_get_location(icalcomponent *comp)
1801 {
1802     icalcomponent *inner;
1803     icalproperty *prop;
1804 
1805     icalerror_check_arg_rz(comp != 0, "comp");
1806 
1807     inner = icalcomponent_get_inner(comp);
1808 
1809     if (inner == 0) {
1810         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1811         return 0;
1812     }
1813 
1814     prop = icalcomponent_get_first_property(inner, ICAL_LOCATION_PROPERTY);
1815 
1816     if (prop == 0) {
1817         return 0;
1818     }
1819 
1820     return icalproperty_get_location(prop);
1821 }
1822 
icalcomponent_set_sequence(icalcomponent * comp,int v)1823 void icalcomponent_set_sequence(icalcomponent *comp, int v)
1824 {
1825     ICALSETUPSET(ICAL_SEQUENCE_PROPERTY);
1826 
1827     if (prop == 0) {
1828         prop = icalproperty_new_sequence(v);
1829         icalcomponent_add_property(inner, prop);
1830     }
1831 
1832     icalproperty_set_sequence(prop, v);
1833 }
1834 
icalcomponent_get_sequence(icalcomponent * comp)1835 int icalcomponent_get_sequence(icalcomponent *comp)
1836 {
1837     icalcomponent *inner;
1838     icalproperty *prop;
1839 
1840     icalerror_check_arg_rz(comp != 0, "comp");
1841 
1842     inner = icalcomponent_get_inner(comp);
1843 
1844     if (inner == 0) {
1845         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1846         return 0;
1847     }
1848 
1849     prop = icalcomponent_get_first_property(inner, ICAL_SEQUENCE_PROPERTY);
1850 
1851     if (prop == 0) {
1852         return 0;
1853     }
1854 
1855     return icalproperty_get_sequence(prop);
1856 }
1857 
icalcomponent_set_status(icalcomponent * comp,enum icalproperty_status v)1858 void icalcomponent_set_status(icalcomponent *comp, enum icalproperty_status v)
1859 {
1860     ICALSETUPSET(ICAL_STATUS_PROPERTY);
1861 
1862     if (prop == 0) {
1863         prop = icalproperty_new_status(v);
1864         icalcomponent_add_property(inner, prop);
1865     }
1866 
1867     icalproperty_set_status(prop, v);
1868 }
1869 
icalcomponent_get_status(icalcomponent * comp)1870 enum icalproperty_status icalcomponent_get_status(icalcomponent *comp)
1871 {
1872     icalcomponent *inner;
1873     icalproperty *prop;
1874 
1875     icalerror_check_arg_rz(comp != 0, "comp");
1876 
1877     inner = icalcomponent_get_inner(comp);
1878 
1879     if (inner == 0) {
1880         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1881         return 0;
1882     }
1883 
1884     prop = icalcomponent_get_first_property(inner, ICAL_STATUS_PROPERTY);
1885 
1886     if (prop == 0) {
1887         return 0;
1888     }
1889 
1890     return icalproperty_get_status(prop);
1891 }
1892 
icalcomponent_new_vcalendar(void)1893 icalcomponent *icalcomponent_new_vcalendar(void)
1894 {
1895     return icalcomponent_new(ICAL_VCALENDAR_COMPONENT);
1896 }
1897 
icalcomponent_new_vevent(void)1898 icalcomponent *icalcomponent_new_vevent(void)
1899 {
1900     return icalcomponent_new(ICAL_VEVENT_COMPONENT);
1901 }
1902 
icalcomponent_new_vtodo(void)1903 icalcomponent *icalcomponent_new_vtodo(void)
1904 {
1905     return icalcomponent_new(ICAL_VTODO_COMPONENT);
1906 }
1907 
icalcomponent_new_vjournal(void)1908 icalcomponent *icalcomponent_new_vjournal(void)
1909 {
1910     return icalcomponent_new(ICAL_VJOURNAL_COMPONENT);
1911 }
1912 
icalcomponent_new_valarm(void)1913 icalcomponent *icalcomponent_new_valarm(void)
1914 {
1915     return icalcomponent_new(ICAL_VALARM_COMPONENT);
1916 }
1917 
icalcomponent_new_vfreebusy(void)1918 icalcomponent *icalcomponent_new_vfreebusy(void)
1919 {
1920     return icalcomponent_new(ICAL_VFREEBUSY_COMPONENT);
1921 }
1922 
icalcomponent_new_vtimezone(void)1923 icalcomponent *icalcomponent_new_vtimezone(void)
1924 {
1925     return icalcomponent_new(ICAL_VTIMEZONE_COMPONENT);
1926 }
1927 
icalcomponent_new_xstandard(void)1928 icalcomponent *icalcomponent_new_xstandard(void)
1929 {
1930     return icalcomponent_new(ICAL_XSTANDARD_COMPONENT);
1931 }
1932 
icalcomponent_new_xdaylight(void)1933 icalcomponent *icalcomponent_new_xdaylight(void)
1934 {
1935     return icalcomponent_new(ICAL_XDAYLIGHT_COMPONENT);
1936 }
1937 
icalcomponent_new_vagenda(void)1938 icalcomponent *icalcomponent_new_vagenda(void)
1939 {
1940     return icalcomponent_new(ICAL_VAGENDA_COMPONENT);
1941 }
1942 
icalcomponent_new_vquery(void)1943 icalcomponent *icalcomponent_new_vquery(void)
1944 {
1945     return icalcomponent_new(ICAL_VQUERY_COMPONENT);
1946 }
1947 
icalcomponent_new_vreply(void)1948 icalcomponent *icalcomponent_new_vreply(void)
1949 {
1950     return icalcomponent_new(ICAL_VREPLY_COMPONENT);
1951 }
1952 
icalcomponent_new_vavailability(void)1953 icalcomponent *icalcomponent_new_vavailability(void)
1954 {
1955     return icalcomponent_new(ICAL_VAVAILABILITY_COMPONENT);
1956 }
1957 
icalcomponent_new_xavailable(void)1958 icalcomponent *icalcomponent_new_xavailable(void)
1959 {
1960     return icalcomponent_new(ICAL_XAVAILABLE_COMPONENT);
1961 }
1962 
icalcomponent_new_vpoll(void)1963 icalcomponent *icalcomponent_new_vpoll(void)
1964 {
1965     return icalcomponent_new(ICAL_VPOLL_COMPONENT);
1966 }
1967 
icalcomponent_new_vvoter(void)1968 icalcomponent *icalcomponent_new_vvoter(void)
1969 {
1970     return icalcomponent_new(ICAL_VVOTER_COMPONENT);
1971 }
1972 
icalcomponent_new_xvote(void)1973 icalcomponent *icalcomponent_new_xvote(void)
1974 {
1975     return icalcomponent_new(ICAL_XVOTE_COMPONENT);
1976 }
1977 
icalcomponent_new_vpatch(void)1978 icalcomponent *icalcomponent_new_vpatch(void)
1979 {
1980     return icalcomponent_new(ICAL_VPATCH_COMPONENT);
1981 }
1982 
icalcomponent_new_xpatch(void)1983 icalcomponent *icalcomponent_new_xpatch(void)
1984 {
1985     return icalcomponent_new(ICAL_XPATCH_COMPONENT);
1986 }
1987 
1988 /*
1989  * Timezone stuff.
1990  */
1991 
1992 /**
1993  *  This takes 2 VCALENDAR components and merges the second one into the first,
1994  *  resolving any problems with conflicting TZIDs. comp_to_merge will no
1995  *  longer exist after calling this function.
1996  */
icalcomponent_merge_component(icalcomponent * comp,icalcomponent * comp_to_merge)1997 void icalcomponent_merge_component(icalcomponent *comp, icalcomponent *comp_to_merge)
1998 {
1999     icalcomponent *subcomp, *next_subcomp;
2000     icalarray *tzids_to_rename;
2001     size_t i;
2002 
2003     /* Check that both components are VCALENDAR components. */
2004     assert(icalcomponent_isa(comp) == ICAL_VCALENDAR_COMPONENT);
2005     assert(icalcomponent_isa(comp_to_merge) == ICAL_VCALENDAR_COMPONENT);
2006 
2007     /* Step through each subcomponent of comp_to_merge, looking for VTIMEZONEs.
2008        For each VTIMEZONE found, check if we need to add it to comp and if we
2009        need to rename it and all TZID references to it. */
2010     tzids_to_rename = icalarray_new(sizeof(char *), 16);
2011     subcomp = icalcomponent_get_first_component(comp_to_merge, ICAL_VTIMEZONE_COMPONENT);
2012     while (subcomp) {
2013         next_subcomp = icalcomponent_get_next_component(comp_to_merge, ICAL_VTIMEZONE_COMPONENT);
2014         /* This will add the VTIMEZONE to comp, if necessary, and also update
2015            the array of TZIDs we need to rename. */
2016         icalcomponent_merge_vtimezone(comp, subcomp, tzids_to_rename);
2017         /* FIXME: Handle possible NEWFAILED error. */
2018 
2019         subcomp = next_subcomp;
2020     }
2021 
2022     /* If we need to do any renaming of TZIDs, do it now. */
2023     if (tzids_to_rename->num_elements != 0) {
2024         icalcomponent_rename_tzids(comp_to_merge, tzids_to_rename);
2025 
2026         /* Now free the tzids_to_rename array. */
2027         for (i = 0; i < tzids_to_rename->num_elements; i++) {
2028             free(icalarray_element_at(tzids_to_rename, i));
2029         }
2030     }
2031     icalarray_free(tzids_to_rename);
2032     tzids_to_rename = 0;
2033     /* Now move all the components from comp_to_merge to comp, excluding
2034        VTIMEZONE components. */
2035     subcomp = icalcomponent_get_first_component(comp_to_merge, ICAL_ANY_COMPONENT);
2036     while (subcomp) {
2037         next_subcomp = icalcomponent_get_next_component(comp_to_merge, ICAL_ANY_COMPONENT);
2038         if (icalcomponent_isa(subcomp) != ICAL_VTIMEZONE_COMPONENT) {
2039             icalcomponent_remove_component(comp_to_merge, subcomp);
2040             icalcomponent_add_component(comp, subcomp);
2041         }
2042         subcomp = next_subcomp;
2043     }
2044 
2045     /* Free comp_to_merge. We have moved most of the subcomponents over to
2046        comp now. */
2047     icalcomponent_free(comp_to_merge);
2048 }
2049 
icalcomponent_merge_vtimezone(icalcomponent * comp,icalcomponent * vtimezone,icalarray * tzids_to_rename)2050 static void icalcomponent_merge_vtimezone(icalcomponent *comp,
2051                                           icalcomponent *vtimezone, icalarray *tzids_to_rename)
2052 {
2053     icalproperty *tzid_prop;
2054     const char *tzid;
2055     char *tzid_copy;
2056     icaltimezone *existing_vtimezone;
2057 
2058     /* Get the TZID of the VTIMEZONE. */
2059     tzid_prop = icalcomponent_get_first_property(vtimezone, ICAL_TZID_PROPERTY);
2060     if (!tzid_prop)
2061         return;
2062 
2063     tzid = icalproperty_get_tzid(tzid_prop);
2064     if (!tzid)
2065         return;
2066 
2067     /* See if there is already a VTIMEZONE in comp with the same TZID. */
2068     existing_vtimezone = icalcomponent_get_timezone(comp, tzid);
2069 
2070     /* If there is no existing VTIMEZONE with the same TZID, we can just move
2071        the VTIMEZONE to comp and return. */
2072     if (!existing_vtimezone) {
2073         icalcomponent_remove_component(icalcomponent_get_parent(vtimezone), vtimezone);
2074         icalcomponent_add_component(comp, vtimezone);
2075         return;
2076     }
2077 
2078     /* If the TZID has a '/' prefix, then we don't have to worry about the
2079        clashing TZIDs, as they are supposed to be exactly the same VTIMEZONE. */
2080     if (tzid[0] == '/')
2081         return;
2082 
2083     /* Now we have two VTIMEZONEs with the same TZID (which isn't a globally
2084        unique one), so we compare the VTIMEZONE components to see if they are
2085        the same. If they are, we don't need to do anything. We make a copy of
2086        the tzid, since the parameter may get modified in these calls. */
2087     tzid_copy = strdup(tzid);
2088     if (!tzid_copy) {
2089         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
2090         return;
2091     }
2092 
2093     if (!icalcomponent_compare_vtimezones(comp, vtimezone)) {
2094         /* FIXME: Handle possible NEWFAILED error. */
2095 
2096         /* Now we have two different VTIMEZONEs with the same TZID. */
2097         icalcomponent_handle_conflicting_vtimezones(comp, vtimezone, tzid_prop,
2098                                                     tzid_copy, tzids_to_rename);
2099     }
2100     free(tzid_copy);
2101 }
2102 
icalcomponent_handle_conflicting_vtimezones(icalcomponent * comp,icalcomponent * vtimezone,icalproperty * tzid_prop,const char * tzid,icalarray * tzids_to_rename)2103 static void icalcomponent_handle_conflicting_vtimezones(icalcomponent *comp,
2104                                                         icalcomponent *vtimezone,
2105                                                         icalproperty *tzid_prop,
2106                                                         const char *tzid,
2107                                                         icalarray *tzids_to_rename)
2108 {
2109     int suffix, max_suffix = 0;
2110     size_t i, num_elements, tzid_len;
2111     char *tzid_copy, *new_tzid, suffix_buf[32];
2112 
2113     _unused(tzid_prop);
2114 
2115     /* Find the length of the TZID without any trailing digits. */
2116     tzid_len = icalcomponent_get_tzid_prefix_len(tzid);
2117 
2118     /* Step through each of the VTIMEZONEs in comp. We may already have the
2119        clashing VTIMEZONE in the calendar, but it may have been renamed
2120        (i.e. a unique number added on the end of the TZID, e.g. 'London2').
2121        So we compare the new VTIMEZONE with any VTIMEZONEs that have the
2122        same prefix (e.g. 'London'). If it matches any of those, we have to
2123        rename the TZIDs to that TZID, else we rename to a new TZID, using
2124        the biggest numeric suffix found + 1. */
2125     num_elements = comp->timezones ? comp->timezones->num_elements : 0;
2126     for (i = 0; i < num_elements; i++) {
2127         icaltimezone *zone;
2128         const char *existing_tzid;
2129         char *existing_tzid_copy;
2130         size_t existing_tzid_len;
2131 
2132         zone = icalarray_element_at(comp->timezones, i);
2133         existing_tzid = icaltimezone_get_tzid(zone);
2134 
2135         /* Find the length of the TZID without any trailing digits. */
2136         existing_tzid_len = icalcomponent_get_tzid_prefix_len(existing_tzid);
2137 
2138         /* Check if we have the same prefix. */
2139         if (tzid_len == existing_tzid_len && !strncmp(tzid, existing_tzid, tzid_len)) {
2140             /* Compare the VTIMEZONEs. */
2141             if (icalcomponent_compare_vtimezones(icaltimezone_get_component(zone), vtimezone)) {
2142                 /* The VTIMEZONEs match, so we can use the existing VTIMEZONE. But
2143                    we have to rename TZIDs to this TZID. */
2144                 tzid_copy = strdup(tzid);
2145                 if (!tzid_copy) {
2146                     icalerror_set_errno(ICAL_NEWFAILED_ERROR);
2147                     return;
2148                 }
2149                 existing_tzid_copy = strdup(existing_tzid);
2150                 if (!existing_tzid_copy) {
2151                     icalerror_set_errno(ICAL_NEWFAILED_ERROR);
2152                     free(tzid_copy);
2153                 } else {
2154                     icalarray_append(tzids_to_rename, tzid_copy);
2155                     free(tzid_copy);
2156                     icalarray_append(tzids_to_rename, existing_tzid_copy);
2157                     free(existing_tzid_copy);
2158                 }
2159                 return;
2160             } else {
2161                 /* FIXME: Handle possible NEWFAILED error. */
2162 
2163                 /* Convert the suffix to an integer and remember the maximum numeric
2164                    suffix found. */
2165                 suffix = atoi(existing_tzid + existing_tzid_len);
2166                 if (max_suffix < suffix)
2167                     max_suffix = suffix;
2168             }
2169         }
2170     }
2171 
2172     /* We didn't find a VTIMEZONE that matched, so we have to rename the TZID,
2173        using the maximum numerical suffix found + 1. */
2174     tzid_copy = strdup(tzid);
2175     if (!tzid_copy) {
2176         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
2177         return;
2178     }
2179 
2180     snprintf(suffix_buf, sizeof(suffix_buf), "%i", max_suffix + 1);
2181     new_tzid = malloc(tzid_len + strlen(suffix_buf) + 1);
2182     if (!new_tzid) {
2183         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
2184         free(tzid_copy);
2185         return;
2186     }
2187 
2188     strncpy(new_tzid, tzid, tzid_len);
2189     strcpy(new_tzid + tzid_len, suffix_buf);
2190     icalarray_append(tzids_to_rename, tzid_copy);
2191     icalarray_append(tzids_to_rename, new_tzid);
2192     free(tzid_copy);
2193     free(new_tzid);
2194 }
2195 
2196 /* Returns the length of the TZID, without any trailing digits. */
icalcomponent_get_tzid_prefix_len(const char * tzid)2197 static size_t icalcomponent_get_tzid_prefix_len(const char *tzid)
2198 {
2199     size_t len;
2200     const char *p;
2201 
2202     len = strlen(tzid);
2203     p = tzid + len - 1;
2204     while (len > 0 && *p >= '0' && *p <= '9') {
2205         p--;
2206         len--;
2207     }
2208 
2209     return len;
2210 }
2211 
2212 /**
2213  * Renames all references to the given TZIDs to a new name. rename_table
2214  * contains pairs of strings - a current TZID, and the new TZID to rename it
2215  * to.
2216  */
icalcomponent_rename_tzids(icalcomponent * comp,icalarray * rename_table)2217 static void icalcomponent_rename_tzids(icalcomponent *comp, icalarray *rename_table)
2218 {
2219     icalcomponent_foreach_tzid(comp, icalcomponent_rename_tzids_callback, rename_table);
2220 }
2221 
icalcomponent_rename_tzids_callback(icalparameter * param,void * data)2222 static void icalcomponent_rename_tzids_callback(icalparameter *param, void *data)
2223 {
2224     icalarray *rename_table = data;
2225     const char *tzid;
2226     size_t i;
2227 
2228     tzid = icalparameter_get_tzid(param);
2229     if (!tzid)
2230         return;
2231 
2232     /* Step through the rename table to see if the current TZID matches
2233        any of the ones we want to rename. */
2234     for (i = 0; i < rename_table->num_elements - 1; i += 2) {
2235         if (!strcmp(tzid, icalarray_element_at(rename_table, i))) {
2236             icalparameter_set_tzid(param, icalarray_element_at(rename_table, i + 1));
2237             break;
2238         }
2239     }
2240 }
2241 
2242 /**
2243  * Calls the given function for each TZID parameter found in the component.
2244  */
icalcomponent_foreach_tzid(icalcomponent * comp,void (* callback)(icalparameter * param,void * data),void * callback_data)2245 void icalcomponent_foreach_tzid(icalcomponent *comp,
2246                                 void (*callback) (icalparameter *param, void *data),
2247                                 void *callback_data)
2248 {
2249     icalproperty *prop;
2250     icalproperty_kind kind;
2251     icalparameter *param;
2252     icalcomponent *subcomp;
2253 
2254     /* First look for any TZID parameters used in this component itself. */
2255     prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
2256     while (prop) {
2257         kind = icalproperty_isa(prop);
2258 
2259         /* These are the only properties that can have a TZID. Note that
2260            COMPLETED, CREATED, DTSTAMP & LASTMODIFIED must be in UTC. */
2261         if (kind == ICAL_DTSTART_PROPERTY ||
2262             kind == ICAL_DTEND_PROPERTY ||
2263             kind == ICAL_DUE_PROPERTY ||
2264             kind == ICAL_EXDATE_PROPERTY ||
2265             kind == ICAL_RDATE_PROPERTY) {
2266             param = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER);
2267             if (param)
2268                 (*callback) (param, callback_data);
2269         }
2270 
2271         prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY);
2272     }
2273 
2274     /* Now recursively check child components. */
2275     subcomp = icalcomponent_get_first_component(comp, ICAL_ANY_COMPONENT);
2276     while (subcomp) {
2277         icalcomponent_foreach_tzid(subcomp, callback, callback_data);
2278         subcomp = icalcomponent_get_next_component(comp, ICAL_ANY_COMPONENT);
2279     }
2280 }
2281 
2282 /**
2283  *  Returns the icaltimezone from the component corresponding to the given
2284  *  TZID, or NULL if the component does not have a corresponding VTIMEZONE.
2285  */
icalcomponent_get_timezone(icalcomponent * comp,const char * tzid)2286 icaltimezone *icalcomponent_get_timezone(icalcomponent *comp, const char *tzid)
2287 {
2288     icaltimezone *zone;
2289     size_t lower, middle, upper;
2290     int cmp;
2291     const char *zone_tzid;
2292 
2293     if (!comp->timezones)
2294         return NULL;
2295 
2296     /* Sort the array if necessary (by the TZID string). */
2297     if (!comp->timezones_sorted) {
2298         icalarray_sort(comp->timezones, icalcomponent_compare_timezone_fn);
2299         comp->timezones_sorted = 1;
2300     }
2301 
2302     /* Do a simple binary search. */
2303     lower = middle = 0;
2304     upper = comp->timezones->num_elements;
2305 
2306     while (lower < upper) {
2307         middle = (lower + upper) >> 1;
2308         zone = icalarray_element_at(comp->timezones, middle);
2309         zone_tzid = icaltimezone_get_tzid(zone);
2310         if (zone_tzid != NULL) {
2311             cmp = strcmp(tzid, zone_tzid);
2312             if (cmp == 0) {
2313                 return zone;
2314             } else if (cmp < 0) {
2315                 upper = middle;
2316             } else {
2317                 lower = middle + 1;
2318             }
2319         }
2320     }
2321 
2322     return NULL;
2323 }
2324 
2325 /**
2326  * A function to compare 2 icaltimezone elements, used for qsort().
2327  */
icalcomponent_compare_timezone_fn(const void * elem1,const void * elem2)2328 static int icalcomponent_compare_timezone_fn(const void *elem1, const void *elem2)
2329 {
2330     icaltimezone *zone1, *zone2;
2331     const char *zone1_tzid, *zone2_tzid;
2332 
2333     zone1 = (icaltimezone *) elem1;
2334     zone2 = (icaltimezone *) elem2;
2335 
2336     zone1_tzid = icaltimezone_get_tzid(zone1);
2337     zone2_tzid = icaltimezone_get_tzid(zone2);
2338 
2339     return strcmp(zone1_tzid, zone2_tzid);
2340 }
2341 
2342 /**
2343  * Compares 2 VTIMEZONE components to see if they match, ignoring their TZIDs.
2344  * It returns 1 if they match, 0 if they don't, or -1 on error.
2345  */
icalcomponent_compare_vtimezones(icalcomponent * vtimezone1,icalcomponent * vtimezone2)2346 static int icalcomponent_compare_vtimezones(icalcomponent *vtimezone1, icalcomponent *vtimezone2)
2347 {
2348     icalproperty *prop1, *prop2;
2349     const char *tzid1, *tzid2;
2350     char *tzid2_copy, *string1, *string2;
2351     int cmp;
2352 
2353     /* Get the TZID property of the first VTIMEZONE. */
2354     prop1 = icalcomponent_get_first_property(vtimezone1, ICAL_TZID_PROPERTY);
2355     if (!prop1)
2356         return -1;
2357 
2358     tzid1 = icalproperty_get_tzid(prop1);
2359     if (!tzid1)
2360         return -1;
2361 
2362     /* Get the TZID property of the second VTIMEZONE. */
2363     prop2 = icalcomponent_get_first_property(vtimezone2, ICAL_TZID_PROPERTY);
2364     if (!prop2)
2365         return -1;
2366 
2367     tzid2 = icalproperty_get_tzid(prop2);
2368     if (!tzid2)
2369         return -1;
2370 
2371     /* Copy the second TZID, and set the property to the same as the first
2372        TZID, since we don't care if these match of not. */
2373     tzid2_copy = strdup(tzid2);
2374     if (!tzid2_copy) {
2375         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
2376         return 0;
2377     }
2378 
2379     icalproperty_set_tzid(prop2, tzid1);
2380 
2381     /* Now convert both VTIMEZONEs to strings and compare them. */
2382     string1 = icalcomponent_as_ical_string_r(vtimezone1);
2383     if (!string1) {
2384         free(tzid2_copy);
2385         return -1;
2386     }
2387 
2388     string2 = icalcomponent_as_ical_string_r(vtimezone2);
2389     if (!string2) {
2390         free(string1);
2391         free(tzid2_copy);
2392         return -1;
2393     }
2394 
2395     cmp = strcmp(string1, string2);
2396 
2397     free(string1);
2398     free(string2);
2399 
2400     /* Now reset the second TZID. */
2401     icalproperty_set_tzid(prop2, tzid2_copy);
2402     free(tzid2_copy);
2403 
2404     return (cmp == 0) ? 1 : 0;
2405 }
2406 
2407 /**
2408  * @brief set the RELCALID property of a component.
2409  *
2410  * @param comp    Valid calendar component.
2411  * @param v       Relcalid URL value
2412  */
2413 
icalcomponent_set_relcalid(icalcomponent * comp,const char * v)2414 void icalcomponent_set_relcalid(icalcomponent *comp, const char *v)
2415 {
2416     ICALSETUPSET(ICAL_RELCALID_PROPERTY);
2417 
2418     if (prop == 0) {
2419         prop = icalproperty_new_relcalid(v);
2420         icalcomponent_add_property(inner, prop);
2421     }
2422 
2423     icalproperty_set_relcalid(prop, v);
2424 }
2425 
2426 /**
2427  * @brief get the RELCALID property of a component.
2428  *
2429  * @param comp    Valid calendar component.
2430  */
2431 
icalcomponent_get_relcalid(icalcomponent * comp)2432 const char *icalcomponent_get_relcalid(icalcomponent *comp)
2433 {
2434     icalcomponent *inner;
2435     icalproperty *prop;
2436 
2437     icalerror_check_arg_rz(comp != 0, "comp");
2438 
2439     inner = icalcomponent_get_inner(comp);
2440 
2441     if (inner == 0) {
2442         return 0;
2443     }
2444 
2445     prop = icalcomponent_get_first_property(inner, ICAL_RELCALID_PROPERTY);
2446 
2447     if (prop == 0) {
2448         return 0;
2449     }
2450 
2451     return icalproperty_get_relcalid(prop);
2452 }
2453 
2454 /** @brief Return the time a TODO task is DUE.
2455  *
2456  *  @param comp Valid calendar component.
2457  *
2458  *  Uses the DUE: property if it exists, otherwise we calculate the DUE
2459  *  value by adding the task's duration to the DTSTART time
2460  */
2461 
icalcomponent_get_due(icalcomponent * comp)2462 struct icaltimetype icalcomponent_get_due(icalcomponent *comp)
2463 {
2464     icalcomponent *inner = icalcomponent_get_inner(comp);
2465 
2466     icalproperty *due_prop = icalcomponent_get_first_property(inner, ICAL_DUE_PROPERTY);
2467 
2468     icalproperty *dur_prop = icalcomponent_get_first_property(inner, ICAL_DURATION_PROPERTY);
2469 
2470     if (due_prop != 0) {
2471         return icalproperty_get_datetime_with_component(due_prop, comp);
2472     } else if (dur_prop != 0) {
2473 
2474         struct icaltimetype start = icalcomponent_get_dtstart(inner);
2475         struct icaldurationtype duration = icalproperty_get_duration(dur_prop);
2476 
2477         struct icaltimetype due = icaltime_add(start, duration);
2478 
2479         return due;
2480     }
2481     return icaltime_null_time();
2482 }
2483 
2484 /** @brief Set the due date of a VTODO task.
2485  *
2486  *  @param comp Valid VTODO component.
2487  *  @param v    Valid due date time.
2488  *
2489  *  - If no duration or due properties then set the DUE property.
2490  *  - If a DUE property is already set, then reset it to the value v.
2491  *  - If a DURATION property is already set, then calculate the new
2492  *    duration based on the supplied value of v.
2493  */
2494 
icalcomponent_set_due(icalcomponent * comp,struct icaltimetype v)2495 void icalcomponent_set_due(icalcomponent *comp, struct icaltimetype v)
2496 {
2497     const char *tzid;
2498 
2499     icalcomponent *inner = icalcomponent_get_inner(comp);
2500 
2501     icalproperty *due_prop = icalcomponent_get_first_property(inner, ICAL_DUE_PROPERTY);
2502 
2503     icalproperty *dur_prop = icalcomponent_get_first_property(inner, ICAL_DURATION_PROPERTY);
2504 
2505     if (due_prop == 0 && dur_prop == 0) {
2506         due_prop = icalproperty_new_due(v);
2507         icalcomponent_add_property(inner, due_prop);
2508     } else if (due_prop != 0) {
2509         icalproperty_set_due(due_prop, v);
2510     } else if (dur_prop != 0) {
2511         struct icaltimetype start = icalcomponent_get_dtstart(inner);
2512 
2513         struct icaltimetype due = icalcomponent_get_due(inner);
2514 
2515         struct icaldurationtype dur = icaltime_subtract(due, start);
2516 
2517         icalproperty_set_duration(dur_prop, dur);
2518     }
2519 
2520     if (due_prop && (tzid = icaltime_get_tzid(v)) != NULL && !icaltime_is_utc(v)) {
2521         icalproperty_set_parameter(due_prop, icalparameter_new_tzid(tzid));
2522     }
2523 }
2524 
prop_compare(void * a,void * b)2525 static int prop_compare(void *a, void *b)
2526 {
2527     icalproperty *p1 = (icalproperty*) a;
2528     icalproperty *p2 = (icalproperty*) b;
2529     icalproperty_kind k1 = icalproperty_isa(p1);
2530     icalproperty_kind k2 = icalproperty_isa(p2);
2531     int r = k1 - k2;
2532 
2533     if (r == 0) {
2534         if (k1 == ICAL_X_PROPERTY) {
2535             r = strcmp(icalproperty_get_x_name(p1),
2536                        icalproperty_get_x_name(p2));
2537         }
2538 
2539         if (r == 0) {
2540             r = strcmp(icalproperty_get_value_as_string(p1),
2541                        icalproperty_get_value_as_string(p2));
2542         }
2543     }
2544 
2545     return r;
2546 }
2547 
comp_compare(void * a,void * b)2548 static int comp_compare(void *a, void *b)
2549 {
2550     icalcomponent *c1 = (icalcomponent*) a;
2551     icalcomponent *c2 = (icalcomponent*) b;
2552     icalcomponent_kind k1 = icalcomponent_isa(c1);
2553     icalcomponent_kind k2 = icalcomponent_isa(c2);
2554     int r = k1 - k2;
2555 
2556     if (r == 0) {
2557         if (k1 == ICAL_X_COMPONENT) {
2558             r = strcmp(c1->x_name, c2->x_name);
2559         }
2560 
2561         if (r == 0) {
2562             const char *u1 = icalcomponent_get_uid(c1);
2563             const char *u2 = icalcomponent_get_uid(c2);
2564 
2565             if (u1 && u2) {
2566                 r = strcmp(u1, u2);
2567 
2568                 if (r == 0) {
2569                     r = icaltime_compare(icalcomponent_get_recurrenceid(c1),
2570                                          icalcomponent_get_recurrenceid(c2));
2571                 }
2572             } else {
2573                 icalproperty *p1, *p2;
2574 
2575                 switch (k1) {
2576                 case ICAL_VALARM_COMPONENT:
2577                     p1 = icalcomponent_get_first_property(c1,
2578                                                           ICAL_TRIGGER_PROPERTY);
2579                     p2 = icalcomponent_get_first_property(c2,
2580                                                           ICAL_TRIGGER_PROPERTY);
2581                     r = strcmp(icalproperty_get_value_as_string(p1),
2582                                icalproperty_get_value_as_string(p2));
2583 
2584                     if (r == 0) {
2585                         p1 = icalcomponent_get_first_property(c1,
2586                                                               ICAL_ACTION_PROPERTY);
2587                         p2 = icalcomponent_get_first_property(c2,
2588                                                               ICAL_ACTION_PROPERTY);
2589                         r = strcmp(icalproperty_get_value_as_string(p1),
2590                                    icalproperty_get_value_as_string(p2));
2591                     }
2592                     break;
2593 
2594                 case ICAL_VTIMEZONE_COMPONENT:
2595                     p1 = icalcomponent_get_first_property(c1,
2596                                                           ICAL_TZID_PROPERTY);
2597                     p2 = icalcomponent_get_first_property(c2,
2598                                                           ICAL_TZID_PROPERTY);
2599                     r = strcmp(icalproperty_get_value_as_string(p1),
2600                                icalproperty_get_value_as_string(p2));
2601                     break;
2602 
2603                 case ICAL_XSTANDARD_COMPONENT:
2604                 case ICAL_XDAYLIGHT_COMPONENT:
2605                     p1 = icalcomponent_get_first_property(c1,
2606                                                           ICAL_DTSTART_PROPERTY);
2607                     p2 = icalcomponent_get_first_property(c2,
2608                                                           ICAL_DTSTART_PROPERTY);
2609                     r = strcmp(icalproperty_get_value_as_string(p1),
2610                                icalproperty_get_value_as_string(p2));
2611                     break;
2612 
2613                 case ICAL_VVOTER_COMPONENT:
2614                     p1 = icalcomponent_get_first_property(c1,
2615                                                           ICAL_VOTER_PROPERTY);
2616                     p2 = icalcomponent_get_first_property(c2,
2617                                                           ICAL_VOTER_PROPERTY);
2618                     r = strcmp(icalproperty_get_value_as_string(p1),
2619                                icalproperty_get_value_as_string(p2));
2620                     break;
2621 
2622                 case ICAL_XVOTE_COMPONENT:
2623                     p1 = icalcomponent_get_first_property(c1,
2624                                                           ICAL_POLLITEMID_PROPERTY);
2625                     p2 = icalcomponent_get_first_property(c2,
2626                                                           ICAL_POLLITEMID_PROPERTY);
2627                     r = strcmp(icalproperty_get_value_as_string(p1),
2628                                icalproperty_get_value_as_string(p2));
2629                     break;
2630 
2631                 default:
2632                     /* XXX  Anything better? */
2633                     r = icaltime_compare(icalcomponent_get_dtstamp(c1),
2634                                          icalcomponent_get_dtstamp(c2));
2635                     break;
2636                 }
2637             }
2638         }
2639     /* Always sort VTIMEZONEs first */
2640     } else if (k1 == ICAL_VTIMEZONE_COMPONENT) {
2641         return -1;
2642     } else if (k2 == ICAL_VTIMEZONE_COMPONENT) {
2643         return 1;
2644     }
2645 
2646     return r;
2647 }
2648 
icalcomponent_normalize(icalcomponent * comp)2649 void icalcomponent_normalize(icalcomponent *comp)
2650 {
2651     pvl_list sorted_props = pvl_newlist();
2652     pvl_list sorted_comps = pvl_newlist();
2653     icalproperty *prop;
2654     icalcomponent *sub;
2655 
2656     /* Normalize properties into sorted list */
2657     while ((prop = pvl_pop(comp->properties)) != 0) {
2658         int nparams, remove = 0;
2659 
2660         icalproperty_normalize(prop);
2661 
2662         nparams = icalproperty_count_parameters(prop);
2663 
2664         /* Remove unparameterized properties having default values */
2665         if (nparams == 0) {
2666             switch (icalproperty_isa(prop)) {
2667             case ICAL_CALSCALE_PROPERTY:
2668                 if (strcmp("GREGORIAN", icalproperty_get_calscale(prop)) == 0) {
2669                     remove = 1;
2670                 }
2671                 break;
2672 
2673             case ICAL_CLASS_PROPERTY:
2674                 if (icalproperty_get_class(prop) == ICAL_CLASS_PUBLIC) {
2675                     remove = 1;
2676                 }
2677                 break;
2678 
2679             case ICAL_PRIORITY_PROPERTY:
2680                 if (icalproperty_get_priority(prop) == 0) {
2681                     remove = 1;
2682                 }
2683                 break;
2684 
2685             case ICAL_TRANSP_PROPERTY:
2686                 if (icalproperty_get_transp(prop) == ICAL_TRANSP_OPAQUE) {
2687                     remove = 1;
2688                 }
2689                 break;
2690 
2691             case ICAL_REPEAT_PROPERTY:
2692                 if (icalproperty_get_repeat(prop) == 0) {
2693                     remove = 1;
2694                 }
2695                 break;
2696 
2697             case ICAL_SEQUENCE_PROPERTY:
2698                 if (icalproperty_get_sequence(prop) == 0) {
2699                     remove = 1;
2700                 }
2701                 break;
2702 
2703             default:
2704                 break;
2705             }
2706         }
2707 
2708         if (remove) {
2709             icalproperty_set_parent(prop, 0); // MUST NOT have a parent to free
2710             icalproperty_free(prop);
2711         } else {
2712             pvl_insert_ordered(sorted_props, prop_compare, prop);
2713         }
2714     }
2715 
2716     pvl_free(comp->properties);
2717     comp->properties = sorted_props;
2718 
2719     /* Normalize sub-components into sorted list */
2720     while ((sub = pvl_pop(comp->components)) != 0) {
2721         icalcomponent_normalize(sub);
2722         pvl_insert_ordered(sorted_comps, comp_compare, sub);
2723     }
2724 
2725     pvl_free(comp->components);
2726     comp->components = sorted_comps;
2727 }
2728