$darkmode
Libical API Documentation 4.0 STABLE VERSION Visit the v3.0 documentation
vcardcomponent.c
Go to the documentation of this file.
1 /*======================================================================
2  FILE: vcardcomponent.c
3  CREATOR: Ken Murchison 24 Aug 2022 <murch@fastmailteam.com>
4 
5  SPDX-FileCopyrightText: 2022, Fastmail Pty. Ltd. (https://fastmail.com)
6  SPDX-License-Identifier: LGPL-2.1-only OR MPL-2.0
7  ======================================================================*/
8 
14 #ifdef HAVE_CONFIG_H
15 #include <config.h>
16 #endif
17 
18 #include "vcardcomponent.h"
19 #include "icalpvl_p.h"
20 #include "vcardparser.h"
21 #include "vcardproperty_p.h"
22 #include "vcardrestriction.h"
23 #include "vcardvalue.h"
24 #include "icalerror_p.h"
25 #include "icalerror.h"
26 #include "icalmemory.h"
27 #include "icaltypes_p.h"
28 
29 #include <assert.h>
30 #include <stdlib.h>
31 #include <limits.h>
32 #include <ctype.h>
33 
34 struct vcardcomponent_impl {
35  icalstructuretype id;
36  vcardcomponent_kind kind;
37  vcardproperty *versionp;
38  icalpvl_list properties;
39  icalpvl_elem property_iterator;
40  icalpvl_list components;
41  icalpvl_elem component_iterator;
42  struct vcardcomponent_impl *parent;
43 };
44 
45 static void vcardcomponent_add_children(vcardcomponent *impl, va_list args)
46 {
47  void *vp;
48 
49  while ((vp = va_arg(args, void *)) != 0) {
50  icalassert(vcardproperty_isa_property(vp) != 0);
51 
52  vcardcomponent_add_property(impl, (vcardproperty *)vp);
53  }
54 }
55 
56 static vcardcomponent *vcardcomponent_new_impl(vcardcomponent_kind kind)
57 {
58  vcardcomponent *comp;
59 
60  if (!vcardcomponent_kind_is_valid(kind)) {
61  return NULL;
62  }
63 
64  if ((comp = (vcardcomponent *)icalmemory_new_buffer(sizeof(vcardcomponent))) == 0) {
66  return 0;
67  }
68 
69  memset(comp, 0, sizeof(vcardcomponent));
70 
71  comp->id = ICAL_STRUCTURE_TYPE_COMPONENT;
72  comp->kind = kind;
73  comp->properties = icalpvl_newlist();
74  comp->components = icalpvl_newlist();
75 
76  return comp;
77 }
78 
79 vcardcomponent *vcardcomponent_new(vcardcomponent_kind kind)
80 {
81  return vcardcomponent_new_impl(kind);
82 }
83 
84 #if defined(__clang__)
85 #pragma clang diagnostic push
86 #pragma clang diagnostic ignored "-Wvarargs"
87 #endif
88 vcardcomponent *vcardcomponent_vanew(vcardcomponent_kind kind, ...)
89 {
90  va_list args;
91 
92  vcardcomponent *impl = vcardcomponent_new_impl(kind);
93 
94  if (impl == 0) {
95  return 0;
96  }
97 
98  va_start(args, kind);
99  vcardcomponent_add_children(impl, args);
100  va_end(args);
101 
102  return impl;
103 }
104 #if defined(__clang__)
105 #pragma clang diagnostic pop
106 #endif
107 
108 vcardcomponent *vcardcomponent_new_from_string(const char *str)
109 {
110  return vcardparser_parse_string(str);
111 }
112 
113 vcardcomponent *vcardcomponent_clone(const vcardcomponent *old)
114 {
115  vcardcomponent *clone;
116  const vcardproperty *p;
117  icalpvl_elem itr;
118 
119  icalerror_check_arg_rz((old != 0), "component");
120 
121  clone = vcardcomponent_new_impl(old->kind);
122 
123  if (clone == 0) {
124  return 0;
125  }
126 
127  for (itr = icalpvl_head(old->properties); itr != 0; itr = icalpvl_next(itr)) {
128  p = (vcardproperty *)icalpvl_data(itr);
129  vcardcomponent_add_property(clone, vcardproperty_clone(p));
130  }
131 
132  return clone;
133 }
134 
135 void vcardcomponent_free(vcardcomponent *c)
136 {
137  vcardcomponent *comp;
138 
139  icalerror_check_arg_rv((c != 0), "component");
140 
141  if (c->parent != 0) {
142  return;
143  }
144 
145  if (c->properties != 0) {
146  vcardproperty *prop;
147  while ((prop = icalpvl_pop(c->properties)) != 0) {
148  vcardproperty_set_parent(prop, 0);
149  vcardproperty_free(prop);
150  }
151  icalpvl_free(c->properties);
152  }
153 
154  while ((comp = icalpvl_data(icalpvl_head(c->components))) != 0) {
155  vcardcomponent_remove_component(c, comp);
156  vcardcomponent_free(comp);
157  }
158 
159  icalpvl_free(c->components);
160 
161  c->kind = VCARD_NO_COMPONENT;
162  c->properties = 0;
163  c->property_iterator = 0;
164  c->components = 0;
165  c->component_iterator = 0;
166  c->id = ICAL_STRUCTURE_TYPE_COMPONENT_EMPTY;
167 
169 }
170 
171 char *vcardcomponent_as_vcard_string(vcardcomponent *comp)
172 {
173  char *buf;
174 
175  buf = vcardcomponent_as_vcard_string_r(comp);
176  if (buf) {
178  }
179  return buf;
180 }
181 
182 char *vcardcomponent_as_vcard_string_r(vcardcomponent *comp)
183 {
184  char *buf;
185  char *tmp_buf;
186  size_t buf_size = 1024;
187  char *buf_ptr = 0;
188  icalpvl_elem itr;
189 
190  /* RFC6350 explicitly says that the newline is *ALWAYS* a \r\n (CRLF)!!!! */
191  const char newline[] = "\r\n";
192 
193  vcardcomponent *c;
194  vcardproperty *p;
195  vcardcomponent_kind kind = vcardcomponent_isa(comp);
196 
197  const char *kind_string = NULL;
198 
199  icalerror_check_arg_rz((comp != 0), "component");
200  icalerror_check_arg_rz((kind != VCARD_NO_COMPONENT),
201  "component kind is VCARD_NO_COMPONENT");
202 
203  kind_string = vcardcomponent_kind_to_string(kind);
204 
205  icalerror_check_arg_rz((kind_string != 0), "Unknown kind of component");
206 
207  buf = icalmemory_new_buffer(buf_size);
208  if (buf == NULL) {
209  return NULL;
210  }
211 
212  buf_ptr = buf;
213 
214  if (kind != VCARD_XROOT_COMPONENT) {
215  icalmemory_append_string(&buf, &buf_ptr, &buf_size, "BEGIN:");
216  icalmemory_append_string(&buf, &buf_ptr, &buf_size, kind_string);
217  icalmemory_append_string(&buf, &buf_ptr, &buf_size, newline);
218 
219  for (itr = icalpvl_head(comp->properties); itr != 0; itr = icalpvl_next(itr)) {
220  p = (vcardproperty *)icalpvl_data(itr);
221 
222  icalerror_assert((p != 0), "Got a null property");
223  tmp_buf = vcardproperty_as_vcard_string_r(p);
224 
225  icalmemory_append_string(&buf, &buf_ptr, &buf_size, tmp_buf);
226  icalmemory_free_buffer(tmp_buf);
227  }
228  }
229 
230  for (itr = icalpvl_head(comp->components); itr != 0; itr = icalpvl_next(itr)) {
231  c = (vcardcomponent *)icalpvl_data(itr);
232 
233  tmp_buf = vcardcomponent_as_vcard_string_r(c);
234  if (tmp_buf != NULL) {
235  icalmemory_append_string(&buf, &buf_ptr, &buf_size, tmp_buf);
236  icalmemory_free_buffer(tmp_buf);
237  }
238  }
239 
240  if (kind != VCARD_XROOT_COMPONENT) {
241  icalmemory_append_string(&buf, &buf_ptr, &buf_size, "END:");
242  icalmemory_append_string(&buf, &buf_ptr, &buf_size, kind_string);
243  icalmemory_append_string(&buf, &buf_ptr, &buf_size, newline);
244  }
245 
246  return buf;
247 }
248 
249 bool vcardcomponent_is_valid(const vcardcomponent *component)
250 {
251  if (component) {
252  if ((component->id == ICAL_STRUCTURE_TYPE_COMPONENT) && (component->kind != VCARD_NO_COMPONENT)) {
253  return true;
254  }
255  }
256  return false;
257 }
258 
259 vcardcomponent_kind vcardcomponent_isa(const vcardcomponent *component)
260 {
261  icalerror_check_arg_rx((component != 0), "component", VCARD_NO_COMPONENT);
262 
263  return component->kind;
264 }
265 
266 bool vcardcomponent_isa_component(const void *component)
267 {
268  const vcardcomponent *impl = component;
269 
270  icalerror_check_arg_rz((component != 0), "component");
271 
272  return (impl->id == ICAL_STRUCTURE_TYPE_COMPONENT);
273 }
274 
275 void vcardcomponent_add_property(vcardcomponent *comp, vcardproperty *property)
276 {
277  icalerror_check_arg_rv((comp != 0), "component");
278  icalerror_check_arg_rv((property != 0), "property");
279 
280  icalerror_assert((!vcardproperty_get_parent(property)),
281  "The property has already been added to a vcard. "
282  "Remove the property with vcardcomponent_remove_property "
283  "before calling vcardcomponent_add_property");
284 
285  vcardproperty_set_parent(property, comp);
286 
287  icalpvl_push(comp->properties, property);
288 
289  if (vcardproperty_isa(property) == VCARD_VERSION_PROPERTY) {
290  comp->versionp = property;
291  }
292 }
293 
294 void vcardcomponent_remove_property(vcardcomponent *comp, vcardproperty *property)
295 {
296  icalpvl_elem itr, next_itr;
297 
298  icalerror_check_arg_rv((comp != 0), "component");
299  icalerror_check_arg_rv((property != 0), "property");
300 
301  if (vcardproperty_get_parent(property) == 0) {
302  return;
303  }
304 
305  if (vcardproperty_isa(property) == VCARD_VERSION_PROPERTY) {
306  comp->versionp = 0;
307  }
308 
309  for (itr = icalpvl_head(comp->properties); itr != 0; itr = next_itr) {
310  next_itr = icalpvl_next(itr);
311 
312  if (icalpvl_data(itr) == (void *)property) {
313  if (comp->property_iterator == itr) {
314  comp->property_iterator = icalpvl_next(itr);
315  }
316 
317  (void)icalpvl_remove(comp->properties, itr);
318  vcardproperty_set_parent(property, 0);
319  }
320  }
321 }
322 
323 int vcardcomponent_count_properties(vcardcomponent *comp,
324  vcardproperty_kind kind,
325  int ignore_alts)
326 {
327  int count = 0;
328  icalpvl_elem itr;
329  vcardstrarray *altids = NULL;
330 
331  icalerror_check_arg_rz((comp != 0), "component");
332 
333  if (ignore_alts) {
334  altids = vcardstrarray_new(2);
335  }
336 
337  for (itr = icalpvl_head(comp->properties); itr != 0; itr = icalpvl_next(itr)) {
338  vcardproperty *prop = (vcardproperty *)icalpvl_data(itr);
339 
340  if (kind == VCARD_ANY_PROPERTY || kind == vcardproperty_isa(prop)) {
341  if (ignore_alts) {
342  /* Like-properties having the same ALTID only get counted once */
343  vcardparameter *param =
344  vcardproperty_get_first_parameter(prop,
345  VCARD_ALTID_PARAMETER);
346  if (param) {
347  const char *altid = vcardparameter_get_altid(param);
348 
349  if (vcardstrarray_find(altids, altid) >= vcardstrarray_size(altids)) {
350  continue;
351  }
352 
353  vcardstrarray_append(altids, altid);
354  }
355  }
356  count++;
357  }
358  }
359 
360  if (ignore_alts) {
361  vcardstrarray_free(altids);
362  }
363 
364  return count;
365 }
366 
367 vcardproperty *vcardcomponent_get_current_property(vcardcomponent *comp)
368 {
369  icalerror_check_arg_rz((comp != 0), "card");
370 
371  if (comp->property_iterator == 0) {
372  return 0;
373  }
374 
375  return (vcardproperty *)icalpvl_data(comp->property_iterator);
376 }
377 
378 vcardproperty *vcardcomponent_get_first_property(vcardcomponent *c,
379  vcardproperty_kind kind)
380 {
381  icalerror_check_arg_rz((c != 0), "card");
382 
383  for (c->property_iterator = icalpvl_head(c->properties);
384  c->property_iterator != 0;
385  c->property_iterator = icalpvl_next(c->property_iterator)) {
386  vcardproperty *p = (vcardproperty *)icalpvl_data(c->property_iterator);
387 
388  if (vcardproperty_isa(p) == kind || kind == VCARD_ANY_PROPERTY) {
389  return p;
390  }
391  }
392  return 0;
393 }
394 
395 vcardproperty *vcardcomponent_get_next_property(vcardcomponent *c,
396  vcardproperty_kind kind)
397 {
398  icalerror_check_arg_rz((c != 0), "card");
399 
400  if (c->property_iterator == 0) {
401  return 0;
402  }
403 
404  for (c->property_iterator = icalpvl_next(c->property_iterator);
405  c->property_iterator != 0;
406  c->property_iterator = icalpvl_next(c->property_iterator)) {
407  vcardproperty *p = (vcardproperty *)icalpvl_data(c->property_iterator);
408 
409  if (vcardproperty_isa(p) == kind || kind == VCARD_ANY_PROPERTY) {
410  return p;
411  }
412  }
413 
414  return 0;
415 }
416 
417 vcardproperty **vcardcomponent_get_properties(vcardcomponent *comp,
418  vcardproperty_kind kind);
419 
420 void vcardcomponent_add_component(vcardcomponent *parent, vcardcomponent *child)
421 {
422  icalerror_check_arg_rv((parent != 0), "parent");
423  icalerror_check_arg_rv((child != 0), "child");
424 
425  if (child->parent != 0) {
427  }
428 
429  child->parent = parent;
430 
431  icalpvl_push(parent->components, child);
432 }
433 
434 void vcardcomponent_remove_component(vcardcomponent *parent,
435  vcardcomponent *child)
436 {
437  icalpvl_elem itr, next_itr;
438 
439  icalerror_check_arg_rv((parent != 0), "parent");
440  icalerror_check_arg_rv((child != 0), "child");
441 
442  for (itr = icalpvl_head(parent->components); itr != 0; itr = next_itr) {
443  next_itr = icalpvl_next(itr);
444 
445  if (icalpvl_data(itr) == (void *)child) {
446  if (parent->component_iterator == itr) {
447  /* Don't let the current iterator become invalid */
448 
449  /* HACK. The semantics for this are troubling. */
450  parent->component_iterator = icalpvl_next(parent->component_iterator);
451  }
452  (void)icalpvl_remove(parent->components, itr);
453  child->parent = 0;
454  break;
455  }
456  }
457 }
458 
459 int vcardcomponent_count_components(vcardcomponent *component,
460  vcardcomponent_kind kind)
461 {
462  int count = 0;
463  icalpvl_elem itr;
464 
465  icalerror_check_arg_rz((component != 0), "component");
466 
467  for (itr = icalpvl_head(component->components); itr != 0; itr = icalpvl_next(itr)) {
468  if (kind == VCARD_ANY_COMPONENT ||
469  kind == vcardcomponent_isa((vcardcomponent *)icalpvl_data(itr))) {
470  count++;
471  }
472  }
473 
474  return count;
475 }
476 
477 vcardcomponent *vcardcomponent_get_current_component(vcardcomponent *component)
478 {
479  icalerror_check_arg_rz((component != 0), "component");
480 
481  if (component->component_iterator == 0) {
482  return 0;
483  }
484 
485  return (vcardcomponent *)icalpvl_data(component->component_iterator);
486 }
487 
488 vcardcomponent *vcardcomponent_get_first_component(vcardcomponent *c,
489  vcardcomponent_kind kind)
490 {
491  icalerror_check_arg_rz((c != 0), "component");
492 
493  for (c->component_iterator = icalpvl_head(c->components);
494  c->component_iterator != 0;
495  c->component_iterator = icalpvl_next(c->component_iterator)) {
496  vcardcomponent *p = (vcardcomponent *)icalpvl_data(c->component_iterator);
497 
498  if (vcardcomponent_isa(p) == kind || kind == VCARD_ANY_COMPONENT) {
499  return p;
500  }
501  }
502 
503  return 0;
504 }
505 
506 vcardcomponent *vcardcomponent_get_next_component(vcardcomponent *c,
507  vcardcomponent_kind kind)
508 {
509  icalerror_check_arg_rz((c != 0), "component");
510 
511  if (c->component_iterator == 0) {
512  return 0;
513  }
514 
515  for (c->component_iterator = icalpvl_next(c->component_iterator);
516  c->component_iterator != 0;
517  c->component_iterator = icalpvl_next(c->component_iterator)) {
518  vcardcomponent *p = (vcardcomponent *)icalpvl_data(c->component_iterator);
519 
520  if (vcardcomponent_isa(p) == kind || kind == VCARD_ANY_COMPONENT) {
521  return p;
522  }
523  }
524 
525  return 0;
526 }
527 
528 int vcardcomponent_check_restrictions(vcardcomponent *comp)
529 {
530  icalerror_check_arg_rz(comp != 0, "comp");
531  return vcardrestriction_check(comp);
532 }
533 
534 int vcardcomponent_count_errors(vcardcomponent *comp)
535 {
536  int errors = 0;
537  icalpvl_elem itr;
538 
539  icalerror_check_arg_rz((comp != 0), "card");
540 
541  for (itr = icalpvl_head(comp->properties); itr != 0; itr = icalpvl_next(itr)) {
542  const vcardproperty *p = (vcardproperty *)icalpvl_data(itr);
543  if (vcardproperty_isa(p) == VCARD_XLICERROR_PROPERTY) {
544  errors++;
545  }
546  }
547 
548  return errors;
549 }
550 
551 void vcardcomponent_strip_errors(vcardcomponent *comp)
552 {
553  icalpvl_elem itr, next_itr;
554 
555  icalerror_check_arg_rv((comp != 0), "comp");
556 
557  for (itr = icalpvl_head(comp->properties); itr != 0; itr = next_itr) {
558  vcardproperty *p = (vcardproperty *)icalpvl_data(itr);
559  next_itr = icalpvl_next(itr);
560 
561  if (vcardproperty_isa(p) == VCARD_XLICERROR_PROPERTY) {
562  vcardcomponent_remove_property(comp, p);
563  vcardproperty_free(p);
564  }
565  }
566 }
567 
568 struct vcardcomponent_kind_map {
569  vcardcomponent_kind kind;
570  char name[20];
571 };
572 
573 static const struct vcardcomponent_kind_map component_map[] = {
574  {VCARD_XROOT_COMPONENT, "XROOT"},
575  {VCARD_VCARD_COMPONENT, "VCARD"},
576 
577  /* End of list */
578  {VCARD_NO_COMPONENT, ""},
579 };
580 
581 bool vcardcomponent_kind_is_valid(const vcardcomponent_kind kind)
582 {
583  int i = 0;
584 
585  do {
586  if (component_map[i].kind == kind) {
587  return true;
588  }
589  } while (component_map[i++].kind != VCARD_NO_COMPONENT);
590 
591  return false;
592 }
593 
594 const char *vcardcomponent_kind_to_string(vcardcomponent_kind kind)
595 {
596  int i;
597 
598  for (i = 0; component_map[i].kind != VCARD_NO_COMPONENT; i++) {
599  if (component_map[i].kind == kind) {
600  return component_map[i].name;
601  }
602  }
603 
604  return 0;
605 }
606 
607 vcardcomponent_kind vcardcomponent_string_to_kind(const char *string)
608 {
609  int i;
610 
611  if (string == 0) {
612  return VCARD_NO_COMPONENT;
613  }
614 
615  for (i = 0; component_map[i].kind != VCARD_NO_COMPONENT; i++) {
616  if (strncasecmp(string, component_map[i].name, strlen(component_map[i].name)) == 0) {
617  return component_map[i].kind;
618  }
619  }
620 
621  return VCARD_NO_COMPONENT;
622 }
623 
624 static int strcmpsafe(const char *a, const char *b)
625 {
626  return strcmp((a == NULL ? "" : a),
627  (b == NULL ? "" : b));
628 }
629 
630 static int prop_compare(void *a, void *b)
631 {
632  const vcardproperty *p1 = (vcardproperty *)a;
633  const vcardproperty *p2 = (vcardproperty *)b;
634  vcardproperty_kind k1 = vcardproperty_isa(p1);
635  vcardproperty_kind k2 = vcardproperty_isa(p2);
636  int r = (int)(k1 - k2);
637 
638  if (r == 0) {
639  if (k1 == VCARD_X_PROPERTY) {
640  r = strcmp(vcardproperty_get_x_name(p1),
641  vcardproperty_get_x_name(p2));
642  }
643 
644  if (r == 0) {
645  r = strcmpsafe(vcardproperty_get_value_as_string(p1),
646  vcardproperty_get_value_as_string(p2));
647  }
648  }
649  /* Always sort VERSION first */
650  else if (k1 == VCARD_VERSION_PROPERTY) {
651  r = -1;
652  } else if (k2 == VCARD_VERSION_PROPERTY) {
653  r = 1;
654  }
655 
656  return r;
657 }
658 
659 static int prop_kind_compare(vcardproperty_kind kind,
660  vcardcomponent *c1, vcardcomponent *c2)
661 {
662  const vcardproperty *p1 = vcardcomponent_get_first_property(c1, kind);
663  const vcardproperty *p2 = vcardcomponent_get_first_property(c2, kind);
664 
665  if (p1 && p2) {
666  return strcmpsafe(vcardproperty_get_value_as_string(p1),
667  vcardproperty_get_value_as_string(p2));
668  } else if (p1) {
669  return -1;
670  } else if (p2) {
671  return 1;
672  }
673 
674  return 0;
675 }
676 
677 static int comp_compare(void *a, void *b)
678 {
679  vcardcomponent *c1 = (vcardcomponent *)a;
680  vcardcomponent *c2 = (vcardcomponent *)b;
681  vcardcomponent_kind k1 = vcardcomponent_isa(c1);
682  vcardcomponent_kind k2 = vcardcomponent_isa(c2);
683  int r = (int)(k1 - k2);
684 
685  if (r == 0) {
686  if (k1 == VCARD_VCARD_COMPONENT) {
687  vcardproperty_kind prop_kinds[] = {
688  VCARD_VERSION_PROPERTY,
689  VCARD_N_PROPERTY,
690  VCARD_FN_PROPERTY,
691  VCARD_NICKNAME_PROPERTY,
692  VCARD_ORG_PROPERTY,
693  /* XXX What else should we compare? */
694  VCARD_NO_PROPERTY};
695 
696  for (int i = 0; r == 0 && prop_kinds[i] != VCARD_NO_PROPERTY; i++) {
697  r = prop_kind_compare(prop_kinds[i], c1, c2);
698  }
699  }
700 
701  if (r == 0) {
702  r = prop_kind_compare(VCARD_UID_PROPERTY, c1, c2);
703 
704  if (r == 0) {
705  /* XXX Anything else to compare? */
706  }
707  }
708  }
709 
710  return r;
711 }
712 
713 void vcardcomponent_normalize(vcardcomponent *comp)
714 {
715  icalpvl_list sorted_props = icalpvl_newlist();
716  icalpvl_list sorted_comps = icalpvl_newlist();
717  vcardproperty *prop;
718  vcardcomponent *sub;
719 
720  /* Normalize properties into sorted list */
721  while ((prop = icalpvl_pop(comp->properties)) != 0) {
722  int nparams, remove = 0;
723 
725 
726  nparams = vcardproperty_count_parameters(prop);
727 
728  /* Remove unparameterized properties having default values */
729  if (nparams == 0) {
730  switch (vcardproperty_isa(prop)) {
731  case VCARD_KIND_PROPERTY:
732  if (vcardproperty_get_kind(prop) == VCARD_KIND_INDIVIDUAL) {
733  remove = 1;
734  }
735  break;
736 
737  default:
738  break;
739  }
740  }
741 
742  if (remove) {
743  vcardproperty_set_parent(prop, 0); // MUST NOT have a parent to free
744  vcardproperty_free(prop);
745  } else {
746  icalpvl_insert_ordered(sorted_props, prop_compare, prop);
747  }
748  }
749 
750  icalpvl_free(comp->properties);
751  comp->properties = sorted_props;
752 
753  /* Normalize sub-components into sorted list */
754  while ((sub = icalpvl_pop(comp->components)) != 0) {
756  icalpvl_insert_ordered(sorted_comps, comp_compare, sub);
757  }
758 
759  icalpvl_free(comp->components);
760  comp->components = sorted_comps;
761 }
762 
763 #define UUID_PREFIX "urn:uuid:"
764 #define UUID_PREFIX_LEN 9
765 
766 static void comp_to_v4(vcardcomponent *impl)
767 {
768  icalpvl_elem itr, next;
769 
770  for (itr = icalpvl_head(impl->properties); itr != 0; itr = next) {
771  vcardproperty *prop = (vcardproperty *)icalpvl_data(itr);
772  vcardproperty_kind pkind = vcardproperty_isa(prop);
773  vcardvalue *value = vcardproperty_get_value(prop);
774  vcardvalue_kind vkind = vcardvalue_isa(value);
775  vcardparameter *param;
776  vcardenumarray *types = NULL;
777 
778  next = icalpvl_next(itr);
779 
780  /* Remove TYPE=PREF and replace with PREF=1 (if no existing (PREF=) */
781  for (param = vcardproperty_get_first_parameter(prop,
782  VCARD_TYPE_PARAMETER);
783  param;
784  param = vcardproperty_get_next_parameter(prop,
785  VCARD_TYPE_PARAMETER)) {
786  vcardenumarray_element pref = {VCARD_TYPE_PREF, NULL};
787  size_t i;
788 
789  types = vcardparameter_get_type(param);
790  i = vcardenumarray_find(types, &pref);
791  if (i < vcardenumarray_size(types)) {
792  vcardenumarray_remove_element_at(types, i);
793  if (!vcardenumarray_size(types)) {
795  }
796 
797  param = vcardproperty_get_first_parameter(prop,
798  VCARD_PREF_PARAMETER);
799  if (!param) {
800  param = vcardparameter_new_pref(1);
801  vcardproperty_add_parameter(prop, param);
802  }
803 
804  break;
805  }
806  }
807 
808  switch (pkind) {
809  case VCARD_VERSION_PROPERTY:
810  vcardproperty_set_version(prop, VCARD_VERSION_40);
811  break;
812 
813  case VCARD_BDAY_PROPERTY:
814  case VCARD_DEATHDATE_PROPERTY:
815  case VCARD_ANNIVERSARY_PROPERTY:
816  for (param = vcardproperty_get_first_parameter(prop,
817  VCARD_X_PARAMETER);
818  param;
819  param = vcardproperty_get_next_parameter(prop,
820  VCARD_X_PARAMETER)) {
821  const char *name = vcardparameter_get_xname(param);
822 
823  /* This appears in the wild for v3 date with missing year */
824  if (name && !strcasecmp(name, "X-APPLE-OMIT-YEAR")) {
825  vcardtimetype dt = vcardproperty_get_bday(prop);
826 
827  dt.year = -1;
828  vcardproperty_set_bday(prop, dt);
830  break;
831  }
832  }
833  break;
834 
835  case VCARD_GEO_PROPERTY:
836  if (vkind != VCARD_X_VALUE) {
837  vcardgeotype geo = vcardvalue_get_geo(value);
838 
839  if (!geo.uri) {
840  /* Convert STRUCTURED value kind to geo: URI */
841  int n = snprintf(NULL, 0, "geo:%s,%s",
842  geo.coords.lat, geo.coords.lon);
843 
844  size_t size = (size_t)n + 1;
845  char *buf = icalmemory_new_buffer(size);
846  snprintf(buf, size, "geo:%s,%s",
847  geo.coords.lat, geo.coords.lon);
848 
849  geo.uri = buf;
850  geo.coords.lat[0] = '\0';
851  geo.coords.lon[0] = '\0';
852  vcardvalue_set_geo(value, geo);
854  }
855  }
856  break;
857 
858  case VCARD_KEY_PROPERTY:
859  case VCARD_LOGO_PROPERTY:
860  case VCARD_PHOTO_PROPERTY:
861  case VCARD_SOUND_PROPERTY: {
862  char *mediatype = NULL;
863  param = vcardproperty_get_first_parameter(prop,
864  VCARD_TYPE_PARAMETER);
865  if (param) {
866  types = vcardparameter_get_type(param);
867  for (size_t i = 0; i < vcardenumarray_size(types); i++) {
868  const vcardenumarray_element *type =
869  vcardenumarray_element_at(types, i);
870 
871  if (type->xvalue) {
872  /* Copy and lowercase the mediatype */
873  char *c;
874 
875  switch (pkind) {
876  case VCARD_LOGO_PROPERTY:
877  case VCARD_PHOTO_PROPERTY:
878  mediatype = icalmemory_strdup("image/");
879  break;
880  case VCARD_SOUND_PROPERTY:
881  mediatype = icalmemory_strdup("audio/");
882  break;
883  default:
884  mediatype = icalmemory_strdup("application/");
885  break;
886  }
887 
888  size_t size = strlen(mediatype);
889  char *buf_ptr = mediatype + size;
890  icalmemory_append_string(&mediatype, &buf_ptr,
891  &size, type->xvalue);
892  for (c = mediatype; *c; c++) {
893  *c = (char)tolower((int)*c);
894  }
895 
896  /* Remove this TYPE */
897  vcardenumarray_remove_element_at(types, i);
898  if (!vcardenumarray_size(types)) {
900  }
901  break;
902  }
903  }
904  }
905 
906  const char *data = vcardvalue_get_uri(value);
907  if (data && !strchr(data, ':')) {
908  /* Convert base64-encoded TEXT value kind to data: URI */
909  size_t size = strlen(data) + 7; // "data:," + NUL
910 
911  param = vcardproperty_get_first_parameter(prop,
912  VCARD_ENCODING_PARAMETER);
913  if (param) {
914  size += 7; // ";base64
915  }
916 
917  char *buf = icalmemory_new_buffer(size);
918  char *buf_ptr = buf;
919 
920  icalmemory_append_string(&buf, &buf_ptr, &size, "data:");
921  if (mediatype) {
922  icalmemory_append_string(&buf, &buf_ptr, &size, mediatype);
923  icalmemory_free_buffer(mediatype);
924  }
925  if (param) {
926  icalmemory_append_string(&buf, &buf_ptr, &size, ";base64");
928  }
929  icalmemory_append_char(&buf, &buf_ptr, &size, ',');
930  icalmemory_append_string(&buf, &buf_ptr, &size, data);
931 
932  value->kind = VCARD_URI_VALUE;
933  vcardvalue_set_uri(value, buf);
934 
936  } else if (mediatype) {
937  param = vcardparameter_new_mediatype(mediatype);
938  vcardproperty_add_parameter(prop, param);
939 
940  icalmemory_free_buffer(mediatype);
941  }
942  break;
943  }
944 
945  case VCARD_UID_PROPERTY: {
946  /* Does it look like a URI (the default)? */
947  const char *data = vcardvalue_get_text(value);
948  if (!strncasecmp(data, "urn:uuid:", 9) ||
949  !strncasecmp(data, "mailto:", 7) ||
950  !strncasecmp(data, "http://", 7) ||
951  !strncasecmp(data, "https://", 8) ||
952  !strncasecmp(data, "ldap://", 7) ||
953  !strncasecmp(data, "ldaps://", 8)) {
954  value->kind = VCARD_URI_VALUE;
956  VCARD_VALUE_PARAMETER);
957  } else {
958  /* Otherwise, treat it as TEXT */
959  value->kind = VCARD_TEXT_VALUE;
960  }
961  break;
962  }
963 
964  case VCARD_X_PROPERTY: {
965  /* Rename X-ADDRESSBOOKSERVER-KIND, X-ADDRESSBOOKSERVER-MEMBER */
966  const char *xname = vcardproperty_get_x_name(prop);
967 
968  if (!strncasecmp(xname, "X-ADDRESSBOOKSERVER-", 20)) {
969  vcardproperty_kind kind = vcardproperty_string_to_kind(xname + 20);
970  const char *valstr = vcardvalue_as_vcard_string(value);
971  vcardproperty *new;
972  char *buf = NULL, *buf_ptr;
973  size_t buf_size;
974 
975  switch (kind) {
976  case VCARD_MEMBER_PROPERTY:
977  /* strip urn:uuid: prefix and RFC6868-decode string */
978  if (!strncmp(valstr, UUID_PREFIX, UUID_PREFIX_LEN)) {
979  valstr += UUID_PREFIX_LEN;
980  }
981 
982  buf_size = strlen(valstr) + 1;
983  buf_ptr = buf = icalmemory_new_buffer(buf_size);
984  icalmemory_append_decoded_string(&buf, &buf_ptr, &buf_size,
985  valstr);
986  valstr = buf;
987 
988  _fallthrough();
989 
990  case VCARD_KIND_PROPERTY:
991  new = vcardproperty_new(kind);
992  vcardproperty_set_value_from_string(new, valstr, "NO");
993  vcardcomponent_add_property(impl, new);
994  vcardcomponent_remove_property(impl, prop);
995  vcardproperty_free(prop);
996  break;
997 
998  default:
999  break;
1000  }
1001 
1003  }
1004  break;
1005  }
1006 
1007  default:
1008  break;
1009  }
1010  }
1011 }
1012 
1013 struct pref_prop {
1014  vcardproperty *prop;
1015  int level;
1016 };
1017 
1018 static void comp_to_v3(vcardcomponent *impl)
1019 {
1020  struct pref_prop *pref_props[VCARD_NO_PROPERTY] = {0};
1022  vcardproperty_kind pkind;
1023  icalpvl_elem itr, next;
1024 
1025  for (itr = icalpvl_head(impl->properties); itr != 0; itr = next) {
1026  vcardproperty *prop = (vcardproperty *)icalpvl_data(itr);
1027  vcardparameter *val_param =
1028  vcardproperty_get_first_parameter(prop, VCARD_VALUE_PARAMETER);
1029  vcardvalue *value = vcardproperty_get_value(prop);
1030  vcardvalue_kind vkind = vcardvalue_isa(value);
1031  vcardparameter *param;
1032  char *subtype = NULL;
1033  const char *mediatype, *uri, *xname = NULL;
1034 
1035  next = icalpvl_next(itr);
1036 
1037  /* Find prop with lowest PREF= for each set of like properties */
1038  pkind = vcardproperty_isa(prop);
1039  param = vcardproperty_get_first_parameter(prop, VCARD_PREF_PARAMETER);
1040  if (param && pkind != VCARD_X_PROPERTY) {
1041  int level = vcardparameter_get_pref(param);
1042  struct pref_prop *pp = pref_props[pkind];
1043 
1044  if (!pp) {
1045  pp = icalmemory_new_buffer(sizeof(struct pref_prop));
1046  pp->prop = prop;
1047  pp->level = level;
1048  pref_props[pkind] = pp;
1049  } else if (level < pp->level) {
1050  pp->prop = prop;
1051  pp->level = level;
1052  }
1053  }
1054 
1055  /* Replace MEDIATYPE with TYPE=<subtype> */
1056  param = vcardproperty_get_first_parameter(prop,
1057  VCARD_MEDIATYPE_PARAMETER);
1058  if (param) {
1059  mediatype = vcardparameter_get_mediatype(param);
1060  subtype = (char *)strchr(mediatype, '/');
1061  if (subtype) {
1062  /* Copy and uppercase the subtype */
1063  char *c;
1064 
1065  subtype = icalmemory_strdup(subtype + 1);
1066  for (c = subtype; *c; c++) {
1067  *c = (char)toupper((int)*c);
1068  }
1069 
1070  /* Add TYPE parameter */
1071  type.val = VCARD_TYPE_NONE;
1072  type.xvalue = subtype;
1073  vcardproperty_add_type_parameter(prop, &type);
1074 
1075  icalmemory_free_buffer(subtype);
1076  }
1077 
1079  }
1080 
1081  switch (pkind) {
1082  case VCARD_VERSION_PROPERTY:
1083  vcardproperty_set_version(prop, VCARD_VERSION_30);
1084  break;
1085 
1086  case VCARD_BDAY_PROPERTY:
1087  case VCARD_DEATHDATE_PROPERTY:
1088  case VCARD_ANNIVERSARY_PROPERTY: {
1089  vcardtimetype dt = vcardproperty_get_bday(prop);
1090 
1091  if (dt.year == -1) {
1092  /* This appears in the wild for v3 date with missing year */
1093  dt.year = 1604;
1094  vcardproperty_set_parameter_from_string(prop,
1095  "X-APPLE-OMIT-YEAR",
1096  "1604");
1097  }
1098  if (dt.hour != -1) {
1099  if (dt.second == -1) {
1100  dt.second = 0;
1101  if (dt.minute == -1) {
1102  dt.minute = 0;
1103  }
1104  }
1105  }
1106 
1107  vcardproperty_set_bday(prop, dt);
1108  break;
1109  }
1110 
1111  case VCARD_GEO_PROPERTY:
1112  if (vkind != VCARD_X_VALUE) {
1113  vcardgeotype geo = vcardvalue_get_geo(value);
1114 
1115  if (geo.uri && !strncmp(geo.uri, "geo:", 4)) {
1116  /* Convert geo: URI to STRUCTURED value kind */
1117  char *buf = icalmemory_strdup(geo.uri);
1118  geo.uri = NULL;
1119  const char *lat = buf + 4;
1120  char *lon = strchr(buf + 4, ',');
1121  if (lon) {
1122  *lon++ = '\0';
1123  }
1124  if (lat && lon) {
1125  strncpy(geo.coords.lat, lat, VCARD_GEO_LEN - 1);
1126  geo.coords.lat[VCARD_GEO_LEN - 1] = '\0';
1127  strncpy(geo.coords.lon, lon, VCARD_GEO_LEN - 1);
1128  geo.coords.lon[VCARD_GEO_LEN - 1] = '\0';
1129  } else {
1130  geo.coords.lat[0] = '\0';
1131  geo.coords.lon[0] = '\0';
1132  }
1133 
1134  vcardvalue_set_geo(value, geo);
1136  }
1137  }
1138  break;
1139 
1140  case VCARD_KEY_PROPERTY:
1141  case VCARD_LOGO_PROPERTY:
1142  case VCARD_PHOTO_PROPERTY:
1143  case VCARD_SOUND_PROPERTY:
1144  uri = vcardvalue_get_uri(value);
1145  if (uri && !strncmp("data:", uri, 5)) {
1146  /* Convert data: URI to base64-encoded TEXT value kind */
1147  char *base64, *data = NULL;
1148 
1149  char *buf = icalmemory_strdup(uri);
1150  mediatype = buf + 5;
1151  base64 = (char *)strstr(mediatype, ";base64,");
1152 
1153  if (base64) {
1154  param = vcardparameter_new_encoding(VCARD_ENCODING_B);
1155  vcardproperty_add_parameter(prop, param);
1156 
1157  *base64 = '\0';
1158  data = base64 + 8;
1159  } else {
1160  data = (char *)strchr(mediatype, ',');
1161  if (data) {
1162  *data++ = '\0';
1163  }
1164  }
1165 
1166  subtype = (char *)strchr(mediatype, '/');
1167  if (subtype) {
1168  /* Copy and uppercase the subtype */
1169  char *c;
1170 
1171  for (c = ++subtype; *c; c++) {
1172  *c = (char)toupper((int)*c);
1173  }
1174 
1175  /* Add TYPE parameter */
1176  type.val = VCARD_VERSION_NONE;
1177  type.xvalue = subtype;
1178  vcardproperty_add_type_parameter(prop, &type);
1179  }
1180 
1181  value->kind = VCARD_TEXT_VALUE;
1182  vcardvalue_set_text(value, data ? data : "");
1184  }
1185  break;
1186 
1187  case VCARD_KIND_PROPERTY:
1188  /* Rename KIND, MEMBER */
1189  xname = "X-ADDRESSBOOKSERVER-KIND";
1190 
1191  _fallthrough();
1192 
1193  case VCARD_MEMBER_PROPERTY: {
1194  char *buf = NULL;
1195 
1196  const char *xval = vcardvalue_as_vcard_string(value);
1197  if (!xname) {
1198  size_t buf_size = strlen(UUID_PREFIX) + strlen(xval) + 1;
1199  char *buf_ptr = buf = icalmemory_new_buffer(buf_size);
1200 
1201  /* RFC6868-encode the uid and prefix with urn:uuid: */
1202  icalmemory_append_string(&buf, &buf_ptr, &buf_size, UUID_PREFIX);
1203  icalmemory_append_encoded_string(&buf, &buf_ptr, &buf_size, xval);
1204  xname = "X-ADDRESSBOOKSERVER-MEMBER";
1205  xval = buf;
1206  }
1207 
1208  vcardproperty *xprop = vcardproperty_new_x(xval);
1209  vcardproperty_set_x_name(xprop, xname);
1210  vcardcomponent_add_property(impl, xprop);
1211  vcardcomponent_remove_property(impl, prop);
1212  vcardproperty_free(prop);
1214  break;
1215  }
1216 
1217  case VCARD_TEL_PROPERTY:
1218  uri = vcardvalue_get_uri(value);
1219  if (uri && !strncmp(uri, "tel:", 4)) {
1220  /* Convert tel: URI to TEXT value kind */
1221  char *buf = icalmemory_strdup(uri + 4);
1222 
1223  value->kind = VCARD_TEXT_VALUE;
1224  vcardvalue_set_text(value, buf);
1225 
1226  if (val_param) {
1227  vcardproperty_remove_parameter_by_ref(prop, val_param);
1228  }
1229 
1231  }
1232  break;
1233 
1234  case VCARD_UID_PROPERTY:
1235  /* Treat all values as TEXT (the default) */
1236  value->kind = VCARD_TEXT_VALUE;
1237 
1238  if (val_param) {
1239  vcardproperty_remove_parameter_by_ref(prop, val_param);
1240  }
1241  break;
1242 
1243  default:
1244  break;
1245  }
1246  }
1247 
1248  /* Add TYPE=PREF for each most preferred property */
1249  for (pkind = 0; pkind < VCARD_NO_PROPERTY; ++pkind) {
1250  struct pref_prop *pp = pref_props[pkind];
1251 
1252  if (pp) {
1253  type.val = VCARD_TYPE_PREF;
1254  type.xvalue = NULL;
1255 
1256  vcardproperty_add_type_parameter(pp->prop, &type);
1258  }
1259  }
1260 }
1261 
1262 void vcardcomponent_transform(vcardcomponent *impl,
1263  vcardproperty_version version)
1264 {
1265  icalpvl_elem itr;
1266  vcardcomponent_kind kind = vcardcomponent_isa(impl);
1267 
1268  icalerror_check_arg_rv((impl != 0), "component");
1269  icalerror_check_arg_rv((kind != VCARD_NO_COMPONENT),
1270  "component kind is VCARD_NO_COMPONENT");
1271 
1272  if (kind == VCARD_VCARD_COMPONENT) {
1273  if (version == VCARD_VERSION_40) {
1274  comp_to_v4(impl);
1275  } else {
1276  comp_to_v3(impl);
1277  }
1278  }
1279 
1280  for (itr = icalpvl_head(impl->components); itr != 0; itr = icalpvl_next(itr)) {
1281  vcardcomponent *c = (vcardcomponent *)icalpvl_data(itr);
1282  vcardcomponent_transform(c, version);
1283  }
1284 }
1285 
1286 /******************** Convenience routines **********************/
1287 
1288 enum vcardproperty_version vcardcomponent_get_version(vcardcomponent *comp)
1289 {
1290  icalerror_check_arg_rx(comp != 0, "comp", VCARD_VERSION_NONE);
1291 
1292  if (comp->versionp == 0) {
1293  comp->versionp =
1294  vcardcomponent_get_first_property(comp, VCARD_VERSION_PROPERTY);
1295 
1296  if (comp->versionp == 0) {
1297  return VCARD_VERSION_NONE;
1298  }
1299  }
1300 
1301  return vcardproperty_get_version(comp->versionp);
1302 }
1303 
1304 const char *vcardcomponent_get_uid(vcardcomponent *comp)
1305 {
1306  vcardproperty *prop;
1307 
1308  icalerror_check_arg_rz(comp != 0, "comp");
1309 
1310  prop = vcardcomponent_get_first_property(comp, VCARD_UID_PROPERTY);
1311 
1312  if (prop == 0) {
1313  return 0;
1314  }
1315 
1316  return vcardproperty_get_uid(prop);
1317 }
1318 
1319 const char *vcardcomponent_get_fn(vcardcomponent *comp)
1320 {
1321  vcardproperty *prop;
1322 
1323  icalerror_check_arg_rz(comp != 0, "comp");
1324 
1325  prop = vcardcomponent_get_first_property(comp, VCARD_FN_PROPERTY);
1326 
1327  if (prop == 0) {
1328  return 0;
1329  }
1330 
1331  return vcardproperty_get_fn(prop);
1332 }
void vcardcomponent_normalize(vcardcomponent *comp)
Normalizes (reorders and sorts the properties) the specified vcard comp.
Common memory management routines.
void icalmemory_append_decoded_string(char **buf, char **pos, size_t *buf_size, const char *string)
Definition: icalmemory.c:521
char * icalmemory_strdup(const char *s)
Creates a duplicate of a string.
Definition: icalmemory.c:242
void icalmemory_free_buffer(void *buf)
Releases a buffer.
Definition: icalmemory.c:355
vcardcomponent * vcardcomponent_clone(const vcardcomponent *old)
Deeply clones an vcard. Returns a pointer to the memory for the newly cloned vcard.
void vcardproperty_remove_parameter_by_ref(vcardproperty *prop, vcardparameter *parameter)
Removes the specified parameter reference from the property.
void icalerror_set_errno(icalerrorenum x)
Sets the icalerrno to a given error.
Definition: icalerror.c:90
Functions to check if a vcardcomponent meets the restrictions imposed by the standard.
Line-oriented parsing vCard format.
bool vcardrestriction_check(vcardcomponent *comp)
Checks if a given VCARD meets all the restrictions imposed by the standard.
vcardproperty * vcardproperty_clone(const vcardproperty *old)
Deeply clones an vcardproperty.
Definition: vcardproperty.c:87
void icalmemory_append_encoded_string(char **buf, char **pos, size_t *buf_size, const char *string)
Definition: icalmemory.c:478
Error handling for libical.
void icalmemory_append_char(char **buf, char **pos, size_t *buf_size, char ch)
Appends a character to a buffer.
Definition: icalmemory.c:406
vcardcomponent * vcardcomponent_new_from_string(const char *str)
Constructor.
Defines the data structure representing vCard components.
void vcardproperty_remove_parameter_by_kind(vcardproperty *prop, vcardparameter_kind kind)
Removes all parameters with the specified kind.
void vcardcomponent_strip_errors(vcardcomponent *comp)
Removes all X-LIC-ERROR properties.
void * icalmemory_new_buffer(size_t size)
Creates new buffer with the specified size.
Definition: icalmemory.c:315
void icalmemory_append_string(char **buf, char **pos, size_t *buf_size, const char *string)
Appends a string to a buffer.
Definition: icalmemory.c:365
void vcardproperty_set_parent(vcardproperty *property, vcardcomponent *comp)
Sets the parent vcard for the specified vcardproperty property.
Defines the data structure representing vCard values.
vcardcomponent * vcardcomponent_new(vcardcomponent_kind kind)
Constructor.
void vcardproperty_normalize(vcardproperty *prop)
vcardcomponent * vcardproperty_get_parent(const vcardproperty *property)
Returns the parent vcard for the specified property.
const char * vcardparameter_get_xname(const vcardparameter *param)
Returns the X-name of param.
int vcardcomponent_count_errors(vcardcomponent *comp)
Returns the number of errors encountered parsing the data.
vcardcomponent * vcardcomponent_vanew(vcardcomponent_kind kind,...)
Constructor.
void icalmemory_add_tmp_buffer(void *buf)
Adds an externally allocated buffer to the ring.
Definition: icalmemory.c:158