diff options
Diffstat (limited to 'src/ical.c')
-rw-r--r-- | src/ical.c | 786 |
1 files changed, 460 insertions, 326 deletions
@@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 calcurse Development Team <misc@calcurse.org> + * Copyright (c) 2004-2023 calcurse Development Team <misc@calcurse.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,11 +36,14 @@ #include <strings.h> #include <sys/types.h> +#include <ctype.h> #include "calcurse.h" #define ICALDATEFMT "%Y%m%d" #define ICALDATETIMEFMT "%Y%m%dT%H%M%S" +#define SEPARATOR "-- \n" +#define INDENT " " typedef enum { ICAL_VEVENT, @@ -62,16 +65,6 @@ typedef enum { COMMENT } ical_property_e; -typedef struct { - enum recur_type type; - unsigned freq; - time_t until; - unsigned count; - llist_t bymonth; - llist_t bywday; - llist_t bymonthday; -} ical_rpt_t; - static void ical_export_header(FILE *); static void ical_export_recur_events(FILE *, int); static void ical_export_events(FILE *, int); @@ -83,22 +76,29 @@ static void ical_export_footer(FILE *); static const char *ical_recur_type[NBRECUR] = { "DAILY", "WEEKLY", "MONTHLY", "YEARLY" }; -/* Escape characters in field before printing */ -static void ical_format_line(FILE * stream, char * field, char * msg) +static const char *ical_wday[] = + {"SU", "MO", "TU", "WE", "TH", "FR", "SA"}; + +/* + * Encode a string as a property value of type TEXT (RFC 5545, 3.3.11). + */ +static void ical_format_line(FILE *stream, char *property, char *msg) { char * p; - fputs(field, stream); + fputs(property, stream); for (p = msg; *p; p++) { switch (*p) { + case '\n': + fprintf(stream, "\\%c", 'n'); + break; case ',': case ';': case '\\': fprintf(stream, "\\%c", *p); break; default: - fputc(*p, stream); - break; + fputc(*p, stream); } } fputc('\n', stream); @@ -115,12 +115,157 @@ static void ical_export_valarm(FILE * stream) fputs("END:VALARM\n", stream); } +static void ical_export_rrule(FILE *stream, struct rpt *rpt, ical_vevent_e item, + char *buf) +{ + llist_item_t *j; + int d; + char *fmt = item == EVENT ? ICALDATEFMT : + item == APPOINTMENT ? ICALDATETIMEFMT : + NULL; + + fprintf(stream, "RRULE:FREQ=%s", ical_recur_type[rpt->type]); + if (rpt->freq > 1) + fprintf(stream, ";INTERVAL=%d", rpt->freq); + if (rpt->until) { + date_sec2date_fmt(rpt->until, fmt, buf); + fprintf(stream, ";UNTIL=%s", buf); + } + if (LLIST_FIRST(&rpt->bymonth)) { + fputs(";BYMONTH=", stream); + LLIST_FOREACH(&rpt->bymonth, j) { + d = *(int *)LLIST_GET_DATA(j); + fprintf(stream, "%d", d); + if (LLIST_NEXT(j)) + fputc(',', stream); + } + } + if (LLIST_FIRST(&rpt->bywday)) { + int ord; + char sign; + + fputs(";BYDAY=", stream); + LLIST_FOREACH(&rpt->bywday, j) { + d = *(int *)LLIST_GET_DATA(j); + sign = d < 0 ? '-' : '+'; + d = abs(d); + ord = d / 7; + d = d % 7; + if (ord == 0) + fprintf(stream, "%s", ical_wday[d]); + else + fprintf(stream, "%c%d%s", sign, ord, ical_wday[d]); + if (LLIST_NEXT(j)) + fputc(',', stream); + } + } + if (LLIST_FIRST(&rpt->bymonthday)) { + fputs(";BYMONTHDAY=", stream); + LLIST_FOREACH(&rpt->bymonthday, j) { + d = *(int *)LLIST_GET_DATA(j); + fprintf(stream, "%d", d); + if (LLIST_NEXT(j)) + fputc(',', stream); + } + } + fputc('\n', stream); +} + +/* + * Copy the characters (lines) between "a" and "z" into an allocated string, + * un-indent it and return it. Note that the final character, a newline, is + * overwritten with '\0'. + */ +static char *ical_unindent(char *a, char *z) +{ + char *p, *q; int len; + + len = z - a + 1; + + p = mem_malloc(len); + strncpy(p, a, len); + p[len - 1] = '\0'; + while ((q = strstr(p, "\n" INDENT))) { + while (*(q + 1 + strlen(INDENT))) { + *(q + 1) = *(q + 1 + strlen(INDENT)); + q++; + } + *(q + 1) = '\0'; + } + return p; +} + +static void ical_export_note(FILE *stream, char *name) +{ + char *note_file, *p, *q, *r, *rest; + char *property[] = { + "Location: ", + "Comment: ", + NULL + }; + char *PROPERTY[] = { + "LOCATION:", + "COMMENT:" + }; + struct string note; + char lbuf[BUFSIZ]; + FILE *fp; + int has_desc, has_prop, i; + + asprintf(¬e_file, "%s/%s", path_notes, name); + if (!(fp = fopen(note_file, "r")) || ungetc(getc(fp), fp) == EOF) { + if (fp) + fclose(fp); + return; + } + string_init(¬e); + while (fgets(lbuf, BUFSIZ, fp)) + string_catf(¬e, "%s", lbuf); + fclose(fp); + + has_desc = has_prop = 0; + rest = note.buf; + if ((p = strstr(note.buf, SEPARATOR))) { + has_prop = 1; + rest = p + strlen(SEPARATOR); + if (p != note.buf) { + has_desc = 1; + *(--p) = '\0'; + } + } else { + has_desc = 1; + note.buf[strlen(note.buf) - 1] = '\0'; + } + + if (has_desc) + ical_format_line(stream, "DESCRIPTION:", note.buf); + + if (!has_prop) + goto cleanup; + for (i = 0; property[i]; i++) { + if ((p = strstr(rest, property[i]))) + p += strlen(property[i]); + else + continue; + /* Find end of property. */ + for (q = p; + (q = strchr(q, '\n')) && starts_with(q + 1, INDENT); + q++) ; + /* Extract property line(s). */ + r = ical_unindent(p, q); + ical_format_line(stream, PROPERTY[i], r); + mem_free(r); + } +cleanup: + mem_free(note.buf); +} + /* Export header. */ static void ical_export_header(FILE * stream) { fputs("BEGIN:VCALENDAR\n", stream); - fprintf(stream, "PRODID:-//calcurse//NONSGML v%s//EN\n", VERSION); fputs("VERSION:2.0\n", stream); + fprintf(stream, "PRODID:-//calcurse//NONSGML v%s//EN\n", VERSION); } /* Export footer. */ @@ -133,26 +278,21 @@ static void ical_export_footer(FILE * stream) static void ical_export_recur_events(FILE * stream, int export_uid) { llist_item_t *i, *j; - char ical_date[BUFSIZ]; + char ical_date[BUFSIZ], *hash; LLIST_FOREACH(&recur_elist, i) { struct recur_event *rev = LLIST_GET_DATA(i); - date_sec2date_fmt(rev->day, ICALDATEFMT, ical_date); fputs("BEGIN:VEVENT\n", stream); - fprintf(stream, "DTSTART;VALUE=DATE:%s\n", ical_date); - fprintf(stream, "RRULE:FREQ=%s;INTERVAL=%d", - ical_recur_type[rev->rpt->type], rev->rpt->freq); - - if (rev->rpt->until != 0) { - date_sec2date_fmt(rev->rpt->until, ICALDATEFMT, - ical_date); - fprintf(stream, ";UNTIL=%s\n", ical_date); - } else { - fputc('\n', stream); + if (export_uid) { + hash = recur_event_hash(rev); + fprintf(stream, "UID:%s\n", hash); + mem_free(hash); } - + date_sec2date_fmt(rev->day, ICALDATEFMT, ical_date); + fprintf(stream, "DTSTART;VALUE=DATE:%s\n", ical_date); + ical_export_rrule(stream, rev->rpt, EVENT, ical_date); if (LLIST_FIRST(&rev->exc)) { - fputs("EXDATE:", stream); + fputs("EXDATE;VALUE=DATE:", stream); LLIST_FOREACH(&rev->exc, j) { struct excp *exc = LLIST_GET_DATA(j); date_sec2date_fmt(exc->st, ICALDATETIMEFMT, @@ -161,15 +301,9 @@ static void ical_export_recur_events(FILE * stream, int export_uid) fputc(LLIST_NEXT(j) ? ',' : '\n', stream); } } - ical_format_line(stream, "SUMMARY:", rev->mesg); - - if (export_uid) { - char *hash = recur_event_hash(rev); - fprintf(stream, "UID:%s\n", hash); - mem_free(hash); - } - + if (rev->note) + ical_export_note(stream, rev->note); fputs("END:VEVENT\n", stream); } } @@ -178,21 +312,21 @@ static void ical_export_recur_events(FILE * stream, int export_uid) static void ical_export_events(FILE * stream, int export_uid) { llist_item_t *i; - char ical_date[BUFSIZ]; + char ical_date[BUFSIZ], *hash; LLIST_FOREACH(&eventlist, i) { struct event *ev = LLIST_TS_GET_DATA(i); - date_sec2date_fmt(ev->day, ICALDATEFMT, ical_date); fputs("BEGIN:VEVENT\n", stream); - fprintf(stream, "DTSTART;VALUE=DATE:%s\n", ical_date); - ical_format_line(stream, "SUMMARY:", ev->mesg); - if (export_uid) { - char *hash = event_hash(ev); + hash = event_hash(ev); fprintf(stream, "UID:%s\n", hash); mem_free(hash); } - + date_sec2date_fmt(ev->day, ICALDATEFMT, ical_date); + fprintf(stream, "DTSTART;VALUE=DATE:%s\n", ical_date); + ical_format_line(stream, "SUMMARY:", ev->mesg); + if (ev->note) + ical_export_note(stream, ev->note); fputs("END:VEVENT\n", stream); } } @@ -201,16 +335,30 @@ static void ical_export_events(FILE * stream, int export_uid) static void ical_export_recur_apoints(FILE * stream, int export_uid) { llist_item_t *i, *j; - char ical_datetime[BUFSIZ]; - char ical_date[BUFSIZ]; + char ical_datetime[BUFSIZ], *hash; + time_t tod; LLIST_TS_LOCK(&recur_alist_p); LLIST_TS_FOREACH(&recur_alist_p, i) { struct recur_apoint *rapt = LLIST_TS_GET_DATA(i); + /* + * Add time-of-day to UNTIL/EXDATE. + * In calcurse until/exception is a date (midnight), but in + * RFC 5545 UNTIL/EXDATE is a DATE-TIME value type by default. + */ + tod = get_item_time(rapt->start); + if (rapt->rpt->until) + rapt->rpt->until += tod; + date_sec2date_fmt(rapt->start, ICALDATETIMEFMT, ical_datetime); fputs("BEGIN:VEVENT\n", stream); + if (export_uid) { + hash = recur_apoint_hash(rapt); + fprintf(stream, "UID:%s\n", hash); + mem_free(hash); + } fprintf(stream, "DTSTART:%s\n", ical_datetime); if (rapt->dur > 0) { fprintf(stream, "DURATION:P%ldDT%ldH%ldM%ldS\n", @@ -219,38 +367,22 @@ static void ical_export_recur_apoints(FILE * stream, int export_uid) (rapt->dur / MININSEC) % HOURINMIN, rapt->dur % MININSEC); } - fprintf(stream, "RRULE:FREQ=%s;INTERVAL=%d", - ical_recur_type[rapt->rpt->type], rapt->rpt->freq); - - if (rapt->rpt->until != 0) { - date_sec2date_fmt(rapt->rpt->until + HOURINSEC, - ICALDATEFMT, ical_date); - fprintf(stream, ";UNTIL=%s\n", ical_date); - } else { - fputc('\n', stream); - } - + ical_export_rrule(stream, rapt->rpt, APPOINTMENT, ical_datetime); if (LLIST_FIRST(&rapt->exc)) { fputs("EXDATE:", stream); LLIST_FOREACH(&rapt->exc, j) { struct excp *exc = LLIST_GET_DATA(j); - date_sec2date_fmt(exc->st, ICALDATETIMEFMT, - ical_date); - fprintf(stream, "%s", ical_date); + date_sec2date_fmt(exc->st + tod, ICALDATETIMEFMT, + ical_datetime); + fprintf(stream, "%s", ical_datetime); fputc(LLIST_NEXT(j) ? ',' : '\n', stream); } } - ical_format_line(stream, "SUMMARY:", rapt->mesg); + if (rapt->note) + ical_export_note(stream, rapt->note); if (rapt->state & APOINT_NOTIFY) ical_export_valarm(stream); - - if (export_uid) { - char *hash = recur_apoint_hash(rapt); - fprintf(stream, "UID:%s\n", hash); - mem_free(hash); - } - fputs("END:VEVENT\n", stream); } LLIST_TS_UNLOCK(&recur_alist_p); @@ -260,14 +392,19 @@ static void ical_export_recur_apoints(FILE * stream, int export_uid) static void ical_export_apoints(FILE * stream, int export_uid) { llist_item_t *i; - char ical_datetime[BUFSIZ]; + char ical_datetime[BUFSIZ], *hash; LLIST_TS_LOCK(&alist_p); LLIST_TS_FOREACH(&alist_p, i) { struct apoint *apt = LLIST_TS_GET_DATA(i); + fputs("BEGIN:VEVENT\n", stream); + if (export_uid) { + hash = apoint_hash(apt); + fprintf(stream, "UID:%s\n", hash); + mem_free(hash); + } date_sec2date_fmt(apt->start, ICALDATETIMEFMT, ical_datetime); - fputs("BEGIN:VEVENT\n", stream); fprintf(stream, "DTSTART:%s\n", ical_datetime); if (apt->dur > 0) { fprintf(stream, "DURATION:P%ldDT%ldH%ldM%ldS\n", @@ -277,15 +414,10 @@ static void ical_export_apoints(FILE * stream, int export_uid) apt->dur % MININSEC); } ical_format_line(stream, "SUMMARY:", apt->mesg); + if (apt->note) + ical_export_note(stream, apt->note); if (apt->state & APOINT_NOTIFY) ical_export_valarm(stream); - - if (export_uid) { - char *hash = apoint_hash(apt); - fprintf(stream, "UID:%s\n", hash); - mem_free(hash); - } - fputs("END:VEVENT\n", stream); } LLIST_TS_UNLOCK(&alist_p); @@ -295,22 +427,23 @@ static void ical_export_apoints(FILE * stream, int export_uid) static void ical_export_todo(FILE * stream, int export_uid) { llist_item_t *i; + char *hash; LLIST_FOREACH(&todolist, i) { struct todo *todo = LLIST_TS_GET_DATA(i); fputs("BEGIN:VTODO\n", stream); - if (todo->completed) - fprintf(stream, "STATUS:COMPLETED\n"); - fprintf(stream, "PRIORITY:%d\n", todo->id); - ical_format_line(stream, "SUMMARY:", todo->mesg); - if (export_uid) { - char *hash = todo_hash(todo); + hash = todo_hash(todo); fprintf(stream, "UID:%s\n", hash); mem_free(hash); } - + fprintf(stream, "PRIORITY:%d\n", todo->id); + ical_format_line(stream, "SUMMARY:", todo->mesg); + if (todo->note) + ical_export_note(stream, todo->note); + if (todo->completed) + fprintf(stream, "STATUS:COMPLETED\n"); fputs("END:VTODO\n", stream); } } @@ -379,32 +512,26 @@ static void ical_store_todo(int priority, int completed, char *mesg, * Calcurse limitation: events are one-day (all-day), and all multi-day events * are turned into one-day events; a note has been added by ical_read_event(). */ -static int +static void ical_store_event(char *mesg, char *note, time_t day, time_t end, - ical_rpt_t *irpt, llist_t *exc, const char *fmt_ev, + struct rpt *rpt, llist_t *exc, const char *fmt_ev, const char *fmt_rev) { const int EVENTID = 1; struct event *ev; struct recur_event *rev; + if (!mesg) + mesg = mem_strdup(_("(empty)")); + EXIT_IF(!mesg, _("ical_store_event: out of memory")); + /* * Repeating event. The end day is ignored, and the event becomes * one-day even if multi-day. */ - if (irpt) { - struct rpt rpt; - rpt.type = irpt->type; - rpt.freq = irpt->freq; - rpt.until = irpt->until; - rpt.bymonth = irpt->bymonth; - rpt.bywday = irpt->bywday; - rpt.bymonthday = irpt->bymonthday; - rpt.exc = *exc; - if (!recur_item_find_occurrence(day, -1, &rpt, NULL, day, NULL)) - return 0; - mem_free(irpt); - rev = recur_event_new(mesg, note, day, EVENTID, &rpt); + if (rpt) { + rpt->exc = *exc; + rev = recur_event_new(mesg, note, day, EVENTID, rpt); if (fmt_rev) print_recur_event(fmt_rev, day, rev); goto cleanup; @@ -423,27 +550,26 @@ ical_store_event(char *mesg, char *note, time_t day, time_t end, * event until the day before the end. In iCal, the end day is * exclusive, the until day inclusive. */ - struct rpt rpt; - rpt.type = RECUR_DAILY; - rpt.freq = 1; - rpt.until = day + ((end - day - 1) / DAYINSEC) * DAYINSEC; - LLIST_INIT(&rpt.bymonth); - LLIST_INIT(&rpt.bywday); - LLIST_INIT(&rpt.bymonthday); - rpt.exc = *exc; - rev = recur_event_new(mesg, note, day, EVENTID, &rpt); + struct rpt tmp; + tmp.type = RECUR_DAILY; + tmp.freq = 1; + tmp.until = day + ((end - day - 1) / DAYINSEC) * DAYINSEC; + LLIST_INIT(&tmp.bymonth); + LLIST_INIT(&tmp.bywday); + LLIST_INIT(&tmp.bymonthday); + tmp.exc = *exc; + rev = recur_event_new(mesg, note, day, EVENTID, &tmp); if (fmt_rev) print_recur_event(fmt_rev, day, rev); cleanup: mem_free(mesg); erase_note(¬e); - return 1; } -static int +static void ical_store_apoint(char *mesg, char *note, time_t start, long dur, - ical_rpt_t * irpt, llist_t * exc, int has_alarm, + struct rpt *rpt, llist_t *exc, int has_alarm, const char *fmt_apt, const char *fmt_rapt) { char state = 0L; @@ -451,21 +577,13 @@ ical_store_apoint(char *mesg, char *note, time_t start, long dur, struct recur_apoint *rapt; time_t day; - day = update_time_in_date(start, 0, 0); + if (!mesg) + mesg = mem_strdup(_("(empty)")); + EXIT_IF(!mesg, _("ical_store_event: out of memory")); + if (has_alarm) state |= APOINT_NOTIFY; - if (irpt) { - struct rpt rpt; - rpt.type = irpt->type; - rpt.freq = irpt->freq; - rpt.until = irpt->until; - rpt.bymonth = irpt->bymonth; - rpt.bywday = irpt->bywday; - rpt.bymonthday = irpt->bymonthday; - rpt.exc = *exc; - if (!recur_item_find_occurrence(start, dur, &rpt, NULL, - day, NULL)) - return 0; + if (rpt) { /* * In calcurse, "until" is interpreted as a day (DATE) - hours, * minutes and seconds are ignored - whereas in iCal the full @@ -474,17 +592,17 @@ ical_store_apoint(char *mesg, char *note, time_t start, long dur, * day, and the start time is after the until value, the * calcurse until day must be changed to the day before. */ - if (rpt.until) { - day = update_time_in_date(rpt.until, 0, 0); - if (recur_item_find_occurrence(start, dur, &rpt, NULL, + if (rpt->until) { + day = DAY(rpt->until); + if (recur_item_find_occurrence(start, dur, rpt, NULL, day, NULL) && - get_item_time(rpt.until) < get_item_time(start)) - rpt.until = date_sec_change(day, 0, -1); + get_item_time(rpt->until) < get_item_time(start)) + rpt->until = date_sec_change(day, 0, -1); else - rpt.until = day; + rpt->until = day; } - mem_free(irpt); - rapt = recur_apoint_new(mesg, note, start, dur, state, &rpt); + rpt->exc = *exc; + rapt = recur_apoint_new(mesg, note, start, dur, state, rpt); if (fmt_rapt) print_recur_apoint(fmt_rapt, start, rapt->start, rapt); } else { @@ -494,7 +612,6 @@ ical_store_apoint(char *mesg, char *note, time_t start, long dur, } mem_free(mesg); erase_note(¬e); - return 1; } /* @@ -507,7 +624,6 @@ static char *ical_unformat_line(char *line, int eol, int indentation) { struct string s; char *p; - const char *INDENT = " "; string_init(&s); for (p = line; *p; p++) { @@ -575,7 +691,7 @@ static int ical_readline(FILE * fdi, char *buf, char *lstore, unsigned *ln) while (fgets(lstore, BUFSIZ, fdi) != NULL) { (*ln)++; if ((eol = strchr(lstore, '\n')) != NULL) { - if (*(eol - 1) == '\r') + if (strlen(lstore) > 1 && *(eol - 1) == '\r') *(eol - 1) = '\0'; else *eol = '\0'; @@ -787,26 +903,6 @@ static long ical_dur2long(char *durstr, ical_vevent_e type) } /* - * Set repetition until date from repetition count - * for an ical recurrence rule (s, d, i, e). - */ -static void ical_count2until(time_t s, long d, ical_rpt_t *i, llist_t *e, - ical_vevent_e type) -{ - struct rpt rpt; - - if (type == EVENT) - d = -1; - rpt.type = i->type; - rpt.freq = i->freq; - rpt.until = 0; - rpt.bymonth = i->bymonth; - rpt.bywday = i->bywday; - rpt.bymonthday = i->bymonthday; - recur_nth_occurrence(s, d, &rpt, e, i->count, &i->until); -} - -/* * Skip to the value part of an iCalendar content line. */ static char *ical_get_value(char *p) @@ -961,14 +1057,15 @@ static int ical_bywday(llist_t *ll, char *cl) * / ( "BYSETPOS" "=" bysplist ) * / ( "WKST" "=" weekday ) */ -static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr, +static struct rpt *ical_read_rrule(FILE *log, char *rrulestr, unsigned *noskipped, const int itemline, ical_vevent_e type, - time_t start) + time_t start, + int *count) { - char freqstr[8]; - ical_rpt_t *rpt; + char freqstr[8], datestr[17]; + struct rpt *rpt; char *p, *q; if (type == UNDEFINED) { @@ -988,15 +1085,15 @@ static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr, for (q = p; (q = strchr(q, ';')); *q = ' ', q++) ; - rpt = mem_malloc(sizeof(ical_rpt_t)); - memset(rpt, 0, sizeof(ical_rpt_t)); + rpt = mem_malloc(sizeof(struct rpt)); + memset(rpt, 0, sizeof(struct rpt)); LLIST_INIT(&rpt->bymonth); LLIST_INIT(&rpt->bywday); LLIST_INIT(&rpt->bymonthday); /* FREQ rule part */ if ((p = strstr(rrulestr, "FREQ="))) { - if (sscanf(p, "FREQ=%s", freqstr) != 1) { + if (sscanf(p, "FREQ=%7s", freqstr) != 1) { ical_log(log, ICAL_VEVENT, itemline, _("frequency not set in rrule.")); (*noskipped)++; @@ -1030,7 +1127,7 @@ static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr, /* INTERVAL rule part */ rpt->freq = 1; if ((p = strstr(rrulestr, "INTERVAL="))) { - if (sscanf(p, "INTERVAL=%u", &rpt->freq) != 1) { + if (sscanf(p, "INTERVAL=%d", &rpt->freq) != 1) { ical_log(log, ICAL_VEVENT, itemline, _("invalid interval.")); (*noskipped)++; mem_free(rpt); @@ -1048,7 +1145,14 @@ static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr, } if ((p = strstr(rrulestr, "UNTIL="))) { - rpt->until = ical_datetime2time_t(strchr(p, '=') + 1, NULL, type); + if (sscanf(p, "UNTIL=%16s", datestr) != 1) { + ical_log(log, ICAL_VEVENT, itemline, + _("missing until value.")); + (*noskipped)++; + mem_free(rpt); + return NULL; + } + rpt->until = ical_datetime2time_t(datestr, NULL, type); if (!rpt->until) { ical_log(log, ICAL_VEVENT, itemline, _("invalid until format.")); @@ -1064,7 +1168,7 @@ static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr, */ if ((p = strstr(rrulestr, "COUNT="))) { p = strchr(p, '=') + 1; - if (!(sscanf(p, "%u", &rpt->count) == 1 && rpt->count)) { + if (!(sscanf(p, "%d", count) == 1 && *count)) { ical_log(log, ICAL_VEVENT, itemline, _("invalid count value.")); (*noskipped)++; @@ -1132,11 +1236,6 @@ ical_read_exdate(llist_t * exc, FILE * log, char *exstr, unsigned *noskipped, time_t t; int n; - if (type == UNDEFINED) { - ical_log(log, ICAL_VEVENT, itemline, - _("need DTSTART to determine event type.")); - goto cleanup; - } if (type != ical_get_type(exstr)) { ical_log(log, ICAL_VEVENT, itemline, _("invalid exception date value type.")); @@ -1180,7 +1279,7 @@ static char *ical_read_note(char *line, ical_property_e property, unsigned *nosk FILE * log) { const int EOL = 1, - INDENT = (property != DESCRIPTION); + IND = (property != DESCRIPTION); char *p, *pname, *notestr; switch (property) { @@ -1207,7 +1306,7 @@ static char *ical_read_note(char *line, ical_property_e property, unsigned *nosk goto leave; } - notestr = ical_unformat_line(p, EOL, INDENT); + notestr = ical_unformat_line(p, EOL, IND); if (!notestr) { asprintf(&p, _("malformed %s."), pname); ical_log(log, item_type, itemline, p); @@ -1223,7 +1322,7 @@ static char *ical_read_summary(char *line, unsigned *noskipped, ical_types_e item_type, const int itemline, FILE * log) { - const int EOL = 0, INDENT = 0; + const int EOL = 0, IND = 0; char *p, *summary = NULL; p = ical_get_value(line); @@ -1233,7 +1332,7 @@ static char *ical_read_summary(char *line, unsigned *noskipped, goto leave; } - summary = ical_unformat_line(p, EOL, INDENT); + summary = ical_unformat_line(p, EOL, IND); if (!summary) { ical_log(log, item_type, itemline, _("malformed summary.")); (*noskipped)++; @@ -1241,12 +1340,9 @@ static char *ical_read_summary(char *line, unsigned *noskipped, } /* An event summary is one line only. */ - if (strchr(summary, '\n')) { - ical_log(log, item_type, itemline, _("line break in summary.")); - (*noskipped)++; - mem_free(summary); - summary = NULL; - } + for (p = summary; *p; p++) + if (*p == '\n') + *p = ' '; leave: return summary; } @@ -1260,23 +1356,25 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, const int ITEMLINE = *lineno - !feof(fdi); ical_vevent_e vevent_type; ical_property_e property; - char *p, *note = NULL, *tmp, *tzid; - const char *SEPARATOR = "-- \n"; - struct string s; + char *p, *note, *tzid; + char *dtstart, *dtend, *duration, *rrule; + struct string s, exdate; struct { llist_t exc; - ical_rpt_t *rpt; + struct rpt *rpt; + int count; char *mesg, *desc, *loc, *comm, *imp, *note; time_t start, end; long dur; int has_alarm; } vevent; - int skip_alarm, has_note, separator; + int skip_alarm, has_note, separator, has_exdate; vevent_type = UNDEFINED; memset(&vevent, 0, sizeof vevent); LLIST_INIT(&vevent.exc); - skip_alarm = has_note = separator = 0; + note = dtstart = dtend = duration = rrule = NULL; + skip_alarm = has_note = separator = has_exdate =0; while (ical_readline(fdi, buf, lstore, lineno)) { note = NULL; property = NO_PROPERTY; @@ -1290,16 +1388,112 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, continue; } if (starts_with_ci(buf, "END:VEVENT")) { - if (!vevent.mesg) { + /* DTSTART and related properties (picked up earlier). */ + if (!dtstart) { ical_log(log, ICAL_VEVENT, ITEMLINE, - _("could not retrieve item summary.")); + _("item start date not defined.")); goto skip; } - if (vevent.start == 0) { + vevent_type = ical_get_type(dtstart); + if ((tzid = ical_get_tzid(dtstart)) && + vevent_type == APPOINTMENT) { + if (vevent.imp) { + asprintf(&p, "%s, TZID=%s", + vevent.imp, tzid); + mem_free(vevent.imp); + vevent.imp = p; + } else + asprintf(&vevent.imp, "TZID=%s", tzid); + has_note = separator = 1; + } + p = ical_get_value(dtstart); + if (!p) { ical_log(log, ICAL_VEVENT, ITEMLINE, - _("item start date is not defined.")); + _("malformed start time line.")); + goto skip; + } + vevent.start = ical_datetime2time_t(p, tzid, vevent_type); + if (tzid) { + mem_free(tzid); + tzid = NULL; + } + if (!vevent.start) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("invalid or malformed event " + "start time.")); + goto skip; + } + /* DTEND */ + if (!dtend) + goto duration; + if (vevent_type != ical_get_type(dtend)) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("invalid end time value type.")); + goto skip; + } + tzid = ical_get_tzid(dtend); + p = ical_get_value(dtend); + if (!p) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("malformed end time line.")); + goto skip; + } + vevent.end = ical_datetime2time_t(p, tzid, vevent_type); + if (tzid) { + mem_free(tzid); + tzid = NULL; + } + if (!vevent.end) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("malformed event end time.")); + goto skip; + } + if (vevent.end <= vevent.start) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("end must be later than start.")); + goto skip; + } + duration: + if (!duration) + goto rrule; + if (vevent.end) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("either end or duration.")); goto skip; } + p = ical_get_value(duration); + if (!p) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("malformed duration line.")); + goto skip; + } + vevent.dur = ical_dur2long(p, vevent_type); + if (!vevent.dur) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("invalid duration.")); + goto skip; + } + rrule: + if (!rrule) + goto exdate; + vevent.rpt = ical_read_rrule(log, rrule, noskipped, + ITEMLINE, vevent_type, vevent.start, + &vevent.count); + if (!vevent.rpt) + goto cleanup; + exdate: + if (!has_exdate) + goto duration_end; + if (!rrule) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("exception date, but no recurrence " + "rule.")); + goto skip; + } + if (!ical_read_exdate(&vevent.exc, log, exdate.buf, + noskipped, ITEMLINE, vevent_type)) + goto cleanup; + duration_end: /* An APPOINTMENT must always have a duration. */ if (vevent_type == APPOINTMENT && !vevent.dur) { vevent.dur = vevent.end ? @@ -1316,25 +1510,22 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, char *md = _("multi-day event changed " "to one-day event"); if (vevent.imp) { - asprintf(&tmp, "%s, %s", + asprintf(&p, "%s, %s", vevent.imp, md); mem_free(vevent.imp); - vevent.imp = tmp; + vevent.imp = p; } else asprintf(&vevent.imp, "%s", md); has_note = separator = 1; } } - if (vevent.rpt && vevent.rpt->count) - ical_count2until(vevent.start, vevent.dur, - vevent.rpt, &vevent.exc, - vevent_type); if (has_note) { /* Construct string with note file contents. */ string_init(&s); if (vevent.desc) { string_catf(&s, "%s", vevent.desc); mem_free(vevent.desc); + vevent.desc = NULL; } if (separator) string_catf(&s, SEPARATOR); @@ -1342,51 +1533,67 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, string_catf(&s, _("Location: %s"), vevent.loc); mem_free(vevent.loc); + vevent.loc = NULL; } if (vevent.comm) { string_catf(&s, _("Comment: %s"), vevent.comm); mem_free(vevent.comm); + vevent.comm = NULL; } if (vevent.imp) { string_catf(&s, ("Import: %s\n"), vevent.imp); mem_free(vevent.imp); + vevent.imp = NULL; } vevent.note = generate_note(string_buf(&s)); mem_free(s.buf); - /* - * Necessary to prevent double-free if item - * creation fails below. - */ - vevent.desc = vevent.loc = vevent.comm = - vevent.imp = NULL; } - char *msg = _("rrule does not match start day (%s)."); - switch (vevent_type) { - case APPOINTMENT: - if (!ical_store_apoint(vevent.mesg, vevent.note, - vevent.start, vevent.dur, - vevent.rpt, &vevent.exc, - vevent.has_alarm, - fmt_apt, fmt_rapt)) { + if (vevent.rpt) { + time_t day, until; + long dur; + char *msg; + + dur = vevent_type == EVENT ? -1 : vevent.dur; + day = DAY(vevent.start); + msg = _("rrule does not match start day (%s)."); + + if (vevent.count) { + recur_nth_occurrence(vevent.start, + dur, + vevent.rpt, + &vevent.exc, + vevent.count, + &until); + vevent.rpt->until = until; + } + if (!recur_item_find_occurrence(vevent.start, + dur, + vevent.rpt, + NULL, + day, + NULL)) { char *l = day_ins(&msg, vevent.start); ical_log(log, ICAL_VEVENT, ITEMLINE, l); mem_free(l); goto skip; } + } + switch (vevent_type) { + case APPOINTMENT: + ical_store_apoint(vevent.mesg, vevent.note, + vevent.start, vevent.dur, + vevent.rpt, &vevent.exc, + vevent.has_alarm, + fmt_apt, fmt_rapt); (*noapoints)++; break; case EVENT: - if (!ical_store_event(vevent.mesg, vevent.note, + ical_store_event(vevent.mesg, vevent.note, vevent.start, vevent.end, vevent.rpt, &vevent.exc, - fmt_ev, fmt_rev)) { - char *l = day_ins(&msg, vevent.start); - ical_log(log, ICAL_VEVENT, ITEMLINE, l); - mem_free(l); - goto skip; - } + fmt_ev, fmt_rev); (*noevents)++; break; case UNDEFINED: @@ -1399,114 +1606,28 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, } if (starts_with_ci(buf, "DTSTART")) { /* - * DTSTART has a value type: either DATE-TIME (by - * default) or DATE. Properties DTEND, DURATION and - * EXDATE and rrule part UNTIL must agree. - * Assume that DTSTART comes before the others even - * though RFC 5545 allows any order. + * DTSTART has a value type: either DATE-TIME or DATE. * In calcurse DATE-TIME implies an appointment, DATE an * event. + * Properties DTEND, DURATION and EXDATE and rrule part + * UNTIL must match the DTSTART value type. */ - vevent_type = ical_get_type(buf); - if ((tzid = ical_get_tzid(buf)) && - vevent_type == APPOINTMENT) { - /* Add note on TZID. */ - if (vevent.imp) { - asprintf(&tmp, "%s, TZID=%s", - vevent.imp, tzid); - mem_free(vevent.imp); - vevent.imp = tmp; - } else - asprintf(&vevent.imp, "TZID=%s", tzid); - has_note = separator = 1; - } - p = ical_get_value(buf); - if (!p) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("malformed start time line.")); - goto skip; - } - - vevent.start = ical_datetime2time_t(p, tzid, vevent_type); - if (tzid) - mem_free(tzid); - if (!vevent.start) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("invalid or malformed event " - "start time.")); - goto skip; - } + asprintf(&dtstart, "%s", buf); } else if (starts_with_ci(buf, "DTEND")) { - if (vevent.dur) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("either end or duration.")); - goto skip; - } - if (vevent_type == UNDEFINED) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("need DTSTART to determine " - "event type.")); - goto skip; - } - if (vevent_type != ical_get_type(buf)) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("invalid end time value type.")); - goto skip; - } - tzid = ical_get_tzid(buf); - p = ical_get_value(buf); - if (!p) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("malformed end time line.")); - goto skip; - } - - vevent.end = ical_datetime2time_t(p, tzid, vevent_type); - if (tzid) - mem_free(tzid); - if (!vevent.end) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("malformed event end time.")); - goto skip; - } - if (vevent.end <= vevent.start) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("end must be later than start.")); - goto skip; - } + asprintf(&dtend, "%s", buf); } else if (starts_with_ci(buf, "DURATION")) { - if (vevent.end) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("either end or duration.")); - goto skip; - } - if (vevent_type == UNDEFINED) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("need DTSTART to determine " - "event type.")); - goto skip; - } - p = ical_get_value(buf); - if (!p) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("malformed duration line.")); - goto skip; - } - vevent.dur = ical_dur2long(p, vevent_type); - if (!vevent.dur) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("invalid duration.")); - goto skip; - } + asprintf(&duration, "%s", buf); } else if (starts_with_ci(buf, "RRULE")) { - vevent.rpt = ical_read_rrule(log, buf, noskipped, - ITEMLINE, vevent_type, vevent.start); - if (!vevent.rpt) - goto cleanup; + asprintf(&rrule, "%s", buf); } else if (starts_with_ci(buf, "EXDATE")) { - if (!ical_read_exdate(&vevent.exc, log, buf, noskipped, - ITEMLINE, vevent_type)) - goto cleanup; + if (!has_exdate) { + has_exdate = 1; + string_init(&exdate); + string_catf(&exdate, "%s", buf); + } else { + p = ical_get_value(buf); + string_catf(&exdate, ",%s", p); + } } else if (starts_with_ci(buf, "SUMMARY")) { vevent.mesg = ical_read_summary(buf, noskipped, ICAL_VEVENT, ITEMLINE, log); @@ -1550,10 +1671,10 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, case COMMENT: /* There may be more than one. */ if (vevent.comm) { - asprintf(&tmp, "%sComment: %s", + asprintf(&p, "%sComment: %s", vevent.comm, note); mem_free(vevent.comm); - vevent.comm = tmp; + vevent.comm = p; } else vevent.comm = note; break; @@ -1567,6 +1688,16 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, skip: (*noskipped)++; cleanup: + if (dtstart) + mem_free(dtstart); + if (dtend) + mem_free(dtend); + if (duration) + mem_free(duration); + if (rrule) + mem_free(rrule); + if (has_exdate) + mem_free(exdate.buf); if (note) mem_free(note); if (vevent.desc) @@ -1590,8 +1721,7 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, { const int ITEMLINE = *lineno - !feof(fdi); ical_property_e property; - char *note = NULL, *tmp; - const char *SEPARATOR = "-- \n"; + char *p, *note; struct string s; struct { char *mesg, *desc, *loc, *comm, *note; @@ -1601,6 +1731,7 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, int skip_alarm, has_note, separator; memset(&vtodo, 0, sizeof vtodo); + note = NULL; skip_alarm = has_note = separator = 0; while (ical_readline(fdi, buf, lstore, lineno)) { note = NULL; @@ -1626,6 +1757,7 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, if (vtodo.desc) { string_catf(&s, "%s", vtodo.desc); mem_free(vtodo.desc); + vtodo.desc = NULL; } if (separator) string_catf(&s, SEPARATOR); @@ -1633,11 +1765,13 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, string_catf(&s, _("Location: %s"), vtodo.loc); mem_free(vtodo.loc); + vtodo.loc = NULL; } if (vtodo.comm) { string_catf(&s, _("Comment: %s"), vtodo.comm); mem_free(vtodo.comm); + vtodo.comm = NULL; } vtodo.note = generate_note(string_buf(&s)); mem_free(s.buf); @@ -1701,10 +1835,10 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, case COMMENT: /* There may be more than one. */ if (vtodo.comm) { - asprintf(&tmp, "%sComment: %s", + asprintf(&p, "%sComment: %s", vtodo.comm, note); mem_free(vtodo.comm); - vtodo.comm = tmp; + vtodo.comm = p; } else vtodo.comm = note; break; |