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