URI: 
       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 }