1 /* icalparse.y -- icalendar (RFC 5545) parser
2  *
3  * This code is Copyright (c) 2014, by the authors of nmh.  See the
4  * COPYRIGHT file in the root directory of the nmh distribution for
5  * complete copyright information.
6  */
7 
8 %{
9     /*
10      * Use these yy* #defines, instead of the -p command line
11      * option, to allow multiple parsers in a program.  yyval
12      * is generated by Solaris yacc and is of type YYSTYPE.
13      * All other yy* symbols are data of a built-in type and
14      * are initialized by yyparse(), so they can be shared
15      * between different parsers.
16      */
17 #define yydebug icaldebug
18 #define yyerror icalerror
19 #define yylex   icallex
20 #define yylval  icallval
21 #define yyval   icalval
22 #define yyparse icalparse
23 #define YYDEBUG 1
24 #define YYERROR_DEBUG
25 #define YYERROR_VERBOSE
26 #define YY_NO_LEAKS
27 
28     /*
29      * To quiet compile warnings with Solaris yacc:
30 #ifdef sun
31 # define lint 1
32 # undef YYDEBUG
33 #endif
34     */
35 
36 #include "h/mh.h"
37 #include "h/icalendar.h"
38 #include "h/utils.h"
39 
40 static char *append (contentline *, const char *, const size_t);
41 static void new_content_line (contentline **);
42 static void new_vevent (vevent *);
43 static void free_param_names (param_list *);
44 static void free_param_values (value_list *);
45 static int icalerror (const char *);
46 %}
47 
48 %token ICAL_NAME ICAL_COLON ICAL_VALUE ICAL_CRLF ICAL_SEMICOLON
49 %token ICAL_PARAM_NAME ICAL_EQUAL ICAL_PARAM_VALUE ICAL_COMMA
50 
51 %start contentline_list
52 
53 %%
54 
55     /* Instead of rigorous definition, cheat based on fact that every
56        icalbody line looks like a contentline.  And we don't need to
57        parse values. */
58 contentline_list
59     : contentline
60     | contentline_list contentline
61 
62     /* contentline = name *(";" param ) ":" value CRLF */
63 contentline
64     : ICAL_NAME {
65           new_content_line (&vevents.last->contentlines);
66           append (vevents.last->contentlines->last, $1, strlen ($1));
67           vevents.last->contentlines->last->name = $1;
68       } ICAL_COLON {
69           append (vevents.last->contentlines->last, $3, strlen ($3));
70       } ICAL_VALUE {
71           append (vevents.last->contentlines->last, $5, strlen ($5));
72           vevents.last->contentlines->last->value = $5;
73       } ICAL_CRLF {
74           append (vevents.last->contentlines->last, $7, strlen ($7));
75           if (vevents.last->contentlines->cr_before_lf == CR_UNSET) {
76               vevents.last->contentlines->cr_before_lf =
77                   $7[0] == '\r'  ?  CR_BEFORE_LF  :  LF_ONLY;
78           }
79           /* END:VEVENT doesn't have a param_list so we don't need
80              to check for it below. */
81           if (vevents.last->contentlines->last->name  &&
82               vevents.last->contentlines->last->value  &&
83               ! strcasecmp (vevents.last->contentlines->last->name, "END")  &&
84               ! strcasecmp (vevents.last->contentlines->last->value,
85                             "VEVENT")) {
86               new_vevent (&vevents);
87           }
88       }
89     | ICAL_NAME {
90           new_content_line (&vevents.last->contentlines);
91           append (vevents.last->contentlines->last, $1, strlen ($1));
92           vevents.last->contentlines->last->name = $1;
93       } param_list ICAL_COLON {
94           append (vevents.last->contentlines->last, $4, strlen ($4));
95       } ICAL_VALUE {
96           append (vevents.last->contentlines->last, $6, strlen ($6));
97           vevents.last->contentlines->last->value = $6;
98       } ICAL_CRLF {
99           append (vevents.last->contentlines->last, $8, strlen ($8));
100           if (vevents.last->contentlines->cr_before_lf == CR_UNSET) {
101               vevents.last->contentlines->cr_before_lf =
102                   $8[0] == '\r'  ?  CR_BEFORE_LF  :  LF_ONLY;
103           }
104       }
105 
106 param_list
107     : ICAL_SEMICOLON {
108           append (vevents.last->contentlines->last, $1, strlen ($1));
109       } param
110     | param_list ICAL_SEMICOLON {
111           append (vevents.last->contentlines->last, $2, strlen ($2));
112       } param
113 
114     /* param = param-name "=" param-value *("," param-value) */
115 param
116     : ICAL_PARAM_NAME {
117           append (vevents.last->contentlines->last, $1, strlen ($1));
118           add_param_name (vevents.last->contentlines->last, $1);
119       } ICAL_EQUAL {
120           append (vevents.last->contentlines->last, $3, strlen ($3));
121       } param_value_list
122 
123 param_value_list
124     : ICAL_PARAM_VALUE {
125           append (vevents.last->contentlines->last, $1, strlen ($1));
126           add_param_value (vevents.last->contentlines->last, $1);
127       }
128     | param_value_list ICAL_COMMA {
129           append (vevents.last->contentlines->last, $2, strlen ($2));
130       } ICAL_PARAM_VALUE {
131           append (vevents.last->contentlines->last, $4, strlen ($4));
132           add_param_value (vevents.last->contentlines->last, $4);
133       }
134 
135 %%
136 
137 /*
138  * Remove the contentline node (by setting its name to NULL).
139  */
140 void
141 remove_contentline (contentline *node) {
142     free (node->name);
143     node->name = NULL;
144 }
145 
146 contentline *
add_contentline(contentline * node,const char * name)147 add_contentline (contentline *node, const char *name) {
148     contentline *new_node;
149 
150     NEW0(new_node);
151     new_node->name  = mh_xstrdup (name);
152     new_node->next = node->next;
153     node->next = new_node;
154 
155     return new_node;
156 }
157 
158 /*
159  * Remove the value from a value_list.
160  */
161 void
remove_value(value_list * node)162 remove_value (value_list *node) {
163     free (node->value);
164     node->value = NULL;
165 }
166 
167 /*
168  * Find the contentline with the specified name, and optionally,
169  * the specified value and/or parameter name.
170  */
171 contentline *
find_contentline(contentline * contentlines,const char * name,const char * val)172 find_contentline (contentline *contentlines, const char *name,
173                   const char *val) {
174     contentline *node;
175 
176     for (node = contentlines; node; node = node->next) {
177         /* node->name will be NULL if the line was "deleted". */
178         if (node->name  &&  ! strcasecmp (name, node->name)) {
179             if (val  &&  node->value) {
180                 if (! strcasecmp (val, node->value)) {
181                     return node;
182                 }
183             } else {
184                 return node;
185             }
186         }
187     }
188 
189     return NULL;
190 }
191 
192 static char *
append(contentline * cline,const char * src,const size_t src_len)193 append (contentline *cline, const char *src, const size_t src_len) {
194     if (src_len > 0) {
195         const size_t len = cline->input_line_len + src_len;
196 
197         while (len >= cline->input_line_size) {
198             cline->input_line_size = cline->input_line_size == 0
199                 ?  NMH_BUFSIZ
200                 :  2 * cline->input_line_size;
201             cline->input_line =
202                 mh_xrealloc (cline->input_line, cline->input_line_size);
203         }
204 
205         memcpy (cline->input_line + cline->input_line_len, src, src_len);
206         cline->input_line[len] = '\0';
207         cline->input_line_len = len;
208     }
209 
210     return cline->input_line;
211 }
212 
213 static void
new_content_line(contentline ** cline)214 new_content_line (contentline **cline) {
215     contentline *new_node;
216 
217     NEW0(new_node);
218     if (*cline) {
219         /* Append the new node to the end of the list. */
220         (*cline)->last->next = new_node;
221     } else {
222         /* First line:  save the root node in *cline. */
223         *cline = new_node;
224     }
225 
226     /* Only maintain the pointer to the last node in the root node. */
227     (*cline)->last = new_node;
228 }
229 
230 static void
new_vevent(vevent * event)231 new_vevent (vevent *event) {
232     vevent *new_node, *node;
233 
234     NEW0(new_node);
235 
236     /* Append the new node to the end of the list. */
237     for (node = event; node->next; node = node->next) { continue; }
238     event->last =  node->next = new_node;
239 }
240 
241 void
add_param_name(contentline * cline,char * name)242 add_param_name (contentline *cline, char *name) {
243     param_list *new_node;
244     param_list *p;
245 
246     NEW0(new_node);
247     new_node->param_name = name;
248 
249     if (cline->params) {
250         for (p = cline->params; p->next; p = p->next) { continue; }
251         /* The loop terminated at, not after, the last node. */
252         p->next = new_node;
253     } else {
254         cline->params = new_node;
255     }
256 }
257 
258 /*
259  * Add a value to the last parameter seen.
260  */
261 void
add_param_value(contentline * cline,char * value)262 add_param_value (contentline *cline, char *value) {
263     value_list *new_node;
264     param_list *p;
265     value_list *v;
266 
267     NEW0(new_node);
268     new_node->value = value;
269 
270     if (cline->params) {
271         for (p = cline->params; p->next; p = p->next) { continue; }
272         /* The loop terminated at, not after, the last param_list node. */
273 
274         if (p->values) {
275             for (v = p->values; v->next; v = v->next) { continue; }
276             /* The loop terminated at, not after, the last value_list node. */
277             v->next = new_node;
278         } else {
279             p->values = new_node;
280         }
281     } else {
282         /* Never should get here because a param value is always
283            preceded by a param name. */
284         free (new_node);
285     }
286 }
287 
288 void
free_contentlines(contentline * root)289 free_contentlines (contentline *root) {
290     contentline *i, *next;
291 
292     for (i = root; i; i = next) {
293         free (i->name);
294         if (i->params) {
295             free_param_names (i->params);
296         }
297         free (i->value);
298         free (i->input_line);
299         charstring_free (i->unexpected);
300         next = i->next;
301         free (i);
302     }
303 }
304 
305 static void
free_param_names(param_list * p)306 free_param_names (param_list *p) {
307     param_list *next;
308 
309     for ( ; p; p = next) {
310         free (p->param_name);
311         free_param_values (p->values);
312         next = p->next;
313         free (p);
314     }
315 }
316 
317 static void
free_param_values(value_list * v)318 free_param_values (value_list *v) {
319     value_list *next;
320 
321     for ( ; v; v = next) {
322         free (v->value);
323         next = v->next;
324         free (v);
325     }
326 }
327 
328 static int
icalerror(const char * error)329 icalerror (const char *error) {
330     contentline *c;
331     charstring_t context = NULL;
332 
333     /* Find last chunk of unexpected text. */
334     for (c = vevents.last->contentlines; c; c = c->next) {
335         if (c->unexpected) {
336             context = c->unexpected;
337         }
338     }
339 
340     if (! strcmp ("syntax error, unexpected $end, expecting ICAL_NAME",
341                   error)) {
342         /* Empty input: produce no output. */
343     } else {
344         if (context) {
345             inform ("%s after \"%s\"", error, charstring_buffer (context));
346         } else {
347             inform ("%s", error);
348         }
349         parser_status = -1;
350         return -1;
351     }
352 
353     return 0;  /* The return value isn't used anyway. */
354 }
355 
356 /*
357  * In case YYDEBUG is disabled above; mhical refers to icaldebug.
358  */
359 #if ! defined YYDEBUG  ||  ! YYDEBUG
360 int icaldebug;
361 #endif /* ! YYDEBUG */
362