/* * vcalical - An vcal/ical converter * Copyright (C) 2006 Daniel Gollub * Copyright (C) 2006 Christopher Stender * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "xml-support.h" #include "vformat.h" #include "xml-vcal.h" #include /* ical 2 vcal */ #define ATTR_COUNT (sizeof(rrule_attr)/sizeof(rrule_attr[0])) #define PARAM_COUNT (sizeof(rrule_param)/sizeof(rrule_param[0])) enum { FIELD_FREQ, FIELD_INTERVAL, FIELD_FREQMOD, FIELD_FREQMOD2, FIELD_COUNTUNTIL, NUM_OF_FIELDS }; enum { TYPE_ATTR, TYPE_PARAM }; struct _rrule_attr { const char *ical; const char *vcal; int field; } rrule_attr[] = { { "BYDAY", " ", FIELD_FREQMOD }, { "BYMONTH", " ", FIELD_FREQMOD }, { "BYMONTHDAY", " ", FIELD_FREQMOD }, { "BYYEARDAY", " ", FIELD_FREQMOD }, { "COUNT", " #", FIELD_COUNTUNTIL }, { "FREQ", "", FIELD_FREQ }, { "INTERVAL", "", FIELD_INTERVAL }, { "UNTIL", " ", FIELD_COUNTUNTIL } }; struct _rrule_param { const char *ical; const char *vcal; } rrule_param[] = { { "DAILY", "D" }, { "MONTHLY", "M" }, { "WEEKLY", "W" }, { "YEARLY", "YM" } }; static int comp_attr(const void *m1, const void *m2) { struct _rrule_attr *mi1 = (struct _rrule_attr *) m1; struct _rrule_attr *mi2 = (struct _rrule_attr *) m2; return strcmp(mi1->ical, mi2->ical); } static int comp_param(const void *m1, const void *m2) { struct _rrule_param *mi1 = (struct _rrule_param *) m1; struct _rrule_param *mi2 = (struct _rrule_param *) m2; return strcmp(mi1->ical, mi2->ical); } struct _rrule_attr *_parse_rrule_attr(const char *ical) { struct _rrule_attr key, *res; key.ical = ical; res = bsearch(&key, rrule_attr, ATTR_COUNT, sizeof(struct _rrule_attr), comp_attr); if (!res) return NULL; return res; } const char *_parse_rrule_param(const char *ical) { struct _rrule_param key, *res; const char *ret = NULL; key.ical = ical; res = bsearch(&key, rrule_param, PARAM_COUNT, sizeof(struct _rrule_param), comp_param); if (!res) ret = ical; else ret = res->vcal; return ret; } char *_blank_field(char *field) { if (field) g_free(field); return g_strdup(""); } char *_adapt_param(const char *param) { int i, len; GString *ret = g_string_new(""); len = strlen(param); for (i=0; i < len; i++) { switch(param[i]) { // evil sperators like ',' case ',': ret = g_string_append_c(ret, ' '); break; default: ret = g_string_append_c(ret,param[i]); } } return g_string_free(ret, FALSE); } void _vcal_hook(char **icalattrs, char **vcalattrs, char **icalparams, char **vcalparams) { if (!strcmp(icalparams[FIELD_FREQ], "MONTHLY")) { // Workround for RRULE:MP1 1+ SU 20071003T193000 if(!strcmp(icalattrs[FIELD_FREQMOD], "BYDAY")) { char sign = '+'; char wday[3]; int nthday; g_free(vcalparams[FIELD_FREQ]); vcalparams[FIELD_FREQ] = g_strdup("MP"); g_free(vcalparams[FIELD_FREQMOD]); if (strlen(icalparams[FIELD_FREQMOD]) > 3) sscanf(icalparams[FIELD_FREQMOD], "%c%d%c%c", &sign, &nthday, &wday[0], &wday[1]); else sscanf(icalparams[FIELD_FREQMOD], "%d%c%c", &nthday, &wday[0], &wday[1]); wday[2] = '\0'; vcalparams[FIELD_FREQMOD] = g_strdup_printf("%d%c %s", nthday, sign, wday); // Workaround for RRULE:MD1 ....... // } else if (!strcmp(icalattrs[FIELD_FREQMOD], "BYMONTHDAY")) { } else { g_free(vcalparams[FIELD_FREQ]); vcalparams[FIELD_FREQ] = g_strdup("MD"); } } if (!strcmp(icalparams[FIELD_FREQ], "YEARLY") && icalparams[FIELD_FREQMOD]) { if (!strcmp(icalattrs[FIELD_FREQMOD], "BYYEARDAY")) { g_free(vcalparams[FIELD_FREQ]); vcalparams[FIELD_FREQ] = g_strdup("YD"); } else if ((!strcmp(icalattrs[FIELD_FREQMOD], "BYMONTH") && !strcmp(icalattrs[FIELD_FREQMOD2], "BYMONTHDAY")) || (!strcmp(icalattrs[FIELD_FREQMOD2], "BYMONTH") && !strcmp(icalattrs[FIELD_FREQMOD], "BYMONTHDAY"))) { g_free(vcalparams[FIELD_FREQ]); vcalparams[FIELD_FREQ] = g_strdup("YM"); vcalattrs[FIELD_FREQMOD] = _blank_field(vcalattrs[FIELD_FREQMOD]); vcalattrs[FIELD_FREQMOD2] = _blank_field(vcalattrs[FIELD_FREQMOD2]); vcalparams[FIELD_FREQMOD] = _blank_field(vcalparams[FIELD_FREQMOD]); vcalparams[FIELD_FREQMOD2] = _blank_field(vcalparams[FIELD_FREQMOD2]); } } // Set INTERVAL to 1 if nothing is set and BYMONTHDAY is not used if (icalparams[FIELD_INTERVAL] == NULL) { vcalparams[FIELD_INTERVAL] = g_strdup("1"); } } char *conv_ical2vcal_rrule(const char *ical) { osync_trace(TRACE_ENTRY, "%s(%s)", __func__, ical); int i; const char *pos, *prev; size_t len; char *icalattrs[NUM_OF_FIELDS] = { NULL }; char *vcalattrs[NUM_OF_FIELDS] = { NULL }; char *icalparams[NUM_OF_FIELDS] = { NULL }; char *vcalparams[NUM_OF_FIELDS] = { NULL }; struct _rrule_attr *field_attr; const char *tmp = NULL; GString *vcal10 = g_string_new(""); pos = prev = ical; // FREQ=WEEKLY;INTERVAL=1;BYDAY=TU,FR;UNTIL=20060901T182200Z // W1 TU FR 20060901T182200Z // *FREQ**INTERVAL* *FREQ-MOD* *COUNT/UNTIL* while ((pos = strstr(pos, "="))) { GString *attr = g_string_new(""); GString *param = g_string_new(""); len = pos - prev; // not equal is required ... ignoring = for (i=0; i < len; i++) attr = g_string_append_c(attr, prev[i]); pos++; prev = pos; pos = strstr(pos, ";"); if (pos == NULL) pos = ical + strlen(ical); len = pos - prev; for (i=0; i < len; i++) param = g_string_append_c(param, prev[i]); field_attr = _parse_rrule_attr(attr->str); if (field_attr == NULL) goto next; if (icalattrs[field_attr->field] && field_attr->field == FIELD_FREQMOD) field_attr->field += 1; vcalattrs[field_attr->field] = g_strdup(field_attr->vcal); icalattrs[field_attr->field] = g_strdup(attr->str); tmp = _parse_rrule_param(param->str); if (tmp) vcalparams[field_attr->field] = _adapt_param(tmp); else vcalparams[field_attr->field] = g_strdup(""); icalparams[field_attr->field] = g_strdup(param->str); g_string_free(attr, TRUE); g_string_free(param, TRUE); next: prev = pos + 1; } for (i=0; i < NUM_OF_FIELDS; i++) { if (!vcalparams[i]) vcalparams[i] = g_strdup(""); if (!vcalattrs[i]) vcalattrs[i] = g_strdup(""); if (!vcalparams[i]) vcalparams[i] = g_strdup(""); if (!icalattrs[i]) icalattrs[i] = g_strdup(""); } _vcal_hook(icalattrs, vcalattrs, icalparams, vcalparams); for (i=0; i < NUM_OF_FIELDS; i++) { // If no end is set append #0 - recurrence for ever if (i == FIELD_COUNTUNTIL && strlen(vcalparams[i]) == 0) vcalparams[i] = g_strdup(" #0"); if (vcalattrs[i]) { vcal10 = g_string_append(vcal10, vcalattrs[i]); // printf("(%i) \"%s\"\n", i, vcalattrs[i]); g_free(vcalattrs[i]); } if (vcalparams[i]) { vcal10 = g_string_append(vcal10, vcalparams[i]); // printf("(#%i) \"%s\"\n", i, vcalparams[i]); g_free(vcalparams[i]); } if (icalattrs[i]) g_free(icalattrs[i]); if (icalparams[i]) g_free(icalparams[i]); } osync_trace(TRACE_EXIT, "%s: %s", __func__, vcal10->str); return g_string_free(vcal10, FALSE); } /* vcal 2 ical */ GList *conv_vcal2ical_rrule(const char *vcal) { osync_trace(TRACE_ENTRY, "%s(%s)", __func__, vcal); gchar** blocks = g_strsplit(vcal, " ", 256); int offset = 0; int frequency_state = 0; char *frequency = NULL; char *frequency_block = NULL; int interval; int duration_number = -1; char *duration_block; char* freq_mod = NULL; /* count blocks */ int counter; for(counter=0; blocks[counter]; counter++); frequency_block = blocks[0]; duration_block = blocks[counter-1]; /* get frequency: only D(1), W(2), MP(3), MD(4), YD(5) and YM(6) is allowed */ switch (*frequency_block++) { case 'D': frequency_state = 1; frequency = "DAILY"; break; case 'W': frequency_state = 2; frequency = "WEEKLY"; break; case 'M': frequency_state = 0; switch (*frequency_block++) { case 'P': frequency_state = 3; frequency = "MONTHLY"; break; case 'D': frequency_state = 4; frequency = "MONTHLY"; break; default: osync_trace(TRACE_INTERNAL, "invalid frequency M"); } break; case 'Y': frequency_state = 0; switch (*frequency_block++) { case 'D': frequency_state = 5; frequency = "YEARLY"; break; case 'M': frequency_state = 6; frequency = "YEARLY"; break; default: osync_trace(TRACE_INTERNAL, "invalid frequency Y"); } break; default: osync_trace(TRACE_INTERNAL, "invalid or missing frequency"); } /* get interval (integer) */ char* e; interval = strtol(frequency_block, &e, 10); if (e == frequency_block) { osync_trace(TRACE_INTERNAL, "interval is missing."); } if (*e != 0) { osync_trace(TRACE_INTERNAL, "interval is to long."); } /* get frequency modifier if there are more than two blocks */ if (counter > 2) { GString *fm_buffer = g_string_new(""); int i; /* for each modifier do... */ for(i=1; i < counter-1; i++) { int count; char sign; if(fm_buffer->len > 0) g_string_append(fm_buffer, ","); /* check frequency modifier */ if (sscanf(blocks[i], "%d%c" , &count, &sign) == 2) { /* we need to convert $COUNT- to -$COUNT -> RFC2445 */ if (sign == '-') count = -count; g_string_append_printf(fm_buffer, "%d", count); if (i < counter-2 && !sscanf(blocks[i+1], "%d", &count)) { g_string_append_printf(fm_buffer, " %s", blocks[i+1]); i++; } } else { /* e.g. Day or 'LD' (Last day) */ g_string_append(fm_buffer, blocks[i]); } } freq_mod = fm_buffer->str; g_string_free(fm_buffer, FALSE); } char *until = NULL; /* get duration (number OR timestamp, but nothing is required) */ if (sscanf(duration_block, "#%d", &duration_number) < 1) { if (!osync_time_isdate(duration_block)) { /* Check if this duration_block is a localtime timestamp. * If it is not UTC change the offset from 0 to the system UTC offset. * vcal doesn't store any TZ information. This means the device have to be * in the same Timezone as the host. */ if (!osync_time_isutc(duration_block)) { struct tm *ttm = osync_time_vtime2tm(duration_block); offset = osync_time_timezone_diff(ttm); g_free(ttm); } until = osync_time_vtime2utc(duration_block, offset); } else { until = g_strdup(duration_block); } } g_strfreev(blocks); /* generate new RRULE: D(1), W(2), MP(3), MD(4), YD(5) and YM(6) */ GList *new_rrule = NULL; new_rrule = g_list_append(new_rrule, g_strdup_printf("FREQ=%s", frequency)); new_rrule = g_list_append(new_rrule, g_strdup_printf("INTERVAL=%d", interval)); if (duration_number > 0) new_rrule = g_list_append(new_rrule, g_strdup_printf("COUNT=%d", duration_number)); if(freq_mod != NULL) { switch(frequency_state) { case 2: new_rrule = g_list_append(new_rrule, g_strdup_printf("BYDAY=%s", freq_mod)); break; case 3: new_rrule = g_list_append(new_rrule, g_strdup_printf("BYDAY=%s", freq_mod)); break; case 4: new_rrule = g_list_append(new_rrule, g_strdup_printf("BYMONTHDAY=%s", freq_mod)); break; case 5: new_rrule = g_list_append(new_rrule, g_strdup_printf("BYYEARDAY=%s", freq_mod)); break; case 6: new_rrule = g_list_append(new_rrule, g_strdup_printf("BYMONTH=%s", freq_mod)); break; default: break; } } if (until != NULL) { new_rrule = g_list_append(new_rrule, g_strdup_printf("UNTIL=%s", until)); g_free(until); } osync_trace(TRACE_EXIT, "%s", __func__); return new_rrule; }