ical.c - ics2txt - convert icalendar .ics file to plain text HTML 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 }