ical.c - ics2txt - convert icalendar .ics file to plain text
(HTM) git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ics2txt
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Tags
(DIR) README
---
ical.c (7209B)
---
1 #include "ical.h"
2 #include <assert.h>
3 #include <ctype.h>
4 #include <errno.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <strings.h>
9 #include "util.h"
10 #include "base64.h"
11
12 char *ical_block_name[ICAL_BLOCK_OTHER + 1] = {
13 [ICAL_BLOCK_VEVENT] = "VEVENT",
14 [ICAL_BLOCK_VTODO] = "VTODO",
15 [ICAL_BLOCK_VJOURNAL] = "VJOURNAL",
16 [ICAL_BLOCK_VFREEBUSY] = "VFREEBUSY",
17 [ICAL_BLOCK_VALARM] = "VALARM",
18 [ICAL_BLOCK_OTHER] = NULL,
19 };
20
21 /* valuel helpers: common utilities to call within the p->fn()
22 * callbacks as well as in the code below */
23
24 int
25 ical_err(IcalParser *p, char *msg)
26 {
27 p->errmsg = msg;
28 return -1;
29 }
30
31 int
32 ical_get_level(IcalParser *p)
33 {
34 return p->current - p->stack;
35 }
36
37 int
38 ical_get_value(IcalParser *p, char *s, size_t *len)
39 {
40 *len = strlen(s);
41 if (p->base64)
42 if (base64_decode(s, len, s, len) < 0)
43 return ical_err(p, "invalid base64 data");
44 return 0;
45 }
46
47 int
48 ical_get_time(IcalParser *p, char *s, time_t *t)
49 {
50 struct tm tm = {0};
51 char const *tzid;
52
53 tzid = (p->tzid) ? p->tzid :
54 (p->current && p->current->tzid[0] != '\0') ? p->current->tzid :
55 "";
56
57 #define N(i, x) ((s[i] - '0') * x)
58
59 /* date */
60 for (int i = 0; i < 8; i++)
61 if (!isdigit(s[i]))
62 return ical_err(p, "invalid date format");
63 tm.tm_year = N(0,1000) + N(1,100) + N(2,10) + N(3,1) - 1900;
64 tm.tm_mon = N(4,10) + N(5,1) - 1;
65 tm.tm_mday = N(6,10) + N(7,1);
66 s += 8;
67
68 if (*s == 'T') {
69 /* time */
70 s++;
71 for (int i = 0; i < 6; i++)
72 if (!isdigit(s[i]))
73 return ical_err(p, "invalid time format");
74 tm.tm_hour = N(0,10) + N(1,1);
75 tm.tm_min = N(2,10) + N(3,1);
76 tm.tm_sec = N(4,10) + N(5,1);
77 if (s[6] == 'Z')
78 tzid = "UTC";
79 }
80
81 #undef N
82
83 if ((*t = tztime(&tm, tzid)) == (time_t)-1)
84 return ical_err(p, "could not convert time");
85
86 return 0;
87 }
88
89 /* hooks: called just before user functions to do extra work such as
90 * processing time zones definition or prepare base64 decoding, and
91 * permit to only have parsing code left to parsing functions */
92
93 static int
94 hook_field_name(IcalParser *p, char *name)
95 {
96 (void)p; (void)name;
97 return 0;
98 }
99
100 static int
101 hook_param_name(IcalParser *p, char *name)
102 {
103 (void)p; (void)name;
104 return 0;
105 }
106
107 static int
108 hook_param_value(IcalParser *p, char *name, char *value)
109 {
110 if (strcasecmp(name, "ENCODING") == 0)
111 p->base64 = (strcasecmp(value, "BASE64") == 0);
112
113 if (strcasecmp(name, "TZID") == 0)
114 p->tzid = value;
115
116 return 0;
117 }
118
119 static int
120 hook_field_value(IcalParser *p, char *name, char *value)
121 {
122 if (strcasecmp(name, "TZID") == 0)
123 if (strlcpy(p->current->tzid, value, sizeof p->current->tzid) >=
124 sizeof p->current->tzid)
125 return ical_err(p, "TZID: name too large");
126
127 p->tzid = NULL;
128
129 return 0;
130 }
131
132 static int
133 hook_block_begin(IcalParser *p, char *name)
134 {
135 p->current++;
136 memset(p->current, 0, sizeof(*p->current));
137 if (ical_get_level(p) >= ICAL_STACK_SIZE)
138 return ical_err(p, "max recurion reached");
139 if (strlcpy(p->current->name, name, sizeof p->current->name) >=
140 sizeof p->current->name)
141 return ical_err(p, "value too large");
142
143 for (int i = 0; ical_block_name[i] != NULL; i++) {
144 if (strcasecmp(ical_block_name[i], name) == 0) {
145 if (p->blocktype != ICAL_BLOCK_OTHER)
146 return ical_err(p, "BEGIN:V* in BEGIN:V*");
147 p->blocktype = i;
148 }
149 }
150
151 return 0;
152 }
153
154 static int
155 hook_block_end_before(IcalParser *p, char *name)
156 {
157 if (p->current == p->stack)
158 return ical_err(p, "more END: than BEGIN:");
159 if (strcasecmp(p->current->name, name) != 0)
160 return ical_err(p, "mismatching BEGIN: and END:");
161 if (p->current <= p->stack)
162 return ical_err(p, "more END: than BEGIN:");
163 return 0;
164 }
165
166 static int
167 hook_block_end_after(IcalParser *p, char *name)
168 {
169 p->current--;
170 if (ical_block_name[p->blocktype] != NULL &&
171 strcasecmp(ical_block_name[p->blocktype], name) == 0)
172 p->blocktype = ICAL_BLOCK_OTHER;
173 return 0;
174 }
175
176 /* parsers: in charge of reading from `fp`, splitting text into
177 * fields, and call hooks and user functions. */
178
179 #define CALL(p, fn, ...) ((p)->fn ? (p)->fn((p), __VA_ARGS__) : 0)
180
181 static int
182 ical_parse_value(IcalParser *p, char **sp, char *name)
183 {
184 int err;
185 char *s, c, *val;
186
187 s = *sp;
188 if (*s == '"') {
189 val = ++s;
190 while (!iscntrl(*s) && *s != '"')
191 s++;
192 if (*s != '"')
193 return ical_err(p, "missing '\"'");
194 *s++ = '\0';
195 } else {
196 val = s;
197 while (!iscntrl(*s) && !strchr(",;:'\"", *s))
198 s++;
199 }
200 c = *s, *s = '\0';
201 if ((err = hook_param_value(p, name, val)) != 0 ||
202 (err = CALL(p, fn_param_value, name, val)) != 0)
203 return err;
204 *s = c;
205 *sp = s;
206 return 0;
207 }
208
209 static int
210 ical_parse_param(IcalParser *p, char **sp)
211 {
212 int err;
213 char *s, *name;
214
215 s = *sp;
216 do {
217 for (name = s; isalnum(*s) || *s == '-'; s++);
218 if (s == name || (*s != '='))
219 return ical_err(p, "invalid parameter name");
220 *s++ = '\0';
221 if ((err = hook_param_name(p, name)) != 0 ||
222 (err = CALL(p, fn_param_name, name)) != 0)
223 return err;
224 do {
225 if ((err = ical_parse_value(p, &s, name)) != 0)
226 return err;
227 } while (*s == ',' && s++);
228 } while (*s == ';' && s++);
229 *sp = s;
230 return 0;
231 }
232
233 static int
234 ical_parse_contentline(IcalParser *p, char *s)
235 {
236 int err;
237 char c, *name, *sep;
238
239 if (*s == '\0')
240 return 0;
241
242 for (name = s; isalnum(*s) || *s == '-'; s++);
243 if (s == name || (*s != ';' && *s != ':'))
244 return ical_err(p, "invalid property name");
245 c = *s, *s = '\0';
246 if (strcasecmp(name, "BEGIN") != 0 && strcasecmp(name, "END") != 0)
247 if ((err = hook_field_name(p, name)) != 0 ||
248 (err = CALL(p, fn_field_name, name)) != 0)
249 return err;
250 *s = c;
251 sep = s;
252
253 p->base64 = 0;
254 while (*s == ';') {
255 s++;
256 if ((err = ical_parse_param(p, &s)) != 0)
257 return err;
258 }
259
260 if (*s != ':')
261 return ical_err(p, "expected ':' delimiter");
262 s++;
263
264 *sep = '\0';
265 if (strcasecmp(name, "BEGIN") == 0) {
266 if ((err = hook_block_begin(p, s)) != 0 ||
267 (err = CALL(p, fn_block_begin, s)) != 0)
268 return err;
269 } else if (strcasecmp(name, "END") == 0) {
270 if ((err = hook_block_end_before(p, s)) != 0 ||
271 (err = CALL(p, fn_block_end, s)) != 0 ||
272 (err = hook_block_end_after(p, s)) != 0)
273 return err;
274 } else {
275 if ((err = hook_field_value(p, name, s)) != 0 ||
276 (err = CALL(p, fn_field_value, name, s)) != 0)
277 return err;
278 }
279 return 0;
280 }
281
282 static ssize_t
283 ical_getline(char **contentline, char **line, size_t *sz, FILE *fp)
284 {
285 size_t num = 0;
286 int c;
287
288 if ((*contentline = realloc(*contentline, 1)) == NULL)
289 return -1;
290 **contentline = '\0';
291
292 do {
293 if (getline(line, sz, fp) <= 0)
294 goto end;
295 num++;
296 strchomp(*line);
297
298 if (strappend(contentline, *line) == NULL)
299 return -1;
300 if ((c = fgetc(fp)) == EOF)
301 goto end;
302 } while (c == ' ');
303 ungetc(c, fp);
304 assert(!ferror(fp));
305 end:
306 return ferror(fp) ? -1 : num;
307 }
308
309 int
310 ical_parse(IcalParser *p, FILE *fp)
311 {
312 char *line = NULL, *contentline = NULL;
313 size_t sz = 0;
314 ssize_t l;
315 int err;
316
317 p->current = p->stack;
318 p->linenum = 0;
319 p->blocktype = ICAL_BLOCK_OTHER;
320
321 do {
322 if ((l = ical_getline(&contentline, &line, &sz, fp)) < 0) {
323 err = ical_err(p, "readling line");
324 break;
325 }
326 p->linenum += l;
327 } while (l > 0 && (err = ical_parse_contentline(p, contentline)) == 0);
328
329 free(contentline);
330
331 if (err == 0 && p->current != p->stack)
332 return ical_err(p, "more BEGIN: than END:");
333
334 return err;
335 }