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