Libical API Documentation 4.0 UNRELEASED Go to the stable 3.0 documentation
Loading...
Searching...
No Matches
icalparser.c
Go to the documentation of this file.
1/*======================================================================
2 FILE: icalparser.c
3 CREATOR: eric 04 August 1999
4
5 SPDX-FileCopyrightText: 2000, Eric Busboom <eric@civicknowledge.com>
6 SPDX-License-Identifier: LGPL-2.1-only OR MPL-2.0
7
8 The Initial Developer of the Original Code is Eric Busboom
9 ======================================================================*/
10
15
16#ifdef HAVE_CONFIG_H
17#include <config.h>
18#endif
19
20#include "icalparser.h"
21#include "icalerror_p.h"
22#include "icalerror.h"
23#include "icallimits.h"
24#include "icalmemory.h"
25#include "icalvalue.h"
26#include "icalparameter.h"
27#include "icalproperty_p.h"
28#include "icalpvl_p.h"
29
30#include <ctype.h>
31#include <stddef.h> /* for ptrdiff_t */
32#include <stdlib.h>
33#include <string.h>
34
36#define TMP_BUF_SIZE 80
38
39static enum icalparser_ctrl icalparser_ctrl_g = ICALPARSER_CTRL_KEEP;
40
41struct icalparser_impl {
42 int buffer_full; /* flag indicates that temp is smaller that
43 data being read into it */
44 int continuation_line; /* last line read was a continuation line */
45 size_t tmp_buf_size;
46 char temp[TMP_BUF_SIZE];
47 icalcomponent *root_component;
48 int version;
49 int level;
50 int lineno;
51 size_t error_count;
52 icalparser_state state;
53 icalpvl_list components;
54
55 void *line_gen_data;
56};
57
58/*
59 * New version of strstrip() that does not move the pointer.
60 */
61static void strstriplt(char *buf)
62{
63 size_t len, a;
64
65 if (buf == NULL) {
66 return;
67 }
68 if (buf[0] == 0) {
69 return;
70 }
71 len = strlen(buf);
72 /* the casts to unsigned char below are to work around isspace asserts
73 on Windows due to non-ascii characters becoming negative */
74 while ((buf[0] != 0) && (isspace((unsigned char)buf[len - 1]))) {
75 buf[--len] = 0;
76 }
77 if (buf[0] == 0) {
78 return;
79 }
80 a = 0;
81 while (isspace((unsigned char)buf[a])) {
82 a++;
83 }
84 if (a > 0) {
85 memmove(buf, &buf[a], len - a + 1);
86 }
87}
88
89icalparser *icalparser_new(void)
90{
91 struct icalparser_impl *impl = 0;
92
93 if ((impl = (struct icalparser_impl *)icalmemory_new_buffer(sizeof(struct icalparser_impl))) == 0) {
95 return 0;
96 }
97
98 impl->root_component = 0;
99 impl->components = icalpvl_newlist();
100 impl->level = 0;
101 impl->state = ICALPARSER_SUCCESS;
102 impl->tmp_buf_size = TMP_BUF_SIZE;
103 impl->buffer_full = 0;
104 impl->continuation_line = 0;
105 impl->lineno = 0;
106 impl->error_count = 0;
107 memset(impl->temp, 0, TMP_BUF_SIZE);
108
109 return (icalparser *)impl;
110}
111
112void icalparser_free(icalparser *parser)
113{
114 icalcomponent *c;
115
116 if (parser->root_component) {
117 icalcomponent_free(parser->root_component);
118 }
119
120 while ((c = icalpvl_pop(parser->components)) != 0) {
122 }
123
124 icalpvl_free(parser->components);
125
127}
128
129void icalparser_set_gen_data(icalparser *parser, void *data)
130{
131 parser->line_gen_data = data;
132}
133
134static char *parser_get_next_char(char c, char *str, int qm)
135{
136 int quote_mode = 0;
137 char *p = str;
138 char next_char = *p;
139 char prev_char = 0;
140
141 size_t charCount = 0;
142 const size_t max_search_chars = icallimit_get(ICAL_LIMIT_PARSE_SEARCH);
143 while (next_char != '\0' && charCount++ < max_search_chars) {
144 if ((prev_char != '\0') && (prev_char != '\\')) {
145 if (qm == 1 && next_char == '"') {
146 /* Encountered a quote, toggle quote mode */
147 quote_mode = !quote_mode;
148 } else if (quote_mode == 0 && next_char == c) {
149 /* Found a matching character out of quote mode, return it */
150 return p;
151 }
152 }
153
154 /* Save the previous character so we can check if it's a backslash in the next iteration */
155 prev_char = next_char;
156 next_char = *(++p);
157 }
158
159 return 0;
160}
161
163static char *make_segment(const char *start, const char *end)
164{
165#if defined(__GNUC__) && !defined(__clang__)
166#pragma GCC diagnostic push
167#pragma GCC diagnostic ignored "-Wstringop-truncation"
168#pragma GCC diagnostic ignored "-Wstringop-overflow"
169#endif
170 char *buf, *tmp;
171 size_t size = (size_t)(ptrdiff_t)(end - start);
172
173 buf = icalmemory_new_buffer(size + 1);
174 strncpy(buf, start, size);
175 *(buf + size) = 0;
176
177 tmp = (buf + size);
178 while ((tmp >= buf) && ((*tmp == '\0') || iswspace((wint_t)*tmp))) {
179 *tmp = 0;
180 tmp--;
181 }
182
183 return buf;
184#if defined(__GNUC__) && !defined(__clang__)
185#pragma GCC diagnostic pop
186#endif
187}
188
189static char *parser_get_prop_name(char *line, char **end)
190{
191 char *p;
192 char *v;
193 char *str;
194
195 p = parser_get_next_char(';', line, 1);
196 v = parser_get_next_char(':', line, 1);
197 if (p == 0 && v == 0) {
198 return 0;
199 }
200
201 /* There is no ';' or, it is after the ';' that marks the beginning of
202 the value */
203 if (v != 0 && (p == 0 || p > v)) {
204 str = make_segment(line, v);
205 *end = v + 1;
206 } else {
207 str = make_segment(line, p);
208 *end = p + 1;
209 }
210
211 return str;
212}
213
214static bool parser_get_param_name_stack(char *line, char *name, size_t name_length,
215 char *value, size_t value_length)
216{
217 char *next;
218 size_t requested_name_length, requested_value_length;
219
220 /* The name is everything up to the equals sign */
221 next = parser_get_next_char('=', line, 1);
222
223 if (next == 0) {
224 return false;
225 }
226
227 requested_name_length = (size_t)(ptrdiff_t)(next - line);
228
229 /* There's not enough room in the name input, we need to fall back
230 to parser_get_param_name_heap and use heap-allocated strings */
231 if (requested_name_length >= name_length - 1) {
232 return false;
233 }
234
235 strncpy(name, line, requested_name_length);
236 name[requested_name_length] = 0;
237
238 icalparameter_kind kind = icalparameter_string_to_kind(name);
239 int is_multivalued = 0;
240 icalparameter_kind_value_kind(kind, &is_multivalued);
241
242 /* Figure out what range of line contains the value (everything after the equals sign) */
243 next++;
244
245 if (next[0] == '"' && !is_multivalued) {
246 /* Dequote the value if it is a single quoted-string */
247 next++;
248
249 const char *end_quote = (*next == '"') ? next : parser_get_next_char('"', next, 0);
250 if (end_quote == 0) {
251 return false;
252 }
253
254 requested_value_length = (size_t)(ptrdiff_t)(end_quote - next);
255 } else {
256 requested_value_length = strlen(next);
257 }
258
259 /* There's not enough room in the value input, we need to fall back
260 to parser_get_param_name_heap and use heap-allocated strings */
261 if (requested_value_length >= value_length - 1) {
262 return false;
263 }
264
265 memcpy(value, next, requested_value_length);
266 value[requested_value_length] = 0;
267
268 if (!is_multivalued) {
270 }
271
272 return true;
273}
274
275static char *parser_get_param_name_heap(char *line, char **end)
276{
277 /* This is similar to parser_get_param_name_stack except it returns heap
278 objects in the return value and the end parameter. This is used in case
279 the name or value is longer than the stack-allocated string.
280 */
281 char *next;
282 char *str;
283
284 next = parser_get_next_char('=', line, 1);
285
286 if (next == 0) {
287 return 0;
288 }
289
290 str = make_segment(line, next);
291
292 icalparameter_kind kind = icalparameter_string_to_kind(str);
293 int is_multivalued = 0;
294 icalparameter_kind_value_kind(kind, &is_multivalued);
295
296 *end = next + 1;
297 if (**end == '"' && !is_multivalued) {
298 *end = *end + 1;
299 next = (**end == '"') ? *end : parser_get_next_char('"', *end, 0);
300 if (next == 0) {
302 *end = NULL;
303 return 0;
304 } else {
305 *end = make_segment(*end, next);
306 }
307 } else {
308 *end = make_segment(*end, *end + strlen(*end));
309 }
310
311 if (!is_multivalued) {
313 }
314
315 return str;
316}
317
318static char *icalparser_get_value(char *line, char **end, icalvalue_kind kind)
319{
320 char *str;
321 size_t length = strlen(line);
322
323 _unused(kind);
324
325 if (length == 0) {
326 return 0;
327 }
328
329 *end = line + length;
330 str = make_segment(line, *end);
331
332 return str;
333}
334
340
341static char *parser_get_next_value(char *line, char **end, icalvalue_kind kind)
342{
343 char *next = 0;
344 char *p;
345 char *str;
346 size_t length = strlen(line);
347 int quoted = 0;
348
349 if (line[0] == '\"' && line[length - 1] == '\"') {
350 /* This line is quoted, don't split into multiple values */
351 quoted = 1;
352 }
353
354 p = line;
355 while (!quoted) {
356 next = parser_get_next_char(',', p, 1);
357
358 /* Unfortunately, RFC2445 allowed that for the RECUR value, COMMA
359 could both separate digits in a list, and it could separate
360 multiple recurrence specifications. This is not a friendly
361 part of the spec and was deprecated in RFC5545. The following
362 weirdness tries to distinguish the two uses. It is probably a HACK */
363
364 if (kind == ICAL_RECUR_VALUE) {
365 if (next != 0 && (*end + length) > next + 5 && strncmp(next, "FREQ", 4) == 0) {
366 /* The COMMA was followed by 'FREQ', is it a real separator */
367 /* Fall through */
368 } else if (next != 0) {
369 /* Not real, get the next COMMA */
370 p = next + 1;
371 next = 0;
372 continue;
373 }
374 }
375 /* ignore all commas for query and x values. select dtstart, dtend etc ... */
376 else if (kind == ICAL_QUERY_VALUE || kind == ICAL_X_VALUE) {
377 if (next != 0) {
378 p = next + 1;
379 continue;
380 } else {
381 break;
382 }
383 }
384
385 /* If the comma is preceded by a '\', then it is a literal and
386 not a value separator */
387
388 if ((next != 0 && *(next - 1) == '\\') || (next != 0 && *(next - 3) == '\\'))
389 /*second clause for '/' is on prev line. HACK may be out of bounds */
390 {
391 p = next + 1;
392 } else {
393 break;
394 }
395 }
396
397 if (next == 0) {
398 next = (char *)(size_t)line + length;
399 *end = next;
400 } else {
401 *end = next + 1;
402 }
403
404 if (next == line) {
405 return 0;
406 }
407
408 str = make_segment(line, next);
409 return str;
410}
411
412static char *parser_get_next_parameter(char *line, char **end)
413{
414 char *next;
415 const char *v;
416
417 v = parser_get_next_char(':', line, 1);
418 next = parser_get_next_char(';', line, 1);
419
420 /* There is no ';' or, it is after the ':' that marks the beginning of
421 the value */
422
423 if (next == 0 || next > v) {
424 next = parser_get_next_char(':', line, 1);
425 }
426
427 if (next != 0) {
428 char *str = make_segment(line, next);
429 *end = next + 1;
430 return str;
431 } else {
432 *end = line;
433 return 0;
434 }
435}
436
437char *icalparser_get_line(icalparser *parser,
438 icalparser_line_gen_func line_gen_func)
439{
440 char *line;
441 char *line_p;
442 size_t buf_size = parser->tmp_buf_size;
443
444 line = icalmemory_new_buffer(buf_size);
445 if (!line) {
446 return NULL;
447 }
448 line_p = line;
449 line[0] = '\0';
450
451 /* Read lines by calling line_gen_func and putting the data into
452 parser->temp. If the line is a continuation line ( begins with a
453 space after a newline ) then append the data onto line and read
454 again. Otherwise, exit the loop. */
455
456 while (1) {
457 /* The first part of the loop deals with the temp buffer,
458 which was read on he last pass through the loop. The
459 routine is split like this because it has to read lone line
460 ahead to determine if a line is a continuation line. */
461
462 /* The tmp buffer is not clear, so transfer the data in it to the
463 output. This may be left over from a previous call */
464 if (parser->temp[0] != '\0') {
465 /* If the last position in the temp buffer is occupied,
466 mark the buffer as full. The means we will do another
467 read later, because the line is not finished */
468 if (parser->temp[parser->tmp_buf_size - 1] == 0 &&
469 parser->temp[parser->tmp_buf_size - 2] != '\n' &&
470 parser->temp[parser->tmp_buf_size - 2] != 0) {
471 parser->buffer_full = 1;
472 } else {
473 parser->buffer_full = 0;
474 }
475
476 /* Copy the temp to the output and clear the temp buffer. */
477 if (parser->continuation_line == 1) {
478 /* back up the pointer to erase the continuation characters */
479 parser->continuation_line = 0;
480 line_p--;
481
482 if (*(line_p - 1) == '\r') {
483 line_p--;
484 }
485
486 /* copy one space up to eliminate the leading space */
487 icalmemory_append_string(&line, &line_p, &buf_size, parser->temp + 1);
488
489 } else {
490 icalmemory_append_string(&line, &line_p, &buf_size, parser->temp);
491 }
492
493 parser->temp[0] = '\0';
494 }
495
496 parser->temp[parser->tmp_buf_size - 1] = 1; /* Mark end of buffer */
497
498 /****** Here is where the routine gets string data ******************/
499 if ((*line_gen_func)(parser->temp, parser->tmp_buf_size, parser->line_gen_data) == 0) { /* Get more data */
500
501 /* If the first position is clear, it means we didn't get
502 any more data from the last call to line_ge_func */
503 if (parser->temp[0] == '\0') {
504 if (line[0] != '\0') {
505 /* There is data in the output, so fall through and process it */
506 break;
507 } else {
508 /* No data in output; return and signal that there
509 is no more input */
511 return 0;
512 }
513 }
514 }
515
516 /* If the output line ends in a '\n' and the temp buffer
517 begins with a ' ' or tab, then the buffer holds a continuation
518 line, so keep reading. RFC 5545, section 3.1 */
519
520 if (line_p > line + 1 && *(line_p - 1) == '\n' && (parser->temp[0] == ' ' || parser->temp[0] == '\t')) {
521 parser->continuation_line = 1;
522
523 } else if (parser->buffer_full == 1) {
524 /* The buffer was filled on the last read, so read again */
525
526 } else {
527 /* Looks like the end of this content line, so break */
528 break;
529 }
530 }
531
532 /* Erase the final newline and/or carriage return */
533 if (line_p > line + 1 && *(line_p - 1) == '\n') {
534 *(line_p - 1) = '\0';
535 if (*(line_p - 2) == '\r') {
536 *(line_p - 2) = '\0';
537 }
538
539 } else {
540 *(line_p) = '\0';
541 }
542
543 while ((*line_p == '\0' || iswspace((wint_t)*line_p)) && line_p > line) {
544 *line_p = '\0';
545 line_p--;
546 }
547
548 return line;
549}
550
551static void insert_error(icalparser *parser, icalcomponent *comp, const char *text,
552 const char *message, icalparameter_xlicerrortype type)
553{
554 char temp[1024];
555
556 if (parser->error_count > icallimit_get(ICAL_LIMIT_PARSE_FAILURE_ERROR_MESSAGES)) {
557 return;
558 }
559
560 if (text == 0) {
561 snprintf(temp, 1024, "%s:", message);
562 } else {
563 snprintf(temp, 1024, "%s: %s", message, text);
564 }
565
566 /* coverity[resource_leak] */
567 icalproperty *errProp = icalproperty_vanew_xlicerror(temp, icalparameter_new_xlicerrortype(type), (void *)0);
568 icalcomponent_add_property(comp, errProp);
569
570 parser->error_count++;
571}
572
573static bool line_is_blank(const char *line)
574{
575 int i = 0;
576
577 for (i = 0; *(line + i) != 0; i++) {
578 char c = *(line + i);
579
580 if (c != ' ' && c != '\n' && c != '\t') {
581 return false;
582 }
583 }
584
585 return true;
586}
587
588icalcomponent *icalparser_parse(icalparser *parser,
589 icalparser_line_gen_func line_gen_func)
590{
591 char *line;
592 icalcomponent *c;
593 icalcomponent *root = 0;
595 bool cont = false;
596
597 icalerror_check_arg_rz((parser != 0), "parser");
598
600
601 /* Maximum number of bad parsed lines allowed */
602 const size_t max_parse_failures = icallimit_get(ICAL_LIMIT_PARSE_FAILURES);
603 size_t parse_failures = 0;
604 do {
605 line = icalparser_get_line(parser, line_gen_func);
606
607 if ((c = icalparser_add_line(parser, line)) != 0) {
608 if (icalcomponent_get_parent(c) != 0) {
609 /* This is bad news... assert? */
610 }
611
612 icalassert(parser->root_component == 0);
613 icalassert(icalpvl_count(parser->components) == 0);
614
615 if (root == 0) {
616 /* Just one component */
617 root = c;
618 } else if (icalcomponent_isa(root) != ICAL_XROOT_COMPONENT) {
619 /*Got a second component, so move the two components under
620 an XROOT container */
621 icalcomponent *tempc = icalcomponent_new(ICAL_XROOT_COMPONENT);
622
623 icalcomponent_add_component(tempc, root);
625 root = tempc;
626 } else if (icalcomponent_isa(root) == ICAL_XROOT_COMPONENT) {
627 /* Already have an XROOT container, so add the component
628 to it */
630
631 } else {
632 /* Badness */
633 icalassert(0);
634 }
635 } else if (parser->state == ICALPARSER_ERROR) {
636 parse_failures++; // track the number of un-parsable data lines
637 }
638 cont = false;
639 if (line != 0) {
641 cont = true;
642 }
643 } while (cont && parse_failures < max_parse_failures); // limit the number of un-parsable data lines
644
646
647 return root;
648}
649
650icalcomponent *icalparser_add_line(icalparser *parser, char *line)
651{
652 char *str;
653 char *end;
654 size_t pcount = 0;
655 size_t vcount = 0;
656 icalproperty *prop;
657 icalproperty_kind prop_kind;
658 icalvalue *value;
659 icalvalue_kind value_kind = ICAL_NO_VALUE;
660
661 icalerror_check_arg_rz((parser != 0), "parser");
662
663 if (line == 0) {
664 parser->state = ICALPARSER_ERROR;
665 return 0;
666 }
667
668 if (line_is_blank(line) == 1) {
669 return 0;
670 }
671
672 if (icalparser_ctrl_g != ICALPARSER_CTRL_KEEP) {
673 static const unsigned char is_icalctrl[256] = {
674 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1,
675 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
676 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
677 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
678 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
679 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
680 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
681 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
682 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
683 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
684 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
685 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
686 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
687 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
688 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
689 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
690
691 char *c, *d;
692 for (c = d = line; *c; c++) {
693 if (!is_icalctrl[(unsigned char)*c]) {
694 *d++ = *c;
695 } else if (icalparser_ctrl_g == ICALPARSER_CTRL_OMIT) {
696 // omit CTRL character
697 } else {
698 icalcomponent *tail = icalpvl_data(icalpvl_tail(parser->components));
699 if (tail) {
700 insert_error(
701 parser, tail, line,
702 "Content line contains invalid CONTROL characters",
703 ICAL_XLICERRORTYPE_COMPONENTPARSEERROR);
704 }
705 parser->state = ICALPARSER_ERROR;
706 return 0;
707 }
708 }
709 *d = '\0';
710 }
711
712 /* Begin by getting the property name at the start of the line. The
713 property name may end up being "BEGIN" or "END" in which case it
714 is not really a property, but the marker for the start or end of
715 a component */
716
717 end = 0;
718 str = parser_get_prop_name(line, &end);
719
720 if (str == 0 || *str == '\0') {
721 /* Could not get a property name */
722 icalcomponent *tail = icalpvl_data(icalpvl_tail(parser->components));
723
724 if (tail) {
725 insert_error(
726 parser, tail, line,
727 "Got a data line, but could not find a property name or component begin tag",
728 ICAL_XLICERRORTYPE_COMPONENTPARSEERROR);
729 }
730 parser->state = ICALPARSER_ERROR;
732 str = NULL;
733 return 0;
734 }
735
736 /**********************************************************************
737 * Handle begin and end of components
738 **********************************************************************/
739 /* If the property name is BEGIN or END, we are actually
740 starting or ending a new component */
741
742 if (strcasecmp(str, "BEGIN") == 0) {
743 icalcomponent *c = NULL;
744 icalcomponent_kind comp_kind;
745
746 parser->level++;
748 str = parser_get_next_value(end, &end, value_kind);
749
750 comp_kind = icalcomponent_string_to_kind(str);
751
752 if (comp_kind == ICAL_X_COMPONENT) {
753 c = icalcomponent_new_x(str);
754 } else if (comp_kind == ICAL_IANA_COMPONENT) {
755 ical_unknown_token_handling tokHandlingSetting =
757 if (tokHandlingSetting == ICAL_ASSUME_IANA_TOKEN) {
758 c = icalcomponent_new_iana(str);
759 }
760 /* ICAL_DISCARD_TOKEN / ICAL_TREAT_AS_ERROR: treat as error */
761 } else {
762 c = icalcomponent_new(comp_kind);
763 }
764
765 if (c == 0) {
767 insert_error(parser, c, str, "Parse error in component name",
768 ICAL_XLICERRORTYPE_COMPONENTPARSEERROR);
769 }
770
771 icalpvl_push(parser->components, c);
772
773 parser->state = ICALPARSER_BEGIN_COMP;
774
776 str = NULL;
777 return 0;
778
779 } else if (strcasecmp(str, "END") == 0) {
780 icalcomponent *tail;
781
782 parser->level--;
784 str = parser_get_next_value(end, &end, value_kind);
785
786 /* Pop last component off of list and add it to the second-to-last */
787 parser->root_component = icalpvl_pop(parser->components);
788
789 tail = icalpvl_data(icalpvl_tail(parser->components));
790
791 if (tail != 0) {
792 icalcomponent_add_component(tail, parser->root_component);
793 }
794
796 str = NULL;
797
798 if (parser->level < 0) {
799 // Encountered an END before any BEGIN, this must be invalid data
800 icalerror_warn("Encountered END before BEGIN");
801 parser->state = ICALPARSER_ERROR;
802 parser->level = 0;
803 return 0;
804 } else if (parser->level == 0) {
805 /* Return the component if we are back to the 0th level */
806 icalcomponent *rtrn;
807
808 if (icalpvl_count(parser->components) != 0) {
809 /* There are still components on the stack -- this means
810 that one of them did not have a proper "END" */
811 icalpvl_push(parser->components, parser->root_component);
812 (void)icalparser_clean(parser); /* may reset parser->root_component */
813 }
814
815 icalassert(icalpvl_count(parser->components) == 0);
816
817 parser->state = ICALPARSER_SUCCESS;
818 rtrn = parser->root_component;
819 parser->root_component = 0;
820 return rtrn;
821
822 } else {
823 parser->state = ICALPARSER_END_COMP;
824 return 0;
825 }
826 }
827
828 /* There is no point in continuing if we have not seen a
829 component yet */
830
831 if (icalpvl_data(icalpvl_tail(parser->components)) == 0) {
832 parser->state = ICALPARSER_ERROR;
834 str = NULL;
835 return 0;
836 }
837
838 /**********************************************************************
839 * Handle property names
840 **********************************************************************/
841
842 /* At this point, the property name really is a property name,
843 (Not a component name) so make a new property and add it to
844 the component */
845
846 prop_kind = icalproperty_string_to_kind(str);
847
848 if (prop_kind == ICAL_IANA_PROPERTY &&
850 prop_kind = ICAL_NO_PROPERTY;
851 }
852
853 prop = icalproperty_new(prop_kind);
854
855 if (prop != 0) {
856 icalcomponent *tail = icalpvl_data(icalpvl_tail(parser->components));
857
858 if (prop_kind == ICAL_X_PROPERTY) {
859 icalproperty_set_x_name(prop, str);
860 } else if (prop_kind == ICAL_IANA_PROPERTY) {
862 }
863
864 icalcomponent_add_property(tail, prop);
865
866 /* Set the value kind for the default for this type of
867 property. This may be re-set by a VALUE parameter */
869
870 } else {
871 icalcomponent *tail = icalpvl_data(icalpvl_tail(parser->components));
872
873 insert_error(parser, tail, str, "Parse error in property name",
874 ICAL_XLICERRORTYPE_PROPERTYPARSEERROR);
875
876 parser->state = ICALPARSER_ERROR;
878 str = NULL;
879 return 0;
880 }
881
883 str = NULL;
884
885 /**********************************************************************
886 * Handle parameter values
887 **********************************************************************/
888
889 /* Now, add any parameters to the last property */
890 const size_t maximum_allowed_parameters = icallimit_get(ICAL_LIMIT_PARAMETERS);
891 while (pcount < maximum_allowed_parameters) {
892 if (*(end - 1) == ':') {
893 /* if the last separator was a ":" and the value is a
894 URL, icalparser_get_next_parameter will find the
895 ':' in the URL, so better break now. */
896 break;
897 }
898
900 str = parser_get_next_parameter(end, &end);
901 strstriplt(str);
902 if (str != 0) {
903 char *name_heap = 0;
904 char *pvalue_heap = 0;
905 char name_stack[TMP_BUF_SIZE];
906 char pvalue_stack[TMP_BUF_SIZE];
907 char *name = name_stack;
908 char *pvalue = pvalue_stack;
909
910 icalparameter *param = 0;
911 icalparameter_kind kind;
912 icalcomponent *tail = icalpvl_data(icalpvl_tail(parser->components));
913
914 if (!parser_get_param_name_stack(str, name_stack, sizeof(name_stack),
915 pvalue_stack, sizeof(pvalue_stack))) {
916 name_heap = parser_get_param_name_heap(str, &pvalue_heap);
917
918 name = name_heap;
919 pvalue = pvalue_heap;
920
921 if (name_heap == 0) {
922 /* 'tail' defined above */
923 insert_error(parser, tail, str, "Can't parse parameter name",
924 ICAL_XLICERRORTYPE_PARAMETERNAMEPARSEERROR);
925 break;
926 }
927 }
928
929 kind = icalparameter_string_to_kind(name);
930
931 if (kind == ICAL_X_PARAMETER) {
932 param = icalparameter_new(ICAL_X_PARAMETER);
933 if (param != 0) {
934 icalparameter_set_xname(param, name);
935 icalparameter_set_xvalue(param, pvalue);
936 }
937 } else if (kind == ICAL_IANA_PARAMETER) {
938 ical_unknown_token_handling tokHandlingSetting =
940 if (tokHandlingSetting == ICAL_DISCARD_TOKEN) {
941 icalmemory_free_buffer(name_heap);
942 name_heap = 0;
943
944 icalmemory_free_buffer(pvalue_heap);
945 pvalue_heap = 0;
946
947 continue;
948 }
949
950 param = icalparameter_new(ICAL_IANA_PARAMETER);
951
952 if (param != 0) {
953 icalparameter_set_xname(param, name);
954 icalparameter_set_xvalue(param, pvalue);
955 }
956 } else if (kind == ICAL_TZID_PARAMETER && *(end - 1) != ';') {
957 /*
958 Special case handling for TZID to work around invalid incoming data.
959 For example, Google Calendar will send back stuff like this:
960 DTSTART;TZID=GMT+05:30:20120904T020000
961
962 In this case we read to the next semicolon or the last colon rather
963 than the first colon. This way the TZID will become GMT+05:30 rather
964 than trying to parse the date-time as 30:20120904T020000.
965
966 This also handles properties that look like this:
967 DTSTART;TZID=GMT+05:30;VALUE=DATE-TIME:20120904T020000
968 */
969 char *lastColon = 0;
970 char *nextColon = end;
971 char *nextSemicolon = parser_get_next_char(';', end, 1);
972
973 /* Find the last colon in the line */
974 do {
975 nextColon = parser_get_next_char(':', nextColon, 1);
976
977 if (nextColon) {
978 lastColon = nextColon;
979 }
980 } while (nextColon);
981
982 if (lastColon && nextSemicolon && nextSemicolon < lastColon) {
983 /*
984 Ensures that we don't read past a semicolon
985
986 Handles the following line:
987 DTSTART;TZID=GMT+05:30;VALUE=DATE-TIME:20120904T020000
988 */
989 lastColon = nextSemicolon;
990 }
991
992 /*
993 Rebuild str so that it includes everything up to the next semicolon
994 or the last colon. So given the above example, str will go from
995 "TZID=GMT+05" to "TZID=GMT+05:30"
996 */
997 if (lastColon && *(lastColon + 1) != 0) {
998 const char *strStart = line + strlen(name) + 2;
999
1000 end = lastColon + 1;
1001
1003 str = make_segment(strStart, end - 1);
1004 }
1005
1006 /* Reparse the parameter name and value with the new segment */
1007 if (!parser_get_param_name_stack(str, name_stack, sizeof(name_stack),
1008 pvalue_stack, sizeof(pvalue_stack))) {
1009 icalmemory_free_buffer(pvalue_heap);
1010 pvalue_heap = 0;
1011
1012 icalmemory_free_buffer(name_heap);
1013 name = 0;
1014 name_heap = parser_get_param_name_heap(str, &pvalue_heap);
1015 pvalue = pvalue_heap;
1016 }
1017 param = icalparameter_new_from_value_string(kind, pvalue);
1018 } else if (kind != ICAL_NO_PARAMETER) {
1019 param = icalparameter_new_from_value_string(kind, pvalue);
1020 } else {
1021 /* Error. Failed to parse the parameter */
1022 /* 'tail' defined above */
1023
1024 /* Change for mozilla */
1025 /* have the option of being flexible towards unsupported parameters */
1027 insert_error(parser, tail, str, "Can't parse parameter name",
1028 ICAL_XLICERRORTYPE_PARAMETERNAMEPARSEERROR);
1029 parser->state = ICALPARSER_ERROR;
1030
1031 icalmemory_free_buffer(pvalue_heap);
1032 icalmemory_free_buffer(name_heap);
1034 str = NULL;
1035 return 0;
1036 } else {
1037 icalmemory_free_buffer(name_heap);
1038 name_heap = 0;
1039 icalmemory_free_buffer(pvalue_heap);
1040 pvalue_heap = 0;
1042 str = NULL;
1043 continue;
1044 }
1045 }
1046
1047 icalmemory_free_buffer(pvalue_heap);
1048 pvalue_heap = 0;
1049
1050 icalmemory_free_buffer(name_heap);
1051 name_heap = 0;
1052
1053 if (param == 0) {
1054 /* 'tail' defined above */
1055 insert_error(parser, tail, str, "Can't parse parameter value",
1056 ICAL_XLICERRORTYPE_PARAMETERVALUEPARSEERROR);
1057
1058 tail = 0;
1059 parser->state = ICALPARSER_ERROR;
1060
1062 str = NULL;
1063
1064 continue;
1065 }
1066
1067 /* If it is a VALUE parameter, set the kind of value */
1068 if (icalparameter_isa(param) == ICAL_VALUE_PARAMETER) {
1069 value_kind =
1070 (icalvalue_kind)icalparameter_value_to_value_kind(
1071 icalparameter_get_value(param));
1072
1073 if (!icalproperty_value_kind_is_valid(prop_kind, value_kind)) {
1074 /* Ooops, invalid VALUE parameter, so reset the value_kind */
1075
1076 const char *err_str = "Invalid VALUE type for property";
1077 const char *prop_str = icalproperty_kind_to_string(prop_kind);
1078 size_t tmp_buf_len = strlen(err_str) + strlen(prop_str) + 2;
1079 char *tmp_buf = icalmemory_tmp_buffer(tmp_buf_len);
1080 snprintf(tmp_buf, tmp_buf_len, "%s %s", err_str, prop_str);
1081
1082 insert_error(parser, tail, str, tmp_buf,
1083 ICAL_XLICERRORTYPE_PARAMETERVALUEPARSEERROR);
1084
1085 value_kind = icalproperty_kind_to_value_kind(prop_kind);
1086
1087 icalparameter_free(param);
1088 tail = 0;
1089 parser->state = ICALPARSER_ERROR;
1090
1092 str = NULL;
1093 pcount++;
1094 continue;
1095 }
1096 }
1097
1098 /* Everything is OK, so add the parameter */
1099 icalproperty_add_parameter(prop, param);
1101 str = NULL;
1102 pcount++;
1103
1104 } else {
1105 /* str is NULL */
1106 break;
1107 }
1108
1109 } /* while(1) */
1110
1111 /**********************************************************************
1112 * Handle values
1113 **********************************************************************/
1114
1115 /* Look for values. If there are ',' characters in the values,
1116 then there are multiple values, so clone the current
1117 parameter and add one part of the value to each clone */
1118
1119 vcount = 0;
1120 const size_t maximum_property_values = icallimit_get(ICAL_LIMIT_PROPERTY_VALUES);
1121 while (vcount < maximum_property_values) {
1122 /* Only some properties can have multiple values. This list was taken
1123 from rfc5545. Also added the x-properties, because the spec actually
1124 says that commas should be escaped. For x-properties, other apps may
1125 depend on that behaviour
1126 */
1128 str = NULL;
1129
1130 if (icalproperty_value_kind_is_multivalued(prop_kind, &value_kind)) {
1131 str = parser_get_next_value(end, &end, value_kind);
1132 } else {
1133 str = icalparser_get_value(end, &end, value_kind);
1134 }
1135 strstriplt(str);
1136
1137 if (str != 0) {
1138 if (vcount > 0) {
1139 /* Actually, only clone after the second value */
1140 icalproperty *clone = icalproperty_clone(prop);
1141 icalcomponent *tail = icalpvl_data(icalpvl_tail(parser->components));
1142
1143 icalcomponent_add_property(tail, clone);
1144 prop = clone;
1145 }
1146
1147 value = icalvalue_new_from_string(value_kind, str);
1148
1149 /* Don't add properties without value */
1150 if (value == 0) {
1151 char temp[200]; /* HACK */
1152
1153 icalproperty_kind isa_prop_kind = icalproperty_isa(prop);
1154 icalcomponent *tail = icalpvl_data(icalpvl_tail(parser->components));
1155
1156 snprintf(temp, sizeof(temp),
1157 "Can't parse as %s value in %s property. Removing entire property",
1158 icalvalue_kind_to_string(value_kind),
1159 icalproperty_kind_to_string(isa_prop_kind));
1160
1161 insert_error(parser, tail, str, temp, ICAL_XLICERRORTYPE_VALUEPARSEERROR);
1162
1163 /* Remove the troublesome property */
1165 icalproperty_free(prop);
1166 parser->state = ICALPARSER_ERROR;
1167
1169 str = NULL;
1170 return 0;
1171
1172 } else {
1173 vcount++;
1174 icalproperty_set_value(prop, value);
1175 }
1177 str = NULL;
1178
1179 } else {
1181 /* Don't replace empty properties with an error.
1182 Set an empty length string (not null) as the value instead */
1183 if (vcount == 0) {
1184 icalproperty_set_value(prop, icalvalue_new(ICAL_NO_VALUE));
1185 }
1186 break;
1187 } else {
1188 if (vcount == 0) {
1189 char temp[200]; /* HACK */
1190
1191 icalproperty_kind isa_prop_kind = icalproperty_isa(prop);
1192 icalcomponent *tail = icalpvl_data(icalpvl_tail(parser->components));
1193
1194 snprintf(temp, sizeof(temp), "No value for %s property. Removing entire property",
1195 icalproperty_kind_to_string(isa_prop_kind));
1196
1197 insert_error(parser, tail, str, temp, ICAL_XLICERRORTYPE_VALUEPARSEERROR);
1198
1199 /* Remove the troublesome property */
1201 icalproperty_free(prop);
1202 parser->state = ICALPARSER_ERROR;
1203 return 0;
1204 } else {
1205 break;
1206 }
1207 }
1208 }
1209 }
1210
1211 /****************************************************************
1212 * End of component parsing.
1213 *****************************************************************/
1214
1215 if (icalpvl_data(icalpvl_tail(parser->components)) == 0 && parser->level == 0) {
1216 /* HACK. Does this clause ever get executed? */
1217 parser->state = ICALPARSER_SUCCESS;
1218 icalassert(0);
1219 return parser->root_component;
1220 } else {
1221 parser->state = ICALPARSER_IN_PROGRESS;
1222 return 0;
1223 }
1224}
1225
1227{
1228 return parser->state;
1229}
1230
1231icalcomponent *icalparser_clean(icalparser *parser)
1232{
1233 icalcomponent *tail;
1234
1235 icalerror_check_arg_rz((parser != 0), "parser");
1236
1237 /* We won't get a clean exit if some components did not have an
1238 "END" tag. Clear off any component that may be left in the list */
1239
1240 while ((tail = icalpvl_data(icalpvl_tail(parser->components))) != 0) {
1241 insert_error(parser, tail, " ",
1242 "Missing END tag for this component. Closing component at end of input.",
1243 ICAL_XLICERRORTYPE_COMPONENTPARSEERROR);
1244
1245 parser->root_component = icalpvl_pop(parser->components);
1246 tail = icalpvl_data(icalpvl_tail(parser->components));
1247
1248 if (tail != 0 && parser->root_component != NULL) {
1249 if (icalcomponent_get_parent(parser->root_component) != 0) {
1250 icalerror_warn(
1251 "icalparser_clean is trying to attach a component for the second time");
1252 } else {
1253 icalcomponent_add_component(tail, parser->root_component);
1254 }
1255 }
1256 }
1257
1258 return parser->root_component;
1259}
1260
1261struct slg_data {
1262 const char *pos;
1263 const char *str;
1264};
1265
1266char *icalparser_string_line_generator(char *out, size_t buf_size, void *d)
1267{
1268 int replace_cr = 0;
1269 const char *n;
1270 size_t size;
1271 struct slg_data *data = (struct slg_data *)d;
1272
1273 if (data->pos == 0) {
1274 data->pos = data->str;
1275 if (data->pos && strlen(data->pos) > 2) {
1276 /* Skip the UTF-8 marker at the beginning of the string */
1277 if (((unsigned char)data->pos[0]) == 0xEF &&
1278 ((unsigned char)data->pos[1]) == 0xBB &&
1279 ((unsigned char)data->pos[2]) == 0xBF) {
1280 data->pos += 3;
1281 }
1282 }
1283 }
1284
1285 /* If the pointer is at the end of the string, we are done */
1286 if (!data->pos || *(data->pos) == 0) {
1287 return 0;
1288 }
1289
1290 n = strchr(data->pos, '\n');
1291
1292 if (n == 0) {
1293 n = strchr(data->pos, '\r'); /* support malformed input with only CR and no LF
1294 (e.g. from Kerio Connect Server) */
1295 if (n == 0) {
1296 size = strlen(data->pos);
1297 } else {
1298 n++; /* include CR in output - will be replaced by LF later on */
1299 replace_cr = 1;
1300 size = (size_t)(ptrdiff_t)(n - data->pos);
1301 }
1302 } else {
1303 n++; /* include newline in output */
1304 size = (size_t)(ptrdiff_t)(n - data->pos);
1305 }
1306
1307 if (size > buf_size - 1) {
1308 size = buf_size - 1;
1309 }
1310
1311#if defined(__GNUC__) && !defined(__clang__)
1312#pragma GCC diagnostic push
1313#pragma GCC diagnostic ignored "-Wstringop-truncation"
1314#pragma GCC diagnostic ignored "-Wstringop-overflow"
1315#endif
1316 strncpy(out, data->pos, size);
1317#if defined(__GNUC__) && !defined(__clang__)
1318#pragma GCC diagnostic pop
1319#endif
1320
1321 if (replace_cr) {
1322 *(out + size - 1) = '\n';
1323 }
1324 *(out + size) = '\0';
1325
1326 data->pos += size;
1327
1328 return out;
1329}
1330
1331icalcomponent *icalparser_parse_string(const char *str)
1332{
1333 icalcomponent *c;
1334 struct slg_data d;
1335 icalparser *p;
1336
1338
1339 d.pos = 0;
1340 d.str = str;
1341
1342 p = icalparser_new();
1343 if (!p) {
1344 return NULL;
1345 }
1346
1348
1350
1352
1354
1355 icalparser_free(p);
1356
1357 return c;
1358}
1359
1361{
1362 return icalparser_ctrl_g;
1363}
1364
1366{
1367 icalparser_ctrl_g = ctrl;
1368}
icalcomponent_kind icalcomponent_string_to_kind(const char *string)
void icalcomponent_remove_property(icalcomponent *component, icalproperty *property)
icalcomponent * icalcomponent_new(icalcomponent_kind kind)
icalcomponent * icalcomponent_new_iana(const char *iana_name)
icalcomponent * icalcomponent_new_x(const char *x_name)
void icalcomponent_add_property(icalcomponent *component, icalproperty *property)
icalcomponent_kind icalcomponent_isa(const icalcomponent *component)
void icalcomponent_free(icalcomponent *c)
void icalcomponent_add_component(icalcomponent *parent, icalcomponent *child)
icalcomponent_kind
Definition icalenums.h:29
@ ICAL_X_COMPONENT
Definition icalenums.h:48
@ ICAL_XROOT_COMPONENT
Definition icalenums.h:32
@ ICAL_IANA_COMPONENT
Definition icalenums.h:67
@ ICAL_XLICINVALID_COMPONENT
Definition icalenums.h:54
bool icalerror_get_errors_are_fatal(void)
Determine if errors are fatal.
Definition icalerror.c:36
void icalerror_set_errno(icalerrorenum x)
Sets the icalerrno to a given error.
Definition icalerror.c:90
Error handling for libical.
icalerrorstate
Determine if an error is fatal or non-fatal.
Definition icalerror.h:86
@ ICAL_ERROR_NONFATAL
Definition icalerror.h:91
icalerrorstate icalerror_get_error_state(icalerrorenum error)
Gets the error state (severity) for a given error.
@ ICAL_NEWFAILED_ERROR
Definition icalerror.h:50
@ ICAL_MALFORMEDDATA_ERROR
Definition icalerror.h:56
void icalerror_set_error_state(icalerrorenum error, icalerrorstate state)
Sets the icalerrorstate for a given icalerrorenum error.
size_t icallimit_get(icallimits_kind kind)
Definition icallimits.c:29
Defines the interface for getting/setting internal library limits.
@ ICAL_LIMIT_PARSE_FAILURE_ERROR_MESSAGES
Definition icallimits.h:28
@ ICAL_LIMIT_PROPERTY_VALUES
Definition icallimits.h:36
@ ICAL_LIMIT_PARAMETERS
Definition icallimits.h:32
@ ICAL_LIMIT_PARSE_FAILURES
Definition icallimits.h:24
@ ICAL_LIMIT_PARSE_SEARCH
Definition icallimits.h:26
void icalmemory_free_buffer(void *buf)
Releases a buffer.
Definition icalmemory.c:353
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_tmp_buffer(size_t size)
Creates a new temporary buffer on the ring and returns it.
Definition icalmemory.c:180
Common memory management routines.
void icalparameter_free(icalparameter *param)
Frees an icalparameter object.
icalparameter * icalparameter_new(icalparameter_kind kind)
Creates new icalparameter object.
void icalparameter_set_xname(icalparameter *param, const char *v)
Sets the X-name of param to v.
icalparameter_kind icalparameter_isa(const icalparameter *parameter)
void icalparameter_decode_value(char *value)
void icalparameter_set_xvalue(icalparameter *param, const char *v)
Sets the X-value of param to v.
Defines the data structure representing iCalendar parameters.
icalparameter_kind icalparameter_string_to_kind(const char *string)
Returns the icalparameter_kind for a given string.
icalvalue_kind icalparameter_kind_value_kind(const icalparameter_kind kind, int *is_multivalued)
icalparameter * icalparameter_new_from_value_string(icalparameter_kind kind, const char *value)
Creates new icalparameter of a given kind with a given value.
icalcomponent * icalparser_parse(icalparser *parser, icalparser_line_gen_func line_gen_func)
Message oriented parsing.
Definition icalparser.c:588
icalcomponent * icalparser_add_line(icalparser *parser, char *line)
Adds a single line to be parsed by the icalparser.
Definition icalparser.c:650
void icalparser_free(icalparser *parser)
Frees an icalparser object.
Definition icalparser.c:112
void icalparser_set_ctrl(enum icalparser_ctrl ctrl)
Set the parser setting how to handle CONTROL characters.
enum icalparser_ctrl icalparser_get_ctrl(void)
Get the current parser setting how to handle CONTROL characters.
icalcomponent * icalparser_parse_string(const char *str)
Parses a string and returns the parsed icalcomponent.
char * icalparser_string_line_generator(char *out, size_t buf_size, void *d)
icalparser * icalparser_new(void)
Creates a new icalparser.
Definition icalparser.c:89
icalcomponent * icalparser_clean(icalparser *parser)
Cleans out an icalparser and returns whatever it has parsed so far.
void icalparser_set_gen_data(icalparser *parser, void *data)
Sets the data that icalparser_parse will give to the line_gen_func as the parameter 'd'.
Definition icalparser.c:129
char * icalparser_get_line(icalparser *parser, icalparser_line_gen_func line_gen_func)
Given a line generator function, returns a single iCal content line.
Definition icalparser.c:437
icalparser_state icalparser_get_state(const icalparser *parser)
Returns current state of the icalparser.
Line-oriented parsing.
icalparser_ctrl
Defines how to handle invalid CONTROL characters in content lines.
Definition icalparser.h:306
@ ICALPARSER_CTRL_OMIT
Definition icalparser.h:310
@ ICALPARSER_CTRL_KEEP
Definition icalparser.h:308
icalparser_state
Represents the current state of the parser.
Definition icalparser.h:40
@ ICALPARSER_BEGIN_COMP
Definition icalparser.h:48
@ ICALPARSER_SUCCESS
Definition icalparser.h:45
@ ICALPARSER_END_COMP
Definition icalparser.h:51
@ ICALPARSER_IN_PROGRESS
Definition icalparser.h:54
@ ICALPARSER_ERROR
Definition icalparser.h:42
void icalproperty_free(icalproperty *p)
void icalproperty_set_value(icalproperty *p, icalvalue *value)
icalproperty_kind icalproperty_isa(const icalproperty *p)
bool icalproperty_get_allow_empty_properties(void)
void icalproperty_add_parameter(icalproperty *p, icalparameter *parameter)
icalproperty * icalproperty_clone(const icalproperty *old)
icalproperty * icalproperty_new(icalproperty_kind kind)
void icalproperty_set_iana_name(icalproperty *prop, const char *name)
void icalproperty_set_x_name(icalproperty *prop, const char *name)
icalproperty_kind icalproperty_string_to_kind(const char *string)
icalvalue_kind icalparameter_value_to_value_kind(icalparameter_value value)
icalvalue_kind icalproperty_kind_to_value_kind(icalproperty_kind kind)
const char * icalproperty_kind_to_string(icalproperty_kind kind)
ical_unknown_token_handling ical_get_unknown_token_handling_setting(void)
Definition icaltypes.c:188
ical_unknown_token_handling
Definition icaltypes.h:155
@ ICAL_DISCARD_TOKEN
Definition icaltypes.h:159
@ ICAL_ASSUME_IANA_TOKEN
Definition icaltypes.h:157
icalvalue * icalvalue_new_from_string(icalvalue_kind kind, const char *str)
Definition icalvalue.c:788
icalvalue * icalvalue_new(icalvalue_kind kind)
Definition icalvalue.c:60
Defines the data structure representing iCalendar parameter values.
const char * icalvalue_kind_to_string(const icalvalue_kind kind)