Libical API Documentation 4.0 STABLE VERSION Visit the v3.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 icalpvl_list properties;
38 icalpvl_elem property_iterator;
39 icalpvl_list components;
40 icalpvl_elem component_iterator;
41 struct vcardcomponent_impl *parent;
42};
43
44static void vcardcomponent_add_children(vcardcomponent *impl, va_list args)
45{
46 void *vp;
47
48 while ((vp = va_arg(args, void *)) != 0) {
49 icalassert(vcardproperty_isa_property(vp) != 0);
50
51 vcardcomponent_add_property(impl, (vcardproperty *)vp);
52 }
53}
54
55static vcardcomponent *vcardcomponent_new_impl(vcardcomponent_kind kind)
56{
57 vcardcomponent *comp;
58
59 if (!vcardcomponent_kind_is_valid(kind)) {
60 return NULL;
61 }
62
63 if ((comp = (vcardcomponent *)icalmemory_new_buffer(sizeof(vcardcomponent))) == 0) {
65 return 0;
66 }
67
68 memset(comp, 0, sizeof(vcardcomponent));
69
70 strcpy(comp->id, "comp");
71
72 comp->kind = kind;
73 comp->properties = icalpvl_newlist();
74 comp->components = icalpvl_newlist();
75
76 return comp;
77}
78
79vcardcomponent *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
88vcardcomponent *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
108vcardcomponent *vcardcomponent_new_from_string(const char *str)
109{
110 return vcardparser_parse_string(str);
111}
112
113vcardcomponent *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
135void 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) {
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[0] = 'X';
167
169}
170
171char *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
182char *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
249bool vcardcomponent_is_valid(const vcardcomponent *component)
250{
251 if (component) {
252 if ((strcmp(component->id, "comp") == 0) && (component->kind != VCARD_NO_COMPONENT)) {
253 return true;
254 }
255 }
256 return false;
257}
258
259vcardcomponent_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
266bool vcardcomponent_isa_component(const void *component)
267{
268 const vcardcomponent *impl = component;
269
270 icalerror_check_arg_rz((component != 0), "component");
271
272 return (strcmp(impl->id, "comp") == 0);
273}
274
275void 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
294void 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
323int 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
367vcardproperty *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
378vcardproperty *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
395vcardproperty *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
417vcardproperty **vcardcomponent_get_properties(vcardcomponent *comp,
418 vcardproperty_kind kind);
419
420void 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
434void 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
459int 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
477vcardcomponent *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
488vcardcomponent *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
506vcardcomponent *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
528int vcardcomponent_check_restrictions(vcardcomponent *comp)
529{
530 icalerror_check_arg_rz(comp != 0, "comp");
531 return vcardrestriction_check(comp);
532}
533
534int 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
551void 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
568struct vcardcomponent_kind_map {
569 vcardcomponent_kind kind;
570 char name[20];
571};
572
573static 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
581bool 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
594const 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
607vcardcomponent_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
624static int strcmpsafe(const char *a, const char *b)
625{
626 return strcmp((a == NULL ? "" : a),
627 (b == NULL ? "" : b));
628}
629
630static 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
659static 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
677static 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
713void 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
766static 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
1013struct pref_prop {
1014 vcardproperty *prop;
1015 int level;
1016};
1017
1018static void comp_to_v3(vcardcomponent *impl)
1019{
1020 struct pref_prop *pref_props[VCARD_NO_PROPERTY] = {0};
1021 vcardenumarray_element type;
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) {
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) {
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
1262void 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
1288enum 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
1304const 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
1319const 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 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:71
void icalmemory_free_buffer(void *buf)
Releases a buffer.
Definition icalmemory.c:355
char * icalmemory_strdup(const char *s)
Creates a duplicate of a string.
Definition icalmemory.c:242
void icalmemory_append_decoded_string(char **buf, char **pos, size_t *buf_size, const char *string)
Definition icalmemory.c:521
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 * icalmemory_new_buffer(size_t size)
Creates new buffer with the specified size.
Definition icalmemory.c:315
void icalmemory_append_encoded_string(char **buf, char **pos, size_t *buf_size, const char *string)
Definition icalmemory.c:478
void icalmemory_append_char(char **buf, char **pos, size_t *buf_size, char ch)
Appends a character to a buffer.
Definition icalmemory.c:406
void icalmemory_add_tmp_buffer(void *buf)
Adds an externally allocated buffer to the ring.
Definition icalmemory.c:158
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.