"Fossies" - the Fresh Open Source Software Archive

Member "calcurse-4.5.1/src/ical.c" (20 Apr 2019, 29792 Bytes) of package /linux/privat/calcurse-4.5.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ical.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 4.3.0_vs_4.4.0.

    1 /*
    2  * Calcurse - text-based organizer
    3  *
    4  * Copyright (c) 2004-2017 calcurse Development Team <misc@calcurse.org>
    5  * All rights reserved.
    6  *
    7  * Redistribution and use in source and binary forms, with or without
    8  * modification, are permitted provided that the following conditions
    9  * are met:
   10  *
   11  *      - Redistributions of source code must retain the above
   12  *        copyright notice, this list of conditions and the
   13  *        following disclaimer.
   14  *
   15  *      - Redistributions in binary form must reproduce the above
   16  *        copyright notice, this list of conditions and the
   17  *        following disclaimer in the documentation and/or other
   18  *        materials provided with the distribution.
   19  *
   20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   31  *
   32  * Send your feedback or comments to : misc@calcurse.org
   33  * Calcurse home page : http://calcurse.org
   34  *
   35  */
   36 
   37 #include <strings.h>
   38 #include <sys/types.h>
   39 
   40 #include "calcurse.h"
   41 
   42 #define ICALDATEFMT      "%Y%m%d"
   43 #define ICALDATETIMEFMT  "%Y%m%dT%H%M%S"
   44 
   45 typedef enum {
   46     ICAL_VEVENT,
   47     ICAL_VTODO,
   48     ICAL_TYPES
   49 } ical_types_e;
   50 
   51 typedef enum {
   52     UNDEFINED,
   53     APPOINTMENT,
   54     EVENT
   55 } ical_vevent_e;
   56 
   57 typedef struct {
   58     enum recur_type type;
   59     int freq;
   60     long until;
   61     unsigned count;
   62 } ical_rpt_t;
   63 
   64 static void ical_export_header(FILE *);
   65 static void ical_export_recur_events(FILE *, int);
   66 static void ical_export_events(FILE *, int);
   67 static void ical_export_recur_apoints(FILE *, int);
   68 static void ical_export_apoints(FILE *, int);
   69 static void ical_export_todo(FILE *, int);
   70 static void ical_export_footer(FILE *);
   71 
   72 static const char *ical_recur_type[RECUR_TYPES] =
   73     { "", "DAILY", "WEEKLY", "MONTHLY", "YEARLY" };
   74 
   75 /* iCal alarm notification. */
   76 static void ical_export_valarm(FILE * stream)
   77 {
   78     fputs("BEGIN:VALARM\n", stream);
   79     pthread_mutex_lock(&nbar.mutex);
   80     fprintf(stream, "TRIGGER:-P%dS\n", nbar.cntdwn);
   81     pthread_mutex_unlock(&nbar.mutex);
   82     fputs("ACTION:DISPLAY\n", stream);
   83     fputs("END:VALARM\n", stream);
   84 }
   85 
   86 /* Export header. */
   87 static void ical_export_header(FILE * stream)
   88 {
   89     fputs("BEGIN:VCALENDAR\n", stream);
   90     fprintf(stream, "PRODID:-//calcurse//NONSGML v%s//EN\n", VERSION);
   91     fputs("VERSION:2.0\n", stream);
   92 }
   93 
   94 /* Export footer. */
   95 static void ical_export_footer(FILE * stream)
   96 {
   97     fputs("END:VCALENDAR\n", stream);
   98 }
   99 
  100 /* Export recurrent events. */
  101 static void ical_export_recur_events(FILE * stream, int export_uid)
  102 {
  103     llist_item_t *i, *j;
  104     char ical_date[BUFSIZ];
  105 
  106     LLIST_FOREACH(&recur_elist, i) {
  107         struct recur_event *rev = LLIST_GET_DATA(i);
  108         date_sec2date_fmt(rev->day, ICALDATEFMT, ical_date);
  109         fputs("BEGIN:VEVENT\n", stream);
  110         fprintf(stream, "DTSTART:%s\n", ical_date);
  111         fprintf(stream, "RRULE:FREQ=%s;INTERVAL=%d",
  112             ical_recur_type[rev->rpt->type], rev->rpt->freq);
  113 
  114         if (rev->rpt->until != 0) {
  115             date_sec2date_fmt(rev->rpt->until, ICALDATEFMT,
  116                       ical_date);
  117             fprintf(stream, ";UNTIL=%s\n", ical_date);
  118         } else {
  119             fputc('\n', stream);
  120         }
  121 
  122         if (LLIST_FIRST(&rev->exc)) {
  123             fputs("EXDATE:", stream);
  124             LLIST_FOREACH(&rev->exc, j) {
  125                 struct excp *exc = LLIST_GET_DATA(j);
  126                 date_sec2date_fmt(exc->st, ICALDATETIMEFMT,
  127                           ical_date);
  128                 fprintf(stream, "%s", ical_date);
  129                 fputc(LLIST_NEXT(j) ? ',' : '\n', stream);
  130             }
  131         }
  132 
  133         fprintf(stream, "SUMMARY:%s\n", rev->mesg);
  134 
  135         if (export_uid) {
  136             char *hash = recur_event_hash(rev);
  137             fprintf(stream, "UID:%s\n", hash);
  138             mem_free(hash);
  139         }
  140 
  141         fputs("END:VEVENT\n", stream);
  142     }
  143 }
  144 
  145 /* Export events. */
  146 static void ical_export_events(FILE * stream, int export_uid)
  147 {
  148     llist_item_t *i;
  149     char ical_date[BUFSIZ];
  150 
  151     LLIST_FOREACH(&eventlist, i) {
  152         struct event *ev = LLIST_TS_GET_DATA(i);
  153         date_sec2date_fmt(ev->day, ICALDATEFMT, ical_date);
  154         fputs("BEGIN:VEVENT\n", stream);
  155         fprintf(stream, "DTSTART;VALUE=DATE:%s\n", ical_date);
  156         fprintf(stream, "SUMMARY:%s\n", ev->mesg);
  157 
  158         if (export_uid) {
  159             char *hash = event_hash(ev);
  160             fprintf(stream, "UID:%s\n", hash);
  161             mem_free(hash);
  162         }
  163 
  164         fputs("END:VEVENT\n", stream);
  165     }
  166 }
  167 
  168 /* Export recurrent appointments. */
  169 static void ical_export_recur_apoints(FILE * stream, int export_uid)
  170 {
  171     llist_item_t *i, *j;
  172     char ical_datetime[BUFSIZ];
  173     char ical_date[BUFSIZ];
  174 
  175     LLIST_TS_LOCK(&recur_alist_p);
  176     LLIST_TS_FOREACH(&recur_alist_p, i) {
  177         struct recur_apoint *rapt = LLIST_TS_GET_DATA(i);
  178 
  179         date_sec2date_fmt(rapt->start, ICALDATETIMEFMT,
  180                   ical_datetime);
  181         fputs("BEGIN:VEVENT\n", stream);
  182         fprintf(stream, "DTSTART:%s\n", ical_datetime);
  183         if (rapt->dur > 0) {
  184             fprintf(stream, "DURATION:P%ldDT%ldH%ldM%ldS\n",
  185                 rapt->dur / DAYINSEC,
  186                 (rapt->dur / HOURINSEC) % DAYINHOURS,
  187                 (rapt->dur / MININSEC) % HOURINMIN,
  188                 rapt->dur % MININSEC);
  189         }
  190         fprintf(stream, "RRULE:FREQ=%s;INTERVAL=%d",
  191             ical_recur_type[rapt->rpt->type], rapt->rpt->freq);
  192 
  193         if (rapt->rpt->until != 0) {
  194             date_sec2date_fmt(rapt->rpt->until + HOURINSEC,
  195                       ICALDATEFMT, ical_date);
  196             fprintf(stream, ";UNTIL=%s\n", ical_date);
  197         } else {
  198             fputc('\n', stream);
  199         }
  200 
  201         if (LLIST_FIRST(&rapt->exc)) {
  202             fputs("EXDATE:", stream);
  203             LLIST_FOREACH(&rapt->exc, j) {
  204                 struct excp *exc = LLIST_GET_DATA(j);
  205                 date_sec2date_fmt(exc->st, ICALDATETIMEFMT,
  206                           ical_date);
  207                 fprintf(stream, "%s", ical_date);
  208                 fputc(LLIST_NEXT(j) ? ',' : '\n', stream);
  209             }
  210         }
  211 
  212         fprintf(stream, "SUMMARY:%s\n", rapt->mesg);
  213         if (rapt->state & APOINT_NOTIFY)
  214             ical_export_valarm(stream);
  215 
  216         if (export_uid) {
  217             char *hash = recur_apoint_hash(rapt);
  218             fprintf(stream, "UID:%s\n", hash);
  219             mem_free(hash);
  220         }
  221 
  222         fputs("END:VEVENT\n", stream);
  223     }
  224     LLIST_TS_UNLOCK(&recur_alist_p);
  225 }
  226 
  227 /* Export appointments. */
  228 static void ical_export_apoints(FILE * stream, int export_uid)
  229 {
  230     llist_item_t *i;
  231     char ical_datetime[BUFSIZ];
  232 
  233     LLIST_TS_LOCK(&alist_p);
  234     LLIST_TS_FOREACH(&alist_p, i) {
  235         struct apoint *apt = LLIST_TS_GET_DATA(i);
  236         date_sec2date_fmt(apt->start, ICALDATETIMEFMT,
  237                   ical_datetime);
  238         fputs("BEGIN:VEVENT\n", stream);
  239         fprintf(stream, "DTSTART:%s\n", ical_datetime);
  240         if (apt->dur > 0) {
  241             fprintf(stream, "DURATION:P%ldDT%ldH%ldM%ldS\n",
  242                 apt->dur / DAYINSEC,
  243                 (apt->dur / HOURINSEC) % DAYINHOURS,
  244                 (apt->dur / MININSEC) % HOURINMIN,
  245                 apt->dur % MININSEC);
  246         }
  247         fprintf(stream, "SUMMARY:%s\n", apt->mesg);
  248         if (apt->state & APOINT_NOTIFY)
  249             ical_export_valarm(stream);
  250 
  251         if (export_uid) {
  252             char *hash = apoint_hash(apt);
  253             fprintf(stream, "UID:%s\n", hash);
  254             mem_free(hash);
  255         }
  256 
  257         fputs("END:VEVENT\n", stream);
  258     }
  259     LLIST_TS_UNLOCK(&alist_p);
  260 }
  261 
  262 /* Export todo items. */
  263 static void ical_export_todo(FILE * stream, int export_uid)
  264 {
  265     llist_item_t *i;
  266 
  267     LLIST_FOREACH(&todolist, i) {
  268         struct todo *todo = LLIST_TS_GET_DATA(i);
  269 
  270         fputs("BEGIN:VTODO\n", stream);
  271         if (todo->completed)
  272             fprintf(stream, "STATUS:COMPLETED\n");
  273         fprintf(stream, "PRIORITY:%d\n", todo->id);
  274         fprintf(stream, "SUMMARY:%s\n", todo->mesg);
  275 
  276         if (export_uid) {
  277             char *hash = todo_hash(todo);
  278             fprintf(stream, "UID:%s\n", hash);
  279             mem_free(hash);
  280         }
  281 
  282         fputs("END:VTODO\n", stream);
  283     }
  284 }
  285 
  286 /* Print a header to describe import log report format. */
  287 static void ical_log_init(FILE * log, int major, int minor)
  288 {
  289     const char *header =
  290         "+-------------------------------------------------------------------+\n"
  291         "| Calcurse icalendar import log.                                    |\n"
  292         "|                                                                   |\n"
  293         "| Items imported from icalendar file, version %d.%d                   |\n"
  294         "| Some items could not be imported, they are described hereafter.   |\n"
  295         "| The log line format is as follows:                                |\n"
  296         "|                                                                   |\n"
  297         "|       TYPE [LINE]: DESCRIPTION                                    |\n"
  298         "|                                                                   |\n"
  299         "| where:                                                            |\n"
  300         "|  * TYPE represents the item type ('VEVENT' or 'VTODO')            |\n"
  301         "|  * LINE is the line in the input stream at which this item begins |\n"
  302         "|  * DESCRIPTION indicates why the item could not be imported       |\n"
  303         "+-------------------------------------------------------------------+\n\n";
  304 
  305     if (log)
  306         fprintf(log, header, major, minor);
  307 }
  308 
  309 /*
  310  * Used to build a report of the import process.
  311  * The icalendar item for which a problem occurs is mentioned (by giving its
  312  * first line inside the icalendar file), together with a message describing the
  313  * problem.
  314  */
  315 static void ical_log(FILE * log, ical_types_e type, unsigned lineno,
  316              char *msg)
  317 {
  318     const char *typestr[ICAL_TYPES] = { "VEVENT", "VTODO" };
  319 
  320     RETURN_IF(type < 0 || type >= ICAL_TYPES, _("unknown ical type"));
  321     if (!log)
  322         return;
  323 
  324     fprintf(log, "%s [%d]: %s\n", typestr[type], lineno, msg);
  325 }
  326 
  327 static void ical_store_todo(int priority, int completed, char *mesg,
  328                 char *note, const char *fmt_todo)
  329 {
  330     struct todo *todo = todo_add(mesg, priority, completed, note);
  331     if (fmt_todo)
  332         print_todo(fmt_todo, todo);
  333     mem_free(mesg);
  334     erase_note(&note);
  335 }
  336 
  337 static void
  338 ical_store_event(char *mesg, char *note, long day, long end,
  339          ical_rpt_t * rpt, llist_t * exc, const char *fmt_ev,
  340          const char *fmt_rev)
  341 {
  342     const int EVENTID = 1;
  343     struct event *ev;
  344     struct recur_event *rev;
  345 
  346     if (rpt) {
  347         rev = recur_event_new(mesg, note, day, EVENTID, rpt->type,
  348                       rpt->freq, rpt->until, exc);
  349         mem_free(rpt);
  350         if (fmt_rev)
  351             print_recur_event(fmt_rev, day, rev);
  352         goto cleanup;
  353     }
  354 
  355     if (end == 0 || end - day <= DAYINSEC) {
  356         ev = event_new(mesg, note, day, EVENTID);
  357         if (fmt_ev)
  358             print_event(fmt_ev, day, ev);
  359         goto cleanup;
  360     }
  361 
  362     /*
  363      * Here we have an event that spans over several days.
  364      *
  365      * In iCal, the end specifies when the event is supposed to end, in
  366      * calcurse, the end specifies the time that the last occurrence of the
  367      * event starts, so we need to do some conversion here.
  368      */
  369     end = day + ((end - day - 1) / DAYINSEC) * DAYINSEC;
  370     rpt = mem_malloc(sizeof(ical_rpt_t));
  371     rpt->type = RECUR_DAILY;
  372     rpt->freq = 1;
  373     rpt->count = 0;
  374     rpt->until = end;
  375     rev = recur_event_new(mesg, note, day, EVENTID, rpt->type,
  376                   rpt->freq, rpt->until, exc);
  377     mem_free(rpt);
  378     if (fmt_rev)
  379         print_recur_event(fmt_rev, day, rev);
  380 
  381 cleanup:
  382     mem_free(mesg);
  383     erase_note(&note);
  384 }
  385 
  386 static void
  387 ical_store_apoint(char *mesg, char *note, long start, long dur,
  388           ical_rpt_t * rpt, llist_t * exc, int has_alarm,
  389           const char *fmt_apt, const char *fmt_rapt)
  390 {
  391     char state = 0L;
  392     struct apoint *apt;
  393     struct recur_apoint *rapt;
  394 
  395     if (has_alarm)
  396         state |= APOINT_NOTIFY;
  397     if (rpt) {
  398         rapt = recur_apoint_new(mesg, note, start, dur, state,
  399                     rpt->type, rpt->freq, rpt->until, exc);
  400         mem_free(rpt);
  401         if (fmt_rapt)
  402             print_recur_apoint(fmt_rapt, start, rapt->start, rapt);
  403     } else {
  404         apt = apoint_new(mesg, note, start, dur, state);
  405         if (fmt_apt)
  406             print_apoint(fmt_apt, start, apt);
  407     }
  408     mem_free(mesg);
  409     erase_note(&note);
  410 }
  411 
  412 /*
  413  * Returns an allocated string representing the string given in argument once
  414  * unformatted.
  415  */
  416 static char *ical_unformat_line(char *line)
  417 {
  418     struct string s;
  419     char *p;
  420 
  421     string_init(&s);
  422     for (p = line; *p; p++) {
  423         switch (*p) {
  424         case '\\':
  425             switch (*(p + 1)) {
  426             case 'n':
  427                 string_catf(&s, "%c", '\n');
  428                 p++;
  429                 break;
  430             case 't':
  431                 string_catf(&s, "%c", '\t');
  432                 p++;
  433                 break;
  434             case ';':
  435             case ':':
  436             case ',':
  437                 string_catf(&s, "%c", *(p + 1));
  438                 p++;
  439                 break;
  440             default:
  441                 string_catf(&s, "%c", *p);
  442                 break;
  443             }
  444             break;
  445         default:
  446             string_catf(&s, "%c", *p);
  447             break;
  448         }
  449     }
  450 
  451     return string_buf(&s);
  452 }
  453 
  454 static void
  455 ical_readline_init(FILE * fdi, char *buf, char *lstore, unsigned *ln)
  456 {
  457     char *eol;
  458 
  459     *buf = *lstore = '\0';
  460     if (fgets(lstore, BUFSIZ, fdi)) {
  461         if ((eol = strchr(lstore, '\n')) != NULL) {
  462             if (*(eol - 1) == '\r')
  463                 *(eol - 1) = '\0';
  464             else
  465                 *eol = '\0';
  466         }
  467         (*ln)++;
  468     }
  469 }
  470 
  471 static int ical_readline(FILE * fdi, char *buf, char *lstore, unsigned *ln)
  472 {
  473     char *eol;
  474 
  475     strncpy(buf, lstore, BUFSIZ);
  476     (*ln)++;
  477 
  478     while (fgets(lstore, BUFSIZ, fdi) != NULL) {
  479         if ((eol = strchr(lstore, '\n')) != NULL) {
  480             if (*(eol - 1) == '\r')
  481                 *(eol - 1) = '\0';
  482             else
  483                 *eol = '\0';
  484         }
  485         if (*lstore != SPACE && *lstore != TAB)
  486             break;
  487         strncat(buf, lstore + 1, BUFSIZ - strlen(buf) - 1);
  488         (*ln)++;
  489     }
  490 
  491     if (feof(fdi)) {
  492         *lstore = '\0';
  493         if (*buf == '\0')
  494             return 0;
  495     }
  496 
  497     return 1;
  498 }
  499 
  500 static int
  501 ical_chk_header(FILE * fd, char *buf, char *lstore, unsigned *lineno,
  502         int *major, int *minor)
  503 {
  504     if (!ical_readline(fd, buf, lstore, lineno))
  505         return 0;
  506 
  507     if (!starts_with_ci(buf, "BEGIN:VCALENDAR"))
  508         return 0;
  509 
  510     while (!sscanf(buf, "VERSION:%d.%d", major, minor)) {
  511         if (!ical_readline(fd, buf, lstore, lineno))
  512             return 0;
  513     }
  514 
  515     return 1;
  516 }
  517 
  518 /*
  519  * iCalendar date-time format is based on the ISO 8601 complete
  520  * representation. It should be something like : DATE 'T' TIME
  521  * where DATE is 'YYYYMMDD' and TIME is 'HHMMSS'.
  522  * The time and 'T' separator are optional (in the case of an day-long event).
  523  *
  524  * Optionally, if the type pointer is given, specify if it is an event
  525  * (no time is given, meaning it is an all-day event), or an appointment
  526  * (time is given).
  527  *
  528  * The timezone is not yet handled by calcurse.
  529  */
  530 static time_t ical_datetime2time_t(char *datestr, ical_vevent_e * type)
  531 {
  532     const int FORMAT_DATE = 3, FORMAT_DATETIME = 6, FORMAT_DATETIMEZ = 7;
  533     struct date date;
  534     unsigned hour, min, sec;
  535     char c;
  536     int format;
  537 
  538     format = sscanf(datestr, "%04u%02u%02uT%02u%02u%02u%c",
  539             &date.yyyy, &date.mm, &date.dd, &hour, &min, &sec, &c);
  540     if (format == FORMAT_DATE) {
  541         if (type)
  542             *type = EVENT;
  543         return date2sec(date, 0, 0);
  544     } else if (format == FORMAT_DATETIME || format == FORMAT_DATETIMEZ) {
  545         if (type)
  546             *type = APPOINTMENT;
  547         if (format == FORMAT_DATETIMEZ && c == 'Z')
  548             return utcdate2sec(date, hour, min);
  549         else
  550             return date2sec(date, hour, min);
  551     }
  552     return 0;
  553 }
  554 
  555 static long ical_durtime2long(char *timestr)
  556 {
  557     char *p = timestr;
  558     int bytes_read;
  559     unsigned hour = 0, min = 0, sec = 0;
  560 
  561     if (*p != 'T')
  562         return 0;
  563     p++;
  564 
  565     if (strchr(p, 'H')) {
  566         if (sscanf(p, "%uH%n", &hour, &bytes_read) != 1)
  567             return 0;
  568         p += bytes_read;
  569     }
  570     if (strchr(p, 'M')) {
  571         if (sscanf(p, "%uM%n", &min, &bytes_read) != 1)
  572             return 0;
  573         p += bytes_read;
  574     }
  575     if (strchr(p, 'S')) {
  576         if (sscanf(p, "%uS%n", &sec, &bytes_read) != 1)
  577             return 0;
  578         p += bytes_read;
  579     }
  580 
  581     return hour * HOURINSEC + min * MININSEC + sec;
  582 }
  583 
  584 /*
  585  * Extract from RFC2445:
  586  *
  587  * Value Name: DURATION
  588  *
  589  * Purpose: This value type is used to identify properties that contain
  590  * duration of time.
  591  *
  592  * Formal Definition: The value type is defined by the following
  593  * notation:
  594  *
  595  * dur-value  = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
  596  * dur-date   = dur-day [dur-time]
  597  * dur-time   = "T" (dur-hour / dur-minute / dur-second)
  598  * dur-week   = 1*DIGIT "W"
  599  * dur-hour   = 1*DIGIT "H" [dur-minute]
  600  * dur-minute = 1*DIGIT "M" [dur-second]
  601  * dur-second = 1*DIGIT "S"
  602  * dur-day    = 1*DIGIT "D"
  603  *
  604  * Example: A duration of 15 days, 5 hours and 20 seconds would be:
  605  * P15DT5H0M20S
  606  * A duration of 7 weeks would be:
  607  * P7W
  608  */
  609 static long ical_dur2long(char *durstr)
  610 {
  611     char *p;
  612     int bytes_read;
  613     struct {
  614         unsigned week, day;
  615     } date;
  616 
  617     memset(&date, 0, sizeof date);
  618 
  619     p = strchr(durstr, 'P');
  620     if (!p)
  621         return -1;
  622     p++;
  623 
  624     if (*p == '-')
  625         return -1;
  626     if (*p == '+')
  627         p++;
  628 
  629     if (*p == 'T') {
  630         /* dur-time */
  631         return ical_durtime2long(p);
  632     } else if (strchr(p, 'W')) {
  633         /* dur-week */
  634         if (sscanf(p, "%u", &date.week) == 1)
  635             return date.week * WEEKINDAYS * DAYINSEC;
  636     } else if (strchr(p, 'D')) {
  637         /* dur-date */
  638         if (sscanf(p, "%uD%n", &date.day, &bytes_read) == 1) {
  639             p += bytes_read;
  640             return date.day * DAYINSEC + ical_durtime2long(p);
  641         }
  642     }
  643 
  644     return -1;
  645 }
  646 
  647 /*
  648  * Compute the vevent repetition end date from the repetition count.
  649  *
  650  * Extract from RFC2445:
  651  * The COUNT rule part defines the number of occurrences at which to
  652  * range-bound the recurrence. The "DTSTART" property value, if specified,
  653  * counts as the first occurrence.
  654  */
  655 static long ical_compute_rpt_until(long start, ical_rpt_t * rpt)
  656 {
  657     switch (rpt->type) {
  658     case RECUR_DAILY:
  659         return date_sec_change(start, 0, rpt->freq * (rpt->count - 1));
  660     case RECUR_WEEKLY:
  661         return date_sec_change(start, 0,
  662                 rpt->freq * WEEKINDAYS * (rpt->count - 1));
  663     case RECUR_MONTHLY:
  664         return date_sec_change(start, rpt->freq * (rpt->count - 1), 0);
  665     case RECUR_YEARLY:
  666         return date_sec_change(start,
  667                     rpt->freq * 12 * (rpt->count - 1), 0);
  668     default:
  669         return 0;
  670     }
  671 }
  672 
  673 /*
  674  * Skip to the value part of an iCalendar content line.
  675  */
  676 static char *ical_get_value(char *p)
  677 {
  678     for (; *p != ':'; p++) {
  679         if (*p == '"')
  680             for (p++; *p != '"' && *p != '\0'; p++);
  681         if (*p == '\0')
  682             return NULL;
  683     }
  684 
  685     return p + 1;
  686 }
  687 
  688 /*
  689  * Read a recurrence rule from an iCalendar RRULE string.
  690  *
  691  * Value Name: RECUR
  692  *
  693  * Purpose: This value type is used to identify properties that contain
  694  * a recurrence rule specification.
  695  *
  696  * Formal Definition: The value type is defined by the following
  697  * notation:
  698  *
  699  * recur      = "FREQ"=freq *(
  700  *
  701  * ; either UNTIL or COUNT may appear in a 'recur',
  702  * ; but UNTIL and COUNT MUST NOT occur in the same 'recur'
  703  *
  704  * ( ";" "UNTIL" "=" enddate ) /
  705  * ( ";" "COUNT" "=" 1*DIGIT ) /
  706  *
  707  * ; the rest of these keywords are optional,
  708  * ; but MUST NOT occur more than
  709  * ; once
  710  *
  711  * ( ";" "INTERVAL" "=" 1*DIGIT )          /
  712  * ( ";" "BYSECOND" "=" byseclist )        /
  713  * ( ";" "BYMINUTE" "=" byminlist )        /
  714  * ( ";" "BYHOUR" "=" byhrlist )           /
  715  * ( ";" "BYDAY" "=" bywdaylist )          /
  716  * ( ";" "BYMONTHDAY" "=" bymodaylist )    /
  717  * ( ";" "BYYEARDAY" "=" byyrdaylist )     /
  718  * ( ";" "BYWEEKNO" "=" bywknolist )       /
  719  * ( ";" "BYMONTH" "=" bymolist )          /
  720  * ( ";" "BYSETPOS" "=" bysplist )         /
  721  * ( ";" "WKST" "=" weekday )              /
  722  * ( ";" x-name "=" text )
  723  * )
  724 */
  725 static ical_rpt_t *ical_read_rrule(FILE * log, char *rrulestr,
  726                    unsigned *noskipped, const int itemline)
  727 {
  728     const char count[] = "COUNT=";
  729     const char interv[] = "INTERVAL=";
  730     char freqstr[BUFSIZ];
  731     unsigned interval;
  732     ical_rpt_t *rpt;
  733     char *p;
  734 
  735     p = ical_get_value(rrulestr);
  736     if (!p) {
  737         ical_log(log, ICAL_VEVENT, itemline,
  738              _("recurrence rule malformed."));
  739         (*noskipped)++;
  740         return NULL;
  741     }
  742 
  743     rpt = mem_malloc(sizeof(ical_rpt_t));
  744     memset(rpt, 0, sizeof(ical_rpt_t));
  745     if (sscanf(p, "FREQ=%s", freqstr) != 1) {
  746         ical_log(log, ICAL_VEVENT, itemline,
  747              _("recurrence frequency not found."));
  748         (*noskipped)++;
  749         mem_free(rpt);
  750         return NULL;
  751     }
  752 
  753     if (starts_with(freqstr, "DAILY")) {
  754         rpt->type = RECUR_DAILY;
  755     } else if (starts_with(freqstr, "WEEKLY")) {
  756         rpt->type = RECUR_WEEKLY;
  757     } else if (starts_with(freqstr, "MONTHLY")) {
  758         rpt->type = RECUR_MONTHLY;
  759     } else if (starts_with(freqstr, "YEARLY")) {
  760         rpt->type = RECUR_YEARLY;
  761     } else {
  762         ical_log(log, ICAL_VEVENT, itemline,
  763              _("recurrence frequency not recognized."));
  764         (*noskipped)++;
  765         mem_free(rpt);
  766         return NULL;
  767     }
  768 
  769     /*
  770      * The UNTIL rule part defines a date-time value which bounds the
  771      * recurrence rule in an inclusive manner.  If not present, and the
  772      * COUNT rule part is also not present, the RRULE is considered to
  773      * repeat forever.
  774 
  775      * The COUNT rule part defines the number of occurrences at which to
  776      * range-bound the recurrence.  The "DTSTART" property value, if
  777      * specified, counts as the first occurrence.
  778      */
  779     if ((p = strstr(rrulestr, "UNTIL")) != NULL) {
  780         rpt->until = ical_datetime2time_t(strchr(p, '=') + 1, NULL);
  781     } else {
  782         unsigned cnt;
  783         char *countstr;
  784 
  785         rpt->until = 0;
  786         if ((countstr = strstr(rrulestr, count))) {
  787             countstr += sizeof(count) - 1;
  788             if (sscanf(countstr, "%u", &cnt) == 1)
  789                 rpt->count = cnt;
  790         }
  791     }
  792 
  793     rpt->freq = 1;
  794     if ((p = strstr(rrulestr, interv))) {
  795         p += sizeof(interv) - 1;
  796         if (sscanf(p, "%u", &interval) == 1)
  797             rpt->freq = interval;
  798     }
  799 
  800     return rpt;
  801 }
  802 
  803 static void ical_add_exc(llist_t * exc_head, long date)
  804 {
  805     if (date == 0)
  806         return;
  807 
  808     struct excp *exc = mem_malloc(sizeof(struct excp));
  809     exc->st = date;
  810 
  811     LLIST_ADD(exc_head, exc);
  812 }
  813 
  814 /*
  815  * This property defines the list of date/time exceptions for a
  816  * recurring calendar component.
  817  */
  818 static void
  819 ical_read_exdate(llist_t * exc, FILE * log, char *exstr,
  820          unsigned *noskipped, const int itemline)
  821 {
  822     char *p, *q;
  823 
  824     p = ical_get_value(exstr);
  825     if (!p) {
  826         ical_log(log, ICAL_VEVENT, itemline,
  827              _("recurrence exception dates malformed."));
  828         (*noskipped)++;
  829         return;
  830     }
  831 
  832     while ((q = strchr(p, ',')) != NULL) {
  833         char buf[BUFSIZ];
  834         const int buflen = q - p;
  835 
  836         strncpy(buf, p, buflen);
  837         buf[buflen] = '\0';
  838         ical_add_exc(exc, ical_datetime2time_t(buf, NULL));
  839         p = ++q;
  840     }
  841     ical_add_exc(exc, ical_datetime2time_t(p, NULL));
  842 }
  843 
  844 /* Return an allocated string containing the name of the newly created note. */
  845 static char *ical_read_note(char *line, unsigned *noskipped,
  846                 ical_types_e item_type, const int itemline,
  847                 FILE * log)
  848 {
  849     char *p, *notestr, *note;
  850 
  851     p = ical_get_value(line);
  852     if (!p) {
  853         ical_log(log, item_type, itemline,
  854              _("description malformed."));
  855         (*noskipped)++;
  856         return NULL;
  857     }
  858 
  859     notestr = ical_unformat_line(p);
  860     if (notestr == NULL) {
  861         ical_log(log, item_type, itemline,
  862              _("could not get entire item description."));
  863         (*noskipped)++;
  864         return NULL;
  865     } else if (strlen(notestr) == 0) {
  866         mem_free(notestr);
  867         return NULL;
  868     } else {
  869         note = generate_note(notestr);
  870         mem_free(notestr);
  871         return note;
  872     }
  873 }
  874 
  875 /* Returns an allocated string containing the ical item summary. */
  876 static char *ical_read_summary(char *line)
  877 {
  878     char *p, *summary;
  879 
  880     p = ical_get_value(line);
  881     if (!p)
  882         return NULL;
  883 
  884     summary = ical_unformat_line(p);
  885     if (!summary)
  886         return NULL;
  887 
  888     /* Event summaries must not contain newlines. */
  889     for (p = strchr(summary, '\n'); p; p = strchr(p, '\n'))
  890         *p = ' ';
  891 
  892     return summary;
  893 }
  894 
  895 static void
  896 ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
  897         unsigned *noapoints, unsigned *noskipped, char *buf,
  898         char *lstore, unsigned *lineno, const char *fmt_ev,
  899         const char *fmt_rev, const char *fmt_apt, const char *fmt_rapt)
  900 {
  901     const int ITEMLINE = *lineno;
  902     ical_vevent_e vevent_type;
  903     char *p;
  904     struct {
  905         llist_t exc;
  906         ical_rpt_t *rpt;
  907         char *mesg, *note;
  908         long start, end, dur;
  909         int has_alarm;
  910     } vevent;
  911     int skip_alarm;
  912 
  913     vevent_type = UNDEFINED;
  914     memset(&vevent, 0, sizeof vevent);
  915     LLIST_INIT(&vevent.exc);
  916     skip_alarm = 0;
  917     while (ical_readline(fdi, buf, lstore, lineno)) {
  918         if (skip_alarm) {
  919             /*
  920              * Need to skip VALARM properties because some keywords
  921              * could interfere, such as DURATION, SUMMARY,..
  922              */
  923             if (starts_with_ci(buf, "END:VALARM"))
  924                 skip_alarm = 0;
  925             continue;
  926         }
  927 
  928         if (starts_with_ci(buf, "END:VEVENT")) {
  929             if (!vevent.mesg) {
  930                 ical_log(log, ICAL_VEVENT, ITEMLINE,
  931                      _("could not retrieve item summary."));
  932                 goto cleanup;
  933             }
  934             if (vevent.start == 0) {
  935                 ical_log(log, ICAL_VEVENT, ITEMLINE,
  936                      _("item start date is not defined."));
  937                 goto cleanup;
  938             }
  939 
  940             if (vevent_type == APPOINTMENT && vevent.dur == 0) {
  941                 if (vevent.end != 0) {
  942                     vevent.dur = vevent.end - vevent.start;
  943                 }
  944 
  945                 if (vevent.dur < 0) {
  946                     ical_log(log, ICAL_VEVENT, ITEMLINE,
  947                         _("item has a negative duration."));
  948                     goto cleanup;
  949                 }
  950             }
  951 
  952             if (vevent.rpt && vevent.rpt->count) {
  953                 vevent.rpt->until =
  954                     ical_compute_rpt_until(vevent.start,
  955                             vevent.rpt);
  956             }
  957 
  958             switch (vevent_type) {
  959             case APPOINTMENT:
  960                 ical_store_apoint(vevent.mesg, vevent.note,
  961                         vevent.start, vevent.dur,
  962                         vevent.rpt, &vevent.exc,
  963                         vevent.has_alarm, fmt_apt,
  964                         fmt_rapt);
  965                 (*noapoints)++;
  966                 break;
  967             case EVENT:
  968                 ical_store_event(vevent.mesg, vevent.note,
  969                         vevent.start, vevent.end,
  970                         vevent.rpt, &vevent.exc,
  971                         fmt_ev, fmt_rev);
  972                 (*noevents)++;
  973                 break;
  974             case UNDEFINED:
  975                 ical_log(log, ICAL_VEVENT, ITEMLINE,
  976                      _("item could not be identified."));
  977                 goto cleanup;
  978                 break;
  979             }
  980 
  981             return;
  982         }
  983 
  984         if (starts_with_ci(buf, "DTSTART")) {
  985             p = ical_get_value(buf);
  986             if (!p) {
  987                 ical_log(log, ICAL_VEVENT, ITEMLINE,
  988                      _("event start time malformed."));
  989                 goto cleanup;
  990             }
  991 
  992             vevent.start = ical_datetime2time_t(p, &vevent_type);
  993             if (!vevent.start) {
  994                 ical_log(log, ICAL_VEVENT, ITEMLINE,
  995                      _("could not retrieve event start time."));
  996                 goto cleanup;
  997             }
  998         } else if (starts_with_ci(buf, "DTEND")) {
  999             p = ical_get_value(buf);
 1000             if (!p) {
 1001                 ical_log(log, ICAL_VEVENT, ITEMLINE,
 1002                      _("event end time malformed."));
 1003                 goto cleanup;
 1004             }
 1005 
 1006             vevent.end = ical_datetime2time_t(p, &vevent_type);
 1007             if (!vevent.end) {
 1008                 ical_log(log, ICAL_VEVENT, ITEMLINE,
 1009                      _("could not retrieve event end time."));
 1010                 goto cleanup;
 1011             }
 1012         } else if (starts_with_ci(buf, "DURATION")) {
 1013             vevent.dur = ical_dur2long(buf);
 1014             if (vevent.dur <= 0) {
 1015                 ical_log(log, ICAL_VEVENT, ITEMLINE,
 1016                      _("item duration malformed."));
 1017                 goto cleanup;
 1018             }
 1019         } else if (starts_with_ci(buf, "RRULE")) {
 1020             vevent.rpt = ical_read_rrule(log, buf, noskipped,
 1021                     ITEMLINE);
 1022         } else if (starts_with_ci(buf, "EXDATE")) {
 1023             ical_read_exdate(&vevent.exc, log, buf, noskipped,
 1024                     ITEMLINE);
 1025         } else if (starts_with_ci(buf, "SUMMARY")) {
 1026             vevent.mesg = ical_read_summary(buf);
 1027         } else if (starts_with_ci(buf, "BEGIN:VALARM")) {
 1028             skip_alarm = vevent.has_alarm = 1;
 1029         } else if (starts_with_ci(buf, "DESCRIPTION")) {
 1030             vevent.note = ical_read_note(buf, noskipped,
 1031                     ICAL_VEVENT, ITEMLINE, log);
 1032         }
 1033     }
 1034 
 1035     ical_log(log, ICAL_VEVENT, ITEMLINE,
 1036          _("The ical file seems to be malformed. "
 1037            "The end of item was not found."));
 1038 
 1039 cleanup:
 1040 
 1041     if (vevent.note)
 1042         mem_free(vevent.note);
 1043     if (vevent.mesg)
 1044         mem_free(vevent.mesg);
 1045     if (vevent.rpt)
 1046         mem_free(vevent.rpt);
 1047     LLIST_FREE(&vevent.exc);
 1048     (*noskipped)++;
 1049 }
 1050 
 1051 static void
 1052 ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped,
 1053            char *buf, char *lstore, unsigned *lineno, const char *fmt_todo)
 1054 {
 1055     const int ITEMLINE = *lineno;
 1056     struct {
 1057         char *mesg, *note;
 1058         int priority;
 1059         int completed;
 1060     } vtodo;
 1061     int skip_alarm;
 1062 
 1063     memset(&vtodo, 0, sizeof vtodo);
 1064     skip_alarm = 0;
 1065     while (ical_readline(fdi, buf, lstore, lineno)) {
 1066         if (skip_alarm) {
 1067             /*
 1068              * Need to skip VALARM properties because some keywords
 1069              * could interfere, such as DURATION, SUMMARY,..
 1070              */
 1071             if (starts_with_ci(buf, "END:VALARM"))
 1072                 skip_alarm = 0;
 1073             continue;
 1074         }
 1075 
 1076         if (starts_with_ci(buf, "END:VTODO")) {
 1077             if (!vtodo.mesg) {
 1078                 ical_log(log, ICAL_VTODO, ITEMLINE,
 1079                      _("could not retrieve item summary."));
 1080                 goto cleanup;
 1081             }
 1082 
 1083             ical_store_todo(vtodo.priority, vtodo.completed,
 1084                     vtodo.mesg, vtodo.note, fmt_todo);
 1085             (*notodos)++;
 1086             return;
 1087         }
 1088 
 1089         if (starts_with_ci(buf, "PRIORITY:")) {
 1090             sscanf(buf, "PRIORITY:%d\n", &vtodo.priority);
 1091             if (vtodo.priority < 0 || vtodo.priority > 9) {
 1092                 ical_log(log, ICAL_VTODO, ITEMLINE,
 1093                      _("item priority is invalid "
 1094                       "(must be between 0 and 9)."));
 1095             }
 1096         } else if (starts_with_ci(buf, "STATUS:COMPLETED")) {
 1097             vtodo.completed = 1;
 1098         } else if (starts_with_ci(buf, "SUMMARY")) {
 1099             vtodo.mesg = ical_read_summary(buf);
 1100         } else if (starts_with_ci(buf, "BEGIN:VALARM")) {
 1101             skip_alarm = 1;
 1102         } else if (starts_with_ci(buf, "DESCRIPTION")) {
 1103             vtodo.note = ical_read_note(buf, noskipped, ICAL_VTODO,
 1104                     ITEMLINE, log);
 1105         }
 1106     }
 1107 
 1108     ical_log(log, ICAL_VTODO, ITEMLINE,
 1109          _("The ical file seems to be malformed. "
 1110            "The end of item was not found."));
 1111 
 1112 cleanup:
 1113     if (vtodo.note)
 1114         mem_free(vtodo.note);
 1115     if (vtodo.mesg)
 1116         mem_free(vtodo.mesg);
 1117     (*noskipped)++;
 1118 }
 1119 
 1120 /* Import calcurse data. */
 1121 void
 1122 ical_import_data(FILE * stream, FILE * log, unsigned *events,
 1123          unsigned *apoints, unsigned *todos, unsigned *lines,
 1124          unsigned *skipped, const char *fmt_ev, const char *fmt_rev,
 1125          const char *fmt_apt, const char *fmt_rapt,
 1126          const char *fmt_todo)
 1127 {
 1128     char buf[BUFSIZ], lstore[BUFSIZ];
 1129     int major, minor;
 1130 
 1131     ical_readline_init(stream, buf, lstore, lines);
 1132     RETURN_IF(!ical_chk_header
 1133           (stream, buf, lstore, lines, &major, &minor),
 1134           _("Warning: ical header malformed or wrong version number. "
 1135            "Aborting..."));
 1136 
 1137     ical_log_init(log, major, minor);
 1138 
 1139     while (ical_readline(stream, buf, lstore, lines)) {
 1140         (*lines)++;
 1141         if (starts_with_ci(buf, "BEGIN:VEVENT")) {
 1142             ical_read_event(stream, log, events, apoints,
 1143                     skipped, buf, lstore, lines, fmt_ev,
 1144                     fmt_rev, fmt_apt, fmt_rapt);
 1145         } else if (starts_with_ci(buf, "BEGIN:VTODO")) {
 1146             ical_read_todo(stream, log, todos, skipped, buf,
 1147                        lstore, lines, fmt_todo);
 1148         }
 1149     }
 1150 }
 1151 
 1152 /* Export calcurse data. */
 1153 void ical_export_data(FILE * stream, int export_uid)
 1154 {
 1155     ical_export_header(stream);
 1156     ical_export_recur_events(stream, export_uid);
 1157     ical_export_events(stream, export_uid);
 1158     ical_export_recur_apoints(stream, export_uid);
 1159     ical_export_apoints(stream, export_uid);
 1160     ical_export_todo(stream, export_uid);
 1161     ical_export_footer(stream);
 1162 }