ical.c - ics2txt - convert icalendar .ics file to plain text | |
git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws… | |
Log | |
Files | |
Refs | |
Tags | |
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->tz… | |
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->… | |
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") !=… | |
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, conten… | |
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 } |