/* icalparse.y -- icalendar (RFC 5545) parser * * This code is Copyright (c) 2014, by the authors of nmh. See the * COPYRIGHT file in the root directory of the nmh distribution for * complete copyright information. */ %{ /* * Use these yy* #defines, instead of the -p command line * option, to allow multiple parsers in a program. yyval * is generated by Solaris yacc and is of type YYSTYPE. * All other yy* symbols are data of a built-in type and * are initialized by yyparse(), so they can be shared * between different parsers. */ #define yydebug icaldebug #define yyerror icalerror #define yylex icallex #define yylval icallval #define yyval icalval #define yyparse icalparse #define YYDEBUG 1 #define YYERROR_DEBUG #define YYERROR_VERBOSE #define YY_NO_LEAKS /* * To quiet compile warnings with Solaris yacc: #ifdef sun # define lint 1 # undef YYDEBUG #endif */ #include "h/mh.h" #include "sbr/error.h" #include "h/icalendar.h" #include "h/utils.h" static char *append (contentline *, const char *, const size_t); static void new_content_line (contentline **); static void new_vevent (vevent *); static void free_param_names (param_list *); static void free_param_values (value_list *); static int icalerror (const char *); %} %token ICAL_NAME ICAL_COLON ICAL_VALUE ICAL_CRLF ICAL_SEMICOLON %token ICAL_PARAM_NAME ICAL_EQUAL ICAL_PARAM_VALUE ICAL_COMMA %start contentline_list %% /* Instead of rigorous definition, cheat based on fact that every icalbody line looks like a contentline. And we don't need to parse values. */ contentline_list : contentline | contentline_list contentline /* contentline = name *(";" param ) ":" value CRLF */ contentline : ICAL_NAME { new_content_line (&vevents.last->contentlines); append (vevents.last->contentlines->last, $1, strlen ($1)); vevents.last->contentlines->last->name = $1; } ICAL_COLON { append (vevents.last->contentlines->last, $3, strlen ($3)); } ICAL_VALUE { append (vevents.last->contentlines->last, $5, strlen ($5)); vevents.last->contentlines->last->value = $5; } ICAL_CRLF { append (vevents.last->contentlines->last, $7, strlen ($7)); if (vevents.last->contentlines->cr_before_lf == CR_UNSET) { vevents.last->contentlines->cr_before_lf = $7[0] == '\r' ? CR_BEFORE_LF : LF_ONLY; } /* END:VEVENT doesn't have a param_list so we don't need to check for it below. */ if (vevents.last->contentlines->last->name && vevents.last->contentlines->last->value && ! strcasecmp (vevents.last->contentlines->last->name, "END") && ! strcasecmp (vevents.last->contentlines->last->value, "VEVENT")) { new_vevent (&vevents); } } | ICAL_NAME { new_content_line (&vevents.last->contentlines); append (vevents.last->contentlines->last, $1, strlen ($1)); vevents.last->contentlines->last->name = $1; } param_list ICAL_COLON { append (vevents.last->contentlines->last, $4, strlen ($4)); } ICAL_VALUE { append (vevents.last->contentlines->last, $6, strlen ($6)); vevents.last->contentlines->last->value = $6; } ICAL_CRLF { append (vevents.last->contentlines->last, $8, strlen ($8)); if (vevents.last->contentlines->cr_before_lf == CR_UNSET) { vevents.last->contentlines->cr_before_lf = $8[0] == '\r' ? CR_BEFORE_LF : LF_ONLY; } } param_list : ICAL_SEMICOLON { append (vevents.last->contentlines->last, $1, strlen ($1)); } param | param_list ICAL_SEMICOLON { append (vevents.last->contentlines->last, $2, strlen ($2)); } param /* param = param-name "=" param-value *("," param-value) */ param : ICAL_PARAM_NAME { append (vevents.last->contentlines->last, $1, strlen ($1)); add_param_name (vevents.last->contentlines->last, $1); } ICAL_EQUAL { append (vevents.last->contentlines->last, $3, strlen ($3)); } param_value_list param_value_list : ICAL_PARAM_VALUE { append (vevents.last->contentlines->last, $1, strlen ($1)); add_param_value (vevents.last->contentlines->last, $1); } | param_value_list ICAL_COMMA { append (vevents.last->contentlines->last, $2, strlen ($2)); } ICAL_PARAM_VALUE { append (vevents.last->contentlines->last, $4, strlen ($4)); add_param_value (vevents.last->contentlines->last, $4); } %% /* * Remove the contentline node (by setting its name to NULL). */ void remove_contentline (contentline *node) { free (node->name); node->name = NULL; } contentline * add_contentline (contentline *node, const char *name) { contentline *new_node; NEW0(new_node); new_node->name = mh_xstrdup (name); new_node->next = node->next; node->next = new_node; return new_node; } /* * Remove the value from a value_list. */ void remove_value (value_list *node) { free (node->value); node->value = NULL; } /* * Find the contentline with the specified name, and optionally, * the specified value and/or parameter name. */ contentline * find_contentline (contentline *contentlines, const char *name, const char *val) { contentline *node; for (node = contentlines; node; node = node->next) { /* node->name will be NULL if the line was "deleted". */ if (node->name && ! strcasecmp (name, node->name)) { if (!val || !node->value || !strcasecmp(val, node->value)) return node; } } return NULL; } static char * append (contentline *cline, const char *src, const size_t src_len) { if (src_len > 0) { const size_t len = cline->input_line_len + src_len; while (len >= cline->input_line_size) { cline->input_line_size = cline->input_line_size == 0 ? NMH_BUFSIZ : 2 * cline->input_line_size; cline->input_line = mh_xrealloc (cline->input_line, cline->input_line_size); } memcpy (cline->input_line + cline->input_line_len, src, src_len); cline->input_line[len] = '\0'; cline->input_line_len = len; } return cline->input_line; } static void new_content_line (contentline **cline) { contentline *new_node; NEW0(new_node); if (*cline) { /* Append the new node to the end of the list. */ (*cline)->last->next = new_node; } else { /* First line: save the root node in *cline. */ *cline = new_node; } /* Only maintain the pointer to the last node in the root node. */ (*cline)->last = new_node; } static void new_vevent (vevent *event) { vevent *new_node, *node; NEW0(new_node); /* Append the new node to the end of the list. */ for (node = event; node->next; node = node->next) { continue; } event->last = node->next = new_node; } void add_param_name (contentline *cline, char *name) { param_list *new_node; param_list *p; NEW0(new_node); new_node->param_name = name; if (cline->params) { for (p = cline->params; p->next; p = p->next) { continue; } /* The loop terminated at, not after, the last node. */ p->next = new_node; } else { cline->params = new_node; } } /* * Add a value to the last parameter seen. */ void add_param_value (contentline *cline, char *value) { value_list *new_node; param_list *p; value_list *v; NEW0(new_node); new_node->value = value; if (cline->params) { for (p = cline->params; p->next; p = p->next) { continue; } /* The loop terminated at, not after, the last param_list node. */ if (p->values) { for (v = p->values; v->next; v = v->next) { continue; } /* The loop terminated at, not after, the last value_list node. */ v->next = new_node; } else { p->values = new_node; } } else { /* Never should get here because a param value is always preceded by a param name. */ free (new_node); } } void free_contentlines (contentline *root) { contentline *i, *next; for (i = root; i; i = next) { free (i->name); if (i->params) { free_param_names (i->params); } free (i->value); free (i->input_line); charstring_free (i->unexpected); next = i->next; free (i); } } static void free_param_names (param_list *p) { param_list *next; for ( ; p; p = next) { free (p->param_name); free_param_values (p->values); next = p->next; free (p); } } static void free_param_values (value_list *v) { value_list *next; for ( ; v; v = next) { free (v->value); next = v->next; free (v); } } static int icalerror (const char *error) { contentline *c; charstring_t context = NULL; /* Find last chunk of unexpected text. */ for (c = vevents.last->contentlines; c; c = c->next) { if (c->unexpected) { context = c->unexpected; } } if (! strcmp ("syntax error, unexpected $end, expecting ICAL_NAME", error)) { /* Empty input: produce no output. */ } else { if (context) { inform ("%s after \"%s\"", error, charstring_buffer (context)); } else { inform ("%s", error); } parser_status = -1; return -1; } return 0; /* The return value isn't used anyway. */ } /* * In case YYDEBUG is disabled above; mhical refers to icaldebug. */ #if ! defined YYDEBUG || ! YYDEBUG int icaldebug; #endif /* ! YYDEBUG */