$darkmode
Libical API Documentation 4.0 STABLE VERSION Visit the v3.0 documentation
vcardparser.c
Go to the documentation of this file.
1 /*======================================================================
2  FILE: vcardparser.c
3  CREATOR: Ken Murchison 24 Aug 2022 <murch@fastmailteam.com>
4  CONTRIBUTOR: Bron Gondwana <brong@fastmailteam.com>
5 
6  SPDX-FileCopyrightText: 2022, Fastmail Pty. Ltd. (https://fastmail.com)
7  SPDX-License-Identifier: LGPL-2.1-only OR MPL-2.0
8  ======================================================================*/
9 
15 #ifdef HAVE_CONFIG_H
16 #include <config.h>
17 #endif
18 
19 #include "vcardparser.h"
20 #include "vcardcomponent.h"
21 #include "vcardparameter.h"
22 #include "vcardproperty.h"
23 #include "vcardtextlist.h"
24 #include "vcardvalue.h"
25 #include "icalmemory.h"
26 
27 #include <ctype.h>
28 #include <stdio.h>
29 #include <stdarg.h>
30 #include <stdlib.h>
31 #include <string.h>
32 
33 #define DEBUG 0
34 
35 static ICAL_GLOBAL_VAR vcard_xprop_value_kind_func xprop_value_kind_func = NULL;
36 static ICAL_GLOBAL_VAR void *xprop_value_kind_data = NULL;
37 
38 enum parse_error
39 {
40  PE_OK = 0,
41  PE_BACKQUOTE_EOF,
42  PE_BEGIN_PARAMS,
43  PE_PROP_MULTIGROUP,
44  PE_FINISHED_EARLY,
45  PE_KEY_EOF,
46  PE_KEY_EOL,
47  PE_MISMATCHED_CARD,
48  PE_NAME_EOF,
49  PE_NAME_EOL,
50  PE_NAME_INVALID,
51  PE_PARAMVALUE_EOF,
52  PE_PARAMVALUE_EOL,
53  PE_QSTRING_EOF,
54  PE_QSTRING_EOL,
55  PE_QSTRING_EOV,
56  PE_VALUE_INVALID,
57  PE_ILLEGAL_CHAR,
58  PE_NUMERR /* last */
59 };
60 
61 struct buf {
62  char *s;
63  size_t len;
64  size_t alloc;
65 };
66 #define BUF_INITIALIZER \
67  { \
68  NULL, 0, 0}
69 
70 struct vcardparser_state {
71  struct buf buf;
72  struct buf errbuf;
73  const char *base;
74  const char *itemstart;
75  const char *p;
76  vcardproperty *version;
77 
78  /* current items */
79  vcardcomponent *xroot;
80  vcardcomponent *vcard;
81  vcardproperty *prop;
82  vcardparameter *param;
83  vcardvalue_kind value_kind;
84 };
85 
86 struct vcardparser_errorpos {
87  int startpos;
88  int startline;
89  int startchar;
90  int errorpos;
91  int errorline;
92  int errorchar;
93 };
94 
95 #define BUF_GROW 128
96 
97 void buf_init(struct buf *buf, size_t size)
98 {
99  buf->len = 0;
100  buf->alloc = size;
101  buf->s = icalmemory_new_buffer(buf->alloc);
102 }
103 
104 static size_t buf_len(const struct buf *buf)
105 {
106  return buf->len;
107 }
108 
109 static void buf_reset(struct buf *buf)
110 {
111  buf->len = 0;
112 }
113 
114 static void buf_free(struct buf *buf)
115 {
116  if (buf->s) {
117  icalmemory_free_buffer(buf->s);
118  buf->s = NULL;
119  }
120 
121  buf->len = buf->alloc = 0;
122 }
123 
124 static void _buf_putc(struct buf *buf, char c)
125 {
126  char *pos = buf->s + buf->len;
127 
128  icalmemory_append_char(&buf->s, &pos, &buf->alloc, c);
129 }
130 
131 static void buf_putc(struct buf *buf, char c)
132 {
133  _buf_putc(buf, c);
134 
135  buf->len++;
136 }
137 
138 static const char *buf_cstring(struct buf *buf)
139 {
140  _buf_putc(buf, '\0');
141 
142  return buf->s;
143 }
144 
145 static void buf_trim(struct buf *buf)
146 {
147  /* trim whitespace
148  *
149  * XXX we can do this within the existing buffer
150  * because the new string length <= old string length
151  */
152  const char *s = buf_cstring(buf);
153 
154  for (buf->len = 0; *s; s++) {
155  if (isspace((int)*s)) {
156  continue;
157  }
158  buf->s[buf->len++] = *s;
159  }
160 }
161 
162 #pragma GCC diagnostic push
163 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
164 static void buf_vprintf(struct buf *buf, const char *fmt, va_list args)
165 {
166  va_list ap;
167  size_t size, n;
168 
169  /* Copy args in case we have to try again */
170  va_copy(ap, args);
171 
172  size = buf->alloc - buf->len;
173  n = (size_t)vsnprintf(buf->s + buf->len, size, fmt, args);
174 
175  if (n >= size) {
176  /* Grow the buffer and try again */
177  size = n + BUF_GROW;
178  buf->alloc += size;
179  buf->s = icalmemory_resize_buffer(buf->s, buf->alloc);
180 
181  n = (size_t)vsnprintf(buf->s + buf->len, size, fmt, ap);
182  }
183  va_end(ap);
184 
185  buf->len += n;
186 }
187 #pragma GCC diagnostic pop
188 
189 #define NOTESTART() state->itemstart = state->p
190 #define MAKE(X, Y) X = icalmemory_new_buffer(sizeof(struct Y))
191 #define PUTC(C) buf_putc(&state->buf, C)
192 #define INC(I) state->p += I
193 #define IS_CTRL(ch) \
194  ((ch) > 0 && (ch) <= 0x1f && (ch) != '\r' && (ch) != '\n' && (ch) != '\t')
195 #define HANDLECTRL(state) \
196  { \
197  if (IS_CTRL(*(state)->p)) { \
198  while (IS_CTRL(*(state)->p)) \
199  (state)->p++; \
200  } \
201  if ((*(state)->p) == 0) \
202  break; \
203  }
204 
205 static int _parse_param_name(struct vcardparser_state *state)
206 {
207  vcardparameter_kind kind;
208  const char *name;
209 
210  while (*state->p) {
211  /* Handle control characters and break for NUL char */
212  HANDLECTRL(state);
213 
214  switch (*state->p) {
215  case '=':
216  name = buf_cstring(&state->buf);
217  kind = vcardparameter_string_to_kind(name);
218  state->param = vcardparameter_new(kind);
219  if (kind == VCARD_X_PARAMETER) {
220  vcardparameter_set_xname(state->param, name);
221  } else if (kind == VCARD_IANA_PARAMETER) {
222  vcardparameter_set_iana_name(state->param, name);
223  }
224  buf_reset(&state->buf);
225  INC(1);
226  return 0;
227 
228  case ';': /* vCard 2.1 parameter with just a value */
229  case ':':
230  state->param = vcardparameter_new(VCARD_TYPE_PARAMETER);
231  /* no INC - we need to see this char up a layer */
232  return 0;
233 
234  case '\r':
235  INC(1);
236  break; /* just skip */
237  case '\n':
238  if (state->p[1] != ' ' && state->p[1] != '\t') {
239  return PE_KEY_EOL;
240  }
241  INC(2);
242  break;
243 
244  /* XXX - check exact legal set? */
245  default:
246  PUTC(*state->p);
247  INC(1);
248  break;
249  }
250  }
251 
252  return PE_KEY_EOF;
253 }
254 
255 /* just leaves it on the buffer */
256 static int _parse_param_quoted(struct vcardparser_state *state,
257  bool is_structured, bool is_multivalued)
258 {
259  NOTESTART();
260 
261  while (*state->p) {
262  /* Handle control characters and break for NUL char */
263  HANDLECTRL(state);
264 
265  switch (*state->p) {
266  case '"':
267  INC(1);
268  return 0;
269 
270  /* normal backslash quoting - NOTE, not strictly RFC compliant,
271  * but I figure anyone who generates one PROBABLY meant to escape
272  * the next character because it's so common, and LABEL definitely
273  * allows \n, so we have to handle that anyway */
274  case '\\':
275  /* seen in the wild - \n split by line wrapping */
276  if (state->p[1] == '\r') {
277  INC(1);
278  }
279  if (state->p[1] == '\n') {
280  if (state->p[2] != ' ' && state->p[2] != '\t') {
281  return PE_QSTRING_EOL;
282  }
283  INC(2);
284  }
285  if (!state->p[1]) {
286  return PE_BACKQUOTE_EOF;
287  }
288  if (state->p[1] == 'n' || state->p[1] == 'N') {
289  PUTC('\n');
290  } else if (is_structured && strchr(";,", state->p[1])) {
291  // preserve escaped COMMA and SEMICOLON in structured value
292  PUTC(state->p[0]);
293  PUTC(state->p[1]);
294  } else {
295  PUTC(state->p[1]);
296  }
297  INC(2);
298  break;
299 
300  /* special value quoting for doublequote and endline (RFC 6868) */
301  case '^':
302  if (state->p[1] == '\r') {
303  INC(1);
304  }
305  if (state->p[1] == '\n') {
306  if (state->p[2] != ' ' && state->p[2] != '\t') {
307  return PE_QSTRING_EOL;
308  }
309  INC(2);
310  }
311  if (state->p[1] == '\'') {
312  PUTC('"');
313  INC(2);
314  } else if (state->p[1] == 'n') { /* only lower case per the RFC */
315  PUTC('\n');
316  INC(2);
317  } else if (state->p[1] == '^') {
318  PUTC('^');
319  INC(2);
320  } else {
321  PUTC('^');
322  INC(1); /* treat next char normally */
323  }
324  break;
325 
326  case '\r':
327  INC(1);
328  break; /* just skip */
329  case '\n':
330  if (state->p[1] != ' ' && state->p[1] != '\t') {
331  return PE_QSTRING_EOL;
332  }
333  INC(2);
334  break;
335 
336  case ',':
337  if (is_multivalued && !is_structured) {
338  return PE_QSTRING_EOV;
339  }
340  /* or fall through, comma isn't special */
341  _fallthrough();
342 
343  default:
344  PUTC(*state->p);
345  INC(1);
346  break;
347  }
348  }
349 
350  return PE_QSTRING_EOF;
351 }
352 
353 static int _parse_param_value(struct vcardparser_state *state)
354 {
355  bool is_multivalued = vcardparameter_is_multivalued(state->param);
356  bool is_structured = vcardparameter_is_structured(state->param);
357  int r;
358 
359  while (*state->p) {
360  /* Handle control characters and break for NUL char */
361  HANDLECTRL(state);
362 
363  switch (*state->p) {
364  case '\\': /* normal backslash quoting */
365  /* seen in the wild - \n split by line wrapping */
366  if (state->p[1] == '\r') {
367  INC(1);
368  }
369  if (state->p[1] == '\n') {
370  if (state->p[2] != ' ' && state->p[2] != '\t') {
371  return PE_PARAMVALUE_EOL;
372  }
373  INC(2);
374  }
375  if (!state->p[1]) {
376  return PE_BACKQUOTE_EOF;
377  }
378  if (state->p[1] == 'n' || state->p[1] == 'N') {
379  PUTC('\n');
380  } else {
381  PUTC(state->p[1]);
382  }
383  INC(2);
384  break;
385 
386  case '^': /* special value quoting for doublequote (RFC 6868) */
387  /* seen in the wild - \n split by line wrapping */
388  if (state->p[1] == '\r') {
389  INC(1);
390  }
391  if (state->p[1] == '\n') {
392  if (state->p[2] != ' ' && state->p[2] != '\t') {
393  return PE_PARAMVALUE_EOL;
394  }
395  INC(2);
396  }
397  if (state->p[1] == '\'') {
398  PUTC('"');
399  INC(2);
400  } else if (state->p[1] == 'n') {
401  PUTC('\n');
402  INC(2);
403  } else if (state->p[1] == '^') {
404  PUTC('^');
405  INC(2);
406  } else {
407  PUTC('^');
408  INC(1); /* treat next char normally */
409  }
410  break;
411 
412  case '"':
413  INC(1);
414  while ((r = _parse_param_quoted(state,
415  is_structured,
416  is_multivalued)) == PE_QSTRING_EOV) {
417  vcardparameter_add_value_from_string(state->param,
418  buf_cstring(&state->buf));
419 
420  buf_reset(&state->buf);
421  INC(1);
422  }
423  if (r) {
424  return r;
425  }
426  break;
427 
428  case ':':
429  case ';':
430  /* done - end of parameter */
431  if (is_multivalued) {
432  vcardparameter_add_value_from_string(state->param,
433  buf_cstring(&state->buf));
434  } else {
435  vcardparameter_set_value_from_string(state->param,
436  buf_cstring(&state->buf));
437 
438  /* if it is a VALUE parameter, set the value kind */
439  if (vcardparameter_isa(state->param) == VCARD_VALUE_PARAMETER) {
440  vcardvalue_kind kind =
441  vcardvalue_string_to_kind(buf_cstring(&state->buf));
442  if (kind != VCARD_NO_VALUE) {
443  state->value_kind = kind;
444  }
445  }
446  }
447 
448  buf_reset(&state->buf);
449  /* no INC - we need to see this char up a layer */
450  return 0;
451 
452  case '\r':
453  INC(1);
454  break; /* just skip */
455 
456  case '\n':
457  if (state->p[1] != ' ' && state->p[1] != '\t') {
458  return PE_PARAMVALUE_EOL;
459  }
460  INC(2);
461  break;
462 
463  case ',':
464  if (is_multivalued) {
465  vcardparameter_add_value_from_string(state->param,
466  buf_cstring(&state->buf));
467  buf_reset(&state->buf);
468  INC(1);
469  break;
470  }
471  /* or fall through, comma isn't special */
472  _fallthrough();
473 
474  default:
475  PUTC(*state->p);
476  INC(1);
477  break;
478  }
479  }
480 
481  return PE_PARAMVALUE_EOF;
482 }
483 
484 static void _parse_error(struct vcardparser_state *state,
485  enum vcardparameter_xlicerrortype type,
486  const char *fmt, ...)
487 {
488  va_list ap;
489 
490  va_start(ap, fmt);
491  buf_reset(&state->errbuf);
492  buf_vprintf(&state->errbuf, fmt, ap);
493  va_end(ap);
494 
495  if (state->prop) {
496  vcardproperty_free(state->prop);
497  }
498  state->version = NULL;
499 
500  /* coverity[resource_leak] */
501  vcardparameter *errParam = vcardparameter_new_xlicerrortype(type);
502  state->prop = vcardproperty_vanew_xlicerror(buf_cstring(&state->errbuf),
503  errParam,
504  (void *)0);
505  buf_reset(&state->buf);
506 }
507 
508 static int _parse_prop_params(struct vcardparser_state *state)
509 {
510  vcardproperty_kind prop_kind = vcardproperty_isa(state->prop);
511  const char *group = vcardproperty_get_group(state->prop);
512 
513  do {
514  int r;
515 
516  INC(1); /* skip ';' */
517 
518  NOTESTART();
519 
520  /* get the name */
521  r = _parse_param_name(state);
522  if (r) {
523  _parse_error(state,
524  VCARD_XLICERRORTYPE_PARAMETERNAMEPARSEERROR,
525  "%s '%s' in %s%s%s property. Removing entire property",
526  vcardparser_errstr(r), buf_cstring(&state->buf),
527  group ? group : "", group ? "." : "",
528  vcardproperty_kind_to_string(prop_kind));
529  return r;
530  }
531 
532  vcardproperty_add_parameter(state->prop, state->param);
533 
534  /* now get the value */
535  r = _parse_param_value(state);
536  if (r) {
537  vcardparameter_kind param_kind = vcardparameter_isa(state->param);
538 
539  _parse_error(state,
540  VCARD_XLICERRORTYPE_PARAMETERVALUEPARSEERROR,
541  "%s for %s in %s%s%s property. Removing entire property",
542  vcardparser_errstr(r),
543  vcardparameter_kind_to_string(param_kind),
544  group ? group : "", group ? "." : "",
545  vcardproperty_kind_to_string(prop_kind));
546  return r;
547  }
548 
549  } while (*state->p == ';'); /* another parameter to parse */
550 
551  return 0;
552 }
553 
554 static int _parse_prop_name(struct vcardparser_state *state)
555 {
556  const char *name;
557  const char *group = NULL;
558  vcardproperty_kind kind;
559  vcardproperty_version version = VCARD_VERSION_NONE;
560  int r = 0;
561 
562  NOTESTART();
563 
564  while (*state->p) {
565  /* Handle control characters and break for NUL char */
566  HANDLECTRL(state);
567 
568  switch (*state->p) {
569  case ':':
570  case ';':
571  name = buf_cstring(&state->buf);
572  kind = vcardproperty_string_to_kind(name);
573 
574  state->prop = vcardproperty_new(kind);
575  if (!state->prop) {
576  return PE_NAME_INVALID;
577  }
578 
579  if (kind == VCARD_X_PROPERTY) {
580  vcardproperty_set_x_name(state->prop, name);
581  }
582 
583  if (group) {
584  vcardproperty_set_group(state->prop, group);
585  }
586 
587  if (state->version) {
588  version = vcardproperty_get_version(state->version);
589  }
590 
591  /* set default value kind */
592  switch (kind) {
593  case VCARD_GEO_PROPERTY:
594  state->value_kind = version == VCARD_VERSION_40 ? VCARD_URI_VALUE : VCARD_GEO_VALUE;
595  break;
596 
597  case VCARD_KEY_PROPERTY:
598  case VCARD_LOGO_PROPERTY:
599  case VCARD_PHOTO_PROPERTY:
600  case VCARD_SOUND_PROPERTY:
601  state->value_kind = version == VCARD_VERSION_40 ? VCARD_URI_VALUE : VCARD_TEXT_VALUE;
602  break;
603 
604  case VCARD_TZ_PROPERTY:
605  state->value_kind = version == VCARD_VERSION_40 ? VCARD_TEXT_VALUE : VCARD_UTCOFFSET_VALUE;
606  break;
607 
608  case VCARD_UID_PROPERTY:
609  state->value_kind = version == VCARD_VERSION_40 ? VCARD_URI_VALUE : VCARD_TEXT_VALUE;
610  break;
611 
612  case VCARD_X_PROPERTY:
613  state->value_kind =
614  xprop_value_kind_func ? xprop_value_kind_func(name, xprop_value_kind_data)
615  : VCARD_X_VALUE;
616  break;
617 
618  default:
619  state->value_kind = vcardproperty_kind_to_value_kind(kind);
620  break;
621  }
622 
623  buf_reset(&state->buf);
624 
625  /* no INC - we need to see this char up a layer */
626  return r;
627 
628  case '.':
629  if (group) {
630  size_t tmpLen = strlen(group) + buf_len(&state->buf) + 2;
631  char *tmp = icalmemory_tmp_buffer(tmpLen);
632  snprintf(tmp, tmpLen - 1, "%s.%s", group, buf_cstring(&state->buf));
633  group = tmp;
634  r = PE_PROP_MULTIGROUP;
635  } else {
636  group = icalmemory_tmp_copy(buf_cstring(&state->buf));
637  }
638  buf_reset(&state->buf);
639  INC(1);
640  break;
641 
642  case '\r':
643  INC(1);
644  break; /* just skip */
645  case '\n':
646  if (state->p[1] == ' ' || state->p[1] == '\t') { /* wrapped line */
647  INC(2);
648  } else if (!state->buf.len) {
649  /* no key yet? blank intermediate lines are OK */
650  INC(1);
651  } else {
652  return PE_NAME_EOL;
653  }
654  break;
655 
656  default:
657  PUTC(*state->p);
658  INC(1);
659  break;
660  }
661  }
662 
663  return PE_NAME_EOF;
664 }
665 
666 static int _parse_prop_value(struct vcardparser_state *state)
667 {
668  vcardproperty_kind prop_kind = vcardproperty_isa(state->prop);
669  vcardvalue *value = NULL;
670 
671  NOTESTART();
672 
673  while (*state->p) {
674  /* Handle control characters and break for NUL char */
675  HANDLECTRL(state);
676 
677  switch (*state->p) {
678  case '\\':
679  /* seen in the wild - \n split by line wrapping */
680  while (state->p[1] && strchr("\r\n", state->p[1])) {
681  if (state->p[1] == '\r') {
682  INC(1);
683  }
684  if (state->p[1] == '\n') {
685  if (state->p[2] != ' ' && state->p[2] != '\t') {
686  /* ignore unescaped backslash at end of line */
687  INC(2);
688  goto out;
689  }
690  INC(2);
691  }
692  }
693  if (state->p[1]) {
694  /* preserve escape sequences */
695  PUTC('\\');
696  PUTC(state->p[1]);
697  INC(2);
698  } else {
699  /* ignore unescaped backslash at end of line */
700  INC(1);
701  goto out;
702  }
703  break;
704  case '\r':
705  INC(1);
706  break; /* just skip */
707  case '\n':
708  if (state->p[1] == ' ' || state->p[1] == '\t') { /* wrapped line */
709  INC(2);
710  break;
711  }
712  /* otherwise it's the end of the value */
713  INC(1);
714  goto out;
715 
716  default:
717  PUTC(*state->p);
718  INC(1);
719  break;
720  }
721  }
722 
723 out:
724  /* reaching the end of the file isn't a failure here,
725  * it's just another type of end-of-value */
726 
727  /* repair critical property values */
728  if (prop_kind == VCARD_VERSION_PROPERTY) {
729  buf_trim(&state->buf);
730  state->version = state->prop;
731  }
732 
733  if (state->value_kind == VCARD_TEXTLIST_VALUE) {
734  char sep = vcardproperty_is_structured(prop_kind) ? ';' : ',';
735  vcardstrarray *textlist =
736  vcardtextlist_new_from_string(buf_cstring(&state->buf), sep);
737  if (textlist) {
738  value = vcardvalue_new_textlist(textlist);
739  }
740  } else {
741  value = vcardvalue_new_from_string(state->value_kind,
742  buf_cstring(&state->buf));
743  }
744 
745  if (!value) {
746  return PE_VALUE_INVALID;
747  }
748 
749  vcardproperty_set_value(state->prop, value);
750  buf_reset(&state->buf);
751 
752  return 0;
753 }
754 
755 static void _parse_eatline(struct vcardparser_state *state)
756 {
757  while (*state->p) {
758  /* Handle control characters and break for NUL char */
759  HANDLECTRL(state);
760 
761  switch (*state->p) {
762  case '\n':
763  if (state->p[1] == ' ' || state->p[1] == '\t') { /* wrapped line */
764  INC(2);
765  break;
766  }
767  /* otherwise it's the end of the line */
768  INC(1);
769  return;
770 
771  default:
772  INC(1);
773  break;
774  }
775  }
776 }
777 
778 static void _parse_prop(struct vcardparser_state *state)
779 {
780  int r = _parse_prop_name(state);
781  if (r) {
782  if (r == PE_PROP_MULTIGROUP) {
783  vcardproperty_kind prop_kind = vcardproperty_isa(state->prop);
784 
785  _parse_error(state,
786  VCARD_XLICERRORTYPE_PROPERTYPARSEERROR,
787  "%s '%s.%s'. Removing entire property",
788  vcardparser_errstr(r),
789  vcardproperty_get_group(state->prop),
790  vcardproperty_kind_to_string(prop_kind));
791  _parse_eatline(state);
792  } else if (r == PE_NAME_INVALID) {
793  _parse_error(state,
794  VCARD_XLICERRORTYPE_PROPERTYPARSEERROR,
795  "%s '%s'. Removing entire property",
796  vcardparser_errstr(r), buf_cstring(&state->buf));
797  _parse_eatline(state);
798  } else {
799  _parse_error(state,
800  VCARD_XLICERRORTYPE_PROPERTYPARSEERROR,
801  "%s '%s'. Ignoring property",
802  vcardparser_errstr(r), buf_cstring(&state->buf));
803  }
804  return;
805  }
806 
807  if (*state->p == ';') {
808  r = _parse_prop_params(state);
809  if (r) {
810  /* errors handled in _parse_prop_params() */
811  return;
812  }
813  }
814 
815  INC(1); /* skip ':' */
816  r = _parse_prop_value(state);
817  if (r) {
818  vcardproperty_kind prop_kind = vcardproperty_isa(state->prop);
819  const char *group = vcardproperty_get_group(state->prop);
820 
821  if (r == PE_VALUE_INVALID) {
822  _parse_error(state,
823  VCARD_XLICERRORTYPE_VALUEPARSEERROR,
824  "Error parsing '%s' as %s value in %s%s%s property."
825  " Removing entire property",
826  buf_cstring(&state->buf),
827  vcardvalue_kind_to_string(state->value_kind),
828  group ? group : "", group ? "." : "",
829  vcardproperty_kind_to_string(prop_kind));
830  } else {
831  _parse_error(state,
832  VCARD_XLICERRORTYPE_VALUEPARSEERROR,
833  "%s in %s%s%s property. Removing entire property",
834  vcardparser_errstr(r),
835  group ? group : "", group ? "." : "",
836  vcardproperty_kind_to_string(prop_kind));
837  }
838  }
839 }
840 
841 static int _parse_vcard(struct vcardparser_state *state, int only_one)
842 {
843  const char *cardstart = state->p;
844  int r = 0;
845 
846  while (*state->p) {
847  /* whitespace is very skippable before AND afterwards */
848  if (*state->p == '\r' || *state->p == '\n' ||
849  *state->p == ' ' || *state->p == '\t') {
850  INC(1);
851  continue;
852  }
853 
854  _parse_prop(state);
855 
856  if (vcardproperty_isa(state->prop) == VCARD_BEGIN_PROPERTY) {
857  if (state->vcard) {
858  // Reject nested VCARD.
859  state->itemstart = cardstart;
860  r = PE_MISMATCHED_CARD;
861  break;
862  }
863 
864  if (vcardvalue_isa(vcardproperty_get_value(state->prop)) !=
865  VCARD_TEXT_VALUE) {
866  r = PE_VALUE_INVALID;
867  break;
868  }
869 
870  const char *val =
871  vcardvalue_get_text(vcardproperty_get_value(state->prop));
872 
873  if (!val || strcasecmp(val, "VCARD") != 0) {
874  state->itemstart = cardstart;
875  r = PE_NAME_INVALID;
876  break;
877  }
878 
879  vcardproperty_free(state->prop);
880  state->prop = NULL;
881  state->vcard = vcardcomponent_new(VCARD_VCARD_COMPONENT);
882  vcardcomponent_add_component(state->xroot, state->vcard);
883 
884  } else if (!state->vcard) {
885  /* this means we're at the top level, haven't seen a BEGIN! */
886  state->itemstart = cardstart;
887  r = PE_MISMATCHED_CARD;
888  break;
889  } else if (vcardproperty_isa(state->prop) == VCARD_END_PROPERTY) {
890  if (vcardvalue_isa(vcardproperty_get_value(state->prop)) !=
891  VCARD_TEXT_VALUE) {
892  r = PE_VALUE_INVALID;
893  break;
894  }
895 
896  const char *val =
897  vcardvalue_get_text(vcardproperty_get_value(state->prop));
898 
899  if (!val || strcasecmp(val, "VCARD") != 0) {
900  state->itemstart = cardstart;
901  r = PE_NAME_INVALID;
902  break;
903  }
904 
905  vcardproperty_free(state->prop);
906  state->vcard = NULL;
907  state->prop = NULL;
908 
909  if (only_one) {
910  break;
911  }
912 
913  } else {
914  vcardcomponent_add_property(state->vcard, state->prop);
915  state->prop = NULL;
916  }
917  }
918 
919  if (state->prop) {
920  if (vcardproperty_isa(state->prop) != VCARD_END_PROPERTY) {
921  r = PE_FINISHED_EARLY;
922  }
923 
924  vcardproperty_free(state->prop);
925  state->prop = NULL;
926  }
927 
928  return r;
929 }
930 
931 static int vcardparser_parse(struct vcardparser_state *state, int only_one)
932 {
933  state->xroot = vcardcomponent_new(VCARD_XROOT_COMPONENT);
934 
935  state->p = state->base;
936 
937  buf_init(&state->buf, BUF_GROW);
938  buf_init(&state->errbuf, BUF_GROW);
939 
940  /* don't parse trailing non-whitespace */
941  return _parse_vcard(state, only_one);
942 }
943 
944 /* FREE MEMORY */
945 
946 static void _free_state(struct vcardparser_state *state)
947 {
948  buf_free(&state->buf);
949  buf_free(&state->errbuf);
950 
951  if (state->xroot) {
952  vcardcomponent_free(state->xroot);
953  }
954 
955  memset(state, 0, sizeof(struct vcardparser_state));
956 }
957 
958 static void vcardparser_free(struct vcardparser_state *state)
959 {
960  _free_state(state);
961 }
962 
963 /* PUBLIC API */
964 
965 const char *vcardparser_errstr(int err)
966 {
967  switch (err) {
968  case PE_BACKQUOTE_EOF:
969  return "EOF after backslash";
970  case PE_BEGIN_PARAMS:
971  return "Params on BEGIN field";
972  case PE_PROP_MULTIGROUP:
973  return "Multiple group levels in property name";
974  case PE_FINISHED_EARLY:
975  return "vCard not completed";
976  case PE_KEY_EOF:
977  return "End of data while parsing parameter key";
978  case PE_KEY_EOL:
979  return "End of line while parsing parameter key";
980  case PE_MISMATCHED_CARD:
981  return "Closed a different card name than opened";
982  case PE_NAME_EOF:
983  return "End of data while parsing property name";
984  case PE_NAME_EOL:
985  return "End of line while parsing property name";
986  case PE_NAME_INVALID:
987  return "Invalid property name";
988  case PE_PARAMVALUE_EOF:
989  return "End of data while parsing parameter value";
990  case PE_PARAMVALUE_EOL:
991  return "End of line while parsing parameter value";
992  case PE_QSTRING_EOF:
993  return "End of data while parsing quoted value";
994  case PE_QSTRING_EOL:
995  return "End of line while parsing quoted value";
996  case PE_QSTRING_EOV:
997  return "End of line while parsing multi or structured value";
998  case PE_VALUE_INVALID:
999  return "Invalid value for property";
1000  case PE_ILLEGAL_CHAR:
1001  return "Illegal character in vCard";
1002  case PE_NUMERR:
1003  default:
1004  return "Unknown error";
1005  }
1006 }
1007 
1008 vcardcomponent *vcardparser_parse_string(const char *str)
1009 {
1010  struct vcardparser_state parser;
1011  vcardcomponent *vcard = NULL;
1012  int r;
1013 
1014  memset(&parser, 0, sizeof(struct vcardparser_state));
1015 
1016  parser.base = str;
1017  r = vcardparser_parse(&parser, 0);
1018  if (!r) {
1019  if (vcardcomponent_count_components(parser.xroot,
1020  VCARD_VCARD_COMPONENT) == 1) {
1021  vcard = vcardcomponent_get_first_component(parser.xroot,
1022  VCARD_VCARD_COMPONENT);
1023  vcardcomponent_remove_component(parser.xroot, vcard);
1024  } else {
1025  vcard = parser.xroot;
1026  parser.xroot = NULL;
1027  }
1028  }
1029 
1030  vcardparser_free(&parser);
1031 
1032  return vcard;
1033 }
1034 
1036 {
1037  xprop_value_kind_func = func;
1038  xprop_value_kind_data = data;
1039 }
void * icalmemory_resize_buffer(void *buf, size_t size)
Resizes a buffer created with icalmemory_new_buffer().
Definition: icalmemory.c:336
Common memory management routines.
void * icalmemory_tmp_buffer(size_t size)
Creates a new temporary buffer on the ring and returns it.
Definition: icalmemory.c:182
vcardparameter * vcardparameter_new(vcardparameter_kind kind)
Creates new vcardparameter object.
void icalmemory_free_buffer(void *buf)
Releases a buffer.
Definition: icalmemory.c:355
Defines the data structure representing vCard parameters.
const char * vcardparameter_kind_to_string(vcardparameter_kind kind)
Returns a string representing the given vcardparameter_kind.
Line-oriented parsing vCard format.
void vcardparameter_set_xname(vcardparameter *param, const char *v)
Sets the X-name of param to v.
void vcardparser_set_xprop_value_kind(vcard_xprop_value_kind_func func, void *data)
Registers a parser callback to override the default value type of an x-property.
Definition: vcardparser.c:1035
void icalmemory_append_char(char **buf, char **pos, size_t *buf_size, char ch)
Appends a character to a buffer.
Definition: icalmemory.c:406
Defines the data structure representing vCard properties.
Defines the data structure representing vCard components.
Defines functions for creating vCard text lists.
void * icalmemory_new_buffer(size_t size)
Creates new buffer with the specified size.
Definition: icalmemory.c:315
void vcardparameter_set_iana_name(vcardparameter *param, const char *v)
Sets the IANA name of param to v.
Defines the data structure representing vCard values.
vcardcomponent * vcardcomponent_new(vcardcomponent_kind kind)
Constructor.
vcardparameter_kind vcardparameter_isa(const vcardparameter *parameter)
vcardvalue_kind(* vcard_xprop_value_kind_func)(const char *name, void *data)
Callback function pointer to define x-property default value types.
Definition: vcardparser.h:39
char * icalmemory_tmp_copy(const char *str)
Creates a copy of the given string, stored on the ring buffer, and returns it.
Definition: icalmemory.c:222
vcardparameter_kind vcardparameter_string_to_kind(const char *string)
Returns the vcardparameter_kind for a given string.