/* MultiSync - A PIM data synchronization program Copyright (C) 2002-2003 Bo Lincoln This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation; In addition, as a special exception, Bo Lincoln gives permission to link the code of this program with the OpenSSL library (or with modified versions of OpenSSL that use the same license as OpenSSL), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than OpenSSL. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED. */ /* * $Id: sync_vtype.c,v 1.28 2004/04/06 10:34:07 lincoln Exp $ */ #include #include #include #include #include #include #include #include #include "sync_vtype.h" typedef enum { ALARM_ACTION_AUDIO, ALARM_ACTION_DISPLAY, ALARM_ACTION_EMAIL, ALARM_ACTION_PROCEDURE } vtype_alarm_action; typedef enum { VTYPE_UNKNOWN = 0, VTYPE_VCALENDAR10 = 0x1, VTYPE_VCALENDAR20 = 0x2, VTYPE_VCALENDAR = 0x3, VTYPE_VCARD21 = 0x4, VTYPE_VCARD30 = 0x8, VTYPE_VCARD = 0xc } vtype_type; // Parse and correct a number of errors in the VCARD/VTODO data // The "opts" parameter decides which errors/features that should be corrected char* sync_vtype_convert(char *card, sync_voption opts, char* charset) { GString *outcard; char *incard = g_strdup(card), *ret = NULL; gboolean alarmmode = FALSE; // If in alarm clause of vCAL 2.0 char *alarmdescription = NULL; time_t alarmtime = 0, alarmduration = 0; vtype_alarm_action alarmaction = ALARM_ACTION_DISPLAY; int alarmrepeat = 0; gboolean output = TRUE; // If feedthrough output is enabled vtype_type datatype = VTYPE_UNKNOWN; outcard = g_string_new(""); while (incard) { char* endln; char head[256]; endln = strstr(incard, "\n"); if (endln) endln[0] = 0; // End string at end of line if (endln && endln > incard && endln[-1] == '\r') endln[-1] = 0; if (strlen(incard) > 1 && sscanf(incard, "%255[^:]:", head) >= 1 && incard[strlen(head)] == ':') { char *line = head; char *endent; char name[256]; char *data = strstr(incard, ":"); gboolean qp = FALSE; gboolean fixdst = FALSE; gboolean nocategorytel = FALSE; gboolean trigger = FALSE; gboolean triggervaluedatetime = FALSE, triggerrelatedend = FALSE; gboolean ver2rrule = FALSE, ver1rrule = FALSE; gboolean addfn = FALSE; gboolean addlabel = FALSE; gboolean adr = FALSE; gboolean fixlocaltime = FALSE; gboolean fixutc = FALSE; gboolean dt2to1 = FALSE, dt1to2 = FALSE; gboolean dtend = FALSE, alldayevent = FALSE; char adrtype[256] = ""; char linecharset[256] = ""; unsigned char *value = NULL; if (data) value = g_strdup(data+1); else value = g_strdup(""); output = TRUE; endent = strstr(line, ";"); if (strstr(line, "QUOTED-PRINTABLE")) // Sloppy detection of QP data, means that =\n is followed by more qp = TRUE; while (endln && (endln[1] == ' ' || (qp && value[strlen(value)-1] == '='))) { // Extend line-broken data char *tmp; incard = endln+1; endln = strstr(incard, "\n"); if (endln) endln[0] = 0; // End string at end of line if (endln && endln > incard && endln[-1] == '\r') endln[-1] = 0; if (qp) tmp = g_strdup_printf("%s\r\n%s", value, incard); else tmp = g_strdup_printf("%s%s", value, incard+1); g_free(value); value = tmp; } qp = FALSE; // Do a proper QP detection if (endent) endent[0] = 0; strncpy(name, line, 255); if (!g_strcasecmp(name, "BEGIN")) { if (!g_strcasecmp(value, "VCALENDAR")) datatype = VTYPE_VCALENDAR; if (!g_strcasecmp(value, "VCARD")) datatype = VTYPE_VCARD; } if (!g_strcasecmp(name, "VERSION")) { if (!g_strcasecmp(value, "1.0") && (datatype&VTYPE_VCALENDAR)) datatype = VTYPE_VCALENDAR10; if (!g_strcasecmp(value, "2.0") && (datatype&VTYPE_VCALENDAR)) datatype = VTYPE_VCALENDAR20; if (!g_strcasecmp(value, "2.1") && (datatype&VTYPE_VCARD)) datatype = VTYPE_VCARD21; if (!g_strcasecmp(value, "3.0") && (datatype&VTYPE_VCARD)) datatype = VTYPE_VCARD30; } if ((opts & (VOPTION_FIXDSTTOCLIENT|VOPTION_FIXDSTFROMCLIENT)) && (!g_strcasecmp(name, "DTSTART") || !g_strcasecmp(name, "DTEND"))) fixdst = TRUE; if ((opts & (VOPTION_CONVERTUTC)) && (!g_strcasecmp(name, "DTSTART") || !g_strcasecmp(name, "DTEND"))) fixlocaltime = TRUE; if ((opts & (VOPTION_REMOVEUTC)) && (!g_strcasecmp(name, "DTSTART") || !g_strcasecmp(name, "DTEND"))) fixutc = TRUE; if ((opts & (VOPTION_CALENDAR2TO1)) && (!g_strcasecmp(name, "DTSTART") || !g_strcasecmp(name, "DTEND"))) dt2to1 = TRUE; if (!g_strcasecmp(name, "DTEND")) dtend = TRUE; if ((opts & (VOPTION_CALENDAR1TO2)) && (!g_strcasecmp(name, "DTSTART") || !g_strcasecmp(name, "DTEND"))) dt1to2 = TRUE; if (!g_strcasecmp(name, "N")) { char *fn = sync_get_key_data(card, "FN"); if (!fn) { addfn = TRUE; } else g_free(fn); } if (!g_strcasecmp(name, "ADR")) { char *label = sync_get_key_data(card, "LABEL"); adr = TRUE; if (!label) { addlabel = TRUE; } else g_free(label); } if ((opts & VOPTION_FIXTELOTHER) && !g_strcasecmp(name, "TEL")) nocategorytel = TRUE; if (!g_strcasecmp(name, "BEGIN") && !g_strcasecmp(value, "VALARM")) { if (opts & VOPTION_CALENDAR2TO1 || (opts & VOPTION_REMOVEALARM)) { alarmmode = TRUE; alarmrepeat = 0; alarmtime = 0; alarmduration = 0; } } if ((opts & VOPTION_REMOVEPHOTO) && !g_strcasecmp(name, "PHOTO")) output = FALSE; if ((opts & VOPTION_CALENDAR2TO1) && !g_strcasecmp(name, "RRULE")) ver2rrule = TRUE; if ((opts & VOPTION_CALENDAR1TO2) && !g_strcasecmp(name, "RRULE")) ver1rrule = TRUE; if (alarmmode && !g_strcasecmp(name, "TRIGGER")) trigger = TRUE; if (alarmmode) output = FALSE; if (alarmmode && !g_strcasecmp(name, "END") && !g_strcasecmp(value, "VALARM")) { // The end of a vCAL 2.0 alarm clause char *dtalarm = sync_timet_to_dt(alarmtime); char *dur = NULL; alarmmode = FALSE; if (!(opts & VOPTION_REMOVEALARM)) { if (alarmduration != 0) dur = sync_timet_to_dur(alarmduration); if (alarmaction == ALARM_ACTION_AUDIO || alarmaction == ALARM_ACTION_DISPLAY) { // Add both AALARM and DALARM so that most devices will be happy. g_string_sprintfa(outcard, "AALARM:%s;%s;%d;\r\n", dtalarm, dur?dur:"", alarmrepeat); g_string_sprintfa(outcard, "DALARM:%s;%s;%d;%s\r\n", dtalarm, dur?dur:"", alarmrepeat, alarmdescription?alarmdescription:""); } if (dur) g_free(dur); } if (dtalarm) g_free(dtalarm); if (alarmdescription) g_free(alarmdescription); alarmdescription = NULL; } if ((opts & VOPTION_CALENDAR1TO2) && !g_strcasecmp(name, "VERSION") && !g_strcasecmp(value, "1.0")) { output = FALSE; g_string_append(outcard, "VERSION:2.0\r\n"); } if ((opts & VOPTION_CALENDAR2TO1) && !g_strcasecmp(name, "VERSION") && !g_strcasecmp(value, "2.0")) { output = FALSE; g_string_append(outcard, "VERSION:1.0\r\n"); } if ((!g_strcasecmp(name, "AALARM") || !g_strcasecmp(name, "DALARM"))) { // Write vCAL 1.0 AALARM or DALARM as 2.0 VALARM clause char dtalarm[256], dur[256], descr[256]; int repeat = 0; char *trigdur, *dtstart, *summary; if (opts & VOPTION_CALENDAR1TO2 || (opts & VOPTION_REMOVEALARM)) output = FALSE; if ((opts & VOPTION_CALENDAR1TO2) && !(opts & VOPTION_REMOVEALARM)) { dur[0] = 0; dtalarm[0] = 0; descr[0] = 0; sscanf(value, "%255[^;];%255[^;];%d;%255s", dtalarm, dur, &repeat, descr); g_string_append(outcard, "BEGIN:VALARM\r\n"); dtstart = sync_get_key_data(card, "DTSTART"); alarmtime = sync_dt_to_timet(dtalarm)-sync_dt_to_timet(dtstart); if (dtstart) g_free(dtstart); trigdur = sync_timet_to_dur(alarmtime); g_string_sprintfa(outcard, "TRIGGER;VALUE=DURATION;RELATED=START:%s\r\n", trigdur?trigdur:""); if (trigdur) g_free(trigdur); g_string_append(outcard, "ACTION:DISPLAY\r\n"); if (repeat > 0) g_string_sprintfa(outcard, "REPEAT:%d\r\n", repeat); if (strlen(dur) > 0) g_string_sprintfa(outcard, "DURATION:%s\r\n", dur); if (!g_strcasecmp(name, "DALARM") && strlen(descr) > 0) { g_string_sprintfa(outcard, "DESCRIPTION:%s\r\n", descr); } else { summary = sync_get_key_data(card, "SUMMARY"); if (summary) { g_string_sprintfa(outcard, "DESCRIPTION:%s\r\n", summary); g_free(summary); } } g_string_append(outcard, "END:VALARM\r\n"); } } if (output) g_string_append(outcard, name); line = endent; if (line) line = line+1; while (line) { char propname[256]; char propdata[256]; gboolean outputprop = TRUE; int ret = 0; endent = strstr(line, ";"); if (endent) endent[0] = 0; ret = sscanf(line, "%255[^=]=%255s", propname, propdata); if (ret > 0) { if (nocategorytel) nocategorytel = FALSE; if (trigger && !g_strcasecmp(propname, "VALUE") && !g_strcasecmp(propdata, "DATE-TIME")) triggervaluedatetime = TRUE; if (trigger && !g_strcasecmp(propname, "RELATED") && !g_strcasecmp(propdata, "END")) triggerrelatedend = TRUE; if (!g_strcasecmp(propname, "CHARSET")) strncpy(linecharset, propdata, 255); if (adr && (!g_strcasecmp(propname, "HOME") || !g_strcasecmp(propname, "WORK"))) { strncpy(adrtype, propname, 255); } if ((!g_strcasecmp(propname, "ENCODING") && !g_strcasecmp(propdata, "QUOTED-PRINTABLE")) || !g_strcasecmp(propname, "QUOTED-PRINTABLE")) { // Convert qp = TRUE; outputprop = FALSE; } if (((opts & VOPTION_CALENDAR2TO1) && !g_strcasecmp(propname, "TZID")) || (((opts & VOPTION_CALENDAR1TO2) || (datatype == VTYPE_VCALENDAR20)) && !g_strcasecmp(propname, "CHARSET"))) { // Discard unknown properties outputprop = FALSE; } if (dt2to1 && !g_strcasecmp(propname, "VALUE") && !g_strcasecmp(propdata, "DATE")) { outputprop = FALSE; alldayevent = TRUE; } if (outputprop) { if (output) { g_string_append(outcard, ";"); g_string_append(outcard, propname); if (ret >= 2) { g_string_append(outcard, "="); g_string_append(outcard, propdata); } } } } line = endent; if (line) line = line+1; } if (qp) { // Temporarily decode QP char *tmp; tmp = value; value = sync_vtype_decode_qp(value); g_free(tmp); } if ((opts & VOPTION_FIXCHARSET) && value && charset) { iconv_t ic; int t; gboolean highchar = FALSE; for (t = 0; t < strlen(value); t++) if (value[t] > 127) highchar = TRUE; if (highchar) { ic = iconv_open("UTF-8", charset); if (ic >= 0) { char *utfvalue = g_malloc0(65536); size_t inbytes = strlen(value); size_t outbytes = 65536; char *inbuf = value, *outbuf = utfvalue; iconv(ic, &inbuf, &inbytes, &outbuf, &outbytes); g_free(value); value = utfvalue; iconv_close(ic); } } } if ((opts & VOPTION_ADDUTF8CHARSET) && (datatype != VTYPE_VCALENDAR20 || (opts & VOPTION_CALENDAR2TO1)) && value && output && strlen(linecharset) == 0) { int t; gboolean highchar = FALSE; for (t = 0; t < strlen(value); t++) if (value[t] > 127) highchar = TRUE; if (highchar) g_string_append(outcard, ";CHARSET=UTF-8"); } if (value && alldayevent) { int y, m, d, h, min, s, res; char utc = ' '; res = sscanf(value, "%4d%2d%2dT%2d%2d%2d%c", &y, &m, &d, &h, &min, &s, &utc); if (res >= 3) { if ((opts & VOPTION_CONVERTALLDAYEVENT)) { // Date only, convert to 00:00 if (!dtend) { // DTSTART g_free(value); value = g_strdup_printf("%04d%02d%02dT000000Z", y, m, d); } else { // DTEND time_t t = sync_dt_to_timet(value); char *tpos; t -= 24*3600; // Move one day backwards g_free(value); value = sync_timet_to_dt(t); tpos = strstr(value, "T"); if (tpos) { char *tmp; tpos[0] = 0; tmp = g_strdup_printf("%sT240000Z", value); g_free(value); value = tmp; } } } else { g_free(value); value = g_strdup_printf("%04d%02d%02dT000000", y, m, d); } } } if (value && dt1to2) { int y, m, d, h, min, s, res; char utc = ' '; res = sscanf(value, "%4d%2d%2dT%2d%2d%2d%c", &y, &m, &d, &h, &min, &s, &utc); if (res == 7 && (h == 0|| h == 24) && min == 0 && s == 0) { // All day event? g_free(value); g_string_append(outcard, ";VALUE=DATE"); value = g_strdup_printf("%04d%02d%02d", y, m, d); if (h == 24) { time_t t = sync_dt_to_timet(value); char *tpos; t += 24*3600; // Move one day forwards g_free(value); value = sync_timet_to_dt(t); tpos = strstr(value, "T"); if (tpos) tpos[0] = 0; // End string after date } } } if (fixdst && value && strlen(value) > 0 && value[strlen(value)-1] == 'Z') { // Fix broken DST handling on Ericsson phones by modifying // times of an event by the DST at present. time_t now, dt; struct tm *nowinfo, *dtinfo; int nowdst, dtdst; time(&now); nowinfo = localtime(&now); nowdst = nowinfo->tm_isdst; dt = sync_dt_to_timet(value); dtinfo = localtime(&dt); dtdst = dtinfo->tm_isdst; if (nowdst && !dtdst) { g_free(value); value = sync_timet_to_dt((opts&VOPTION_FIXDSTFROMCLIENT)?dt+3600:dt-3600); } if (!nowdst && dtdst) { g_free(value); value = sync_timet_to_dt((opts&VOPTION_FIXDSTFROMCLIENT)?dt-3600:dt+3600); } } if (fixlocaltime && value && strlen(value) > 0 && value[strlen(value)-1] == 'Z') { time_t dt; dt = sync_dt_to_timet(value); g_free(value); value = sync_timet_to_dt(dt); } if (fixutc && value && strlen(value) > 0 && value[strlen(value)-1] == 'Z') { // Interpret UTC as localtime instead value[strlen(value)-1] = 0; } if (nocategorytel) { // Add VOICE as category if the phone number has no category g_string_append(outcard, ";"); g_string_append(outcard, "VOICE"); } if (ver1rrule && value) { // Convert a vCAL 1.0 RRULE to 2.0 char* rrule = sync_vtype_vcal1_to_vcal2(value); g_free(value); value = rrule; } if (ver2rrule && value) { // Convert a vCAL 2.0 RRULE to 1.0 char* rrule = sync_vtype_vcal2_to_vcal1(value); g_free(value); value = rrule; } if (trigger) { // A trigger in an alarm clause time_t dur = sync_dur_to_timet(value); char *dtstart = sync_get_key_data(card, "DTSTART"); alarmtime = sync_dt_to_timet(dtstart)+dur; if (dtstart) g_free(dtstart); } if (alarmmode && !g_strcasecmp(name, "ACTION")) { if (!g_strcasecmp(value, "AUDIO")) alarmaction = ALARM_ACTION_AUDIO; if (!g_strcasecmp(value, "DISPLAY")) alarmaction = ALARM_ACTION_DISPLAY; if (!g_strcasecmp(value, "EMAIL")) alarmaction = ALARM_ACTION_EMAIL; if (!g_strcasecmp(value, "PROCEDURE")) alarmaction = ALARM_ACTION_PROCEDURE; } if (alarmmode && !g_strcasecmp(name, "REPEAT")) sscanf(value, "%d", &alarmrepeat); if (alarmmode && !g_strcasecmp(name, "DURATION")) alarmduration = sync_dur_to_timet(value); if (alarmmode && !g_strcasecmp(name, "DESCRIPTION")) alarmdescription = g_strdup(value); if (value && (opts & VOPTION_CALENDAR1TO2)) { // Convert "," to "\," char **tmp = g_strsplit(value, ",", 255); g_free(value); value = g_strjoinv("\\,", tmp); g_strfreev(tmp); } if (value && (opts & VOPTION_CALENDAR2TO1)) { // Convert "\," to "," char **tmp = g_strsplit(value, "\\,", 255); g_free(value); value = g_strjoinv(",", tmp); g_strfreev(tmp); } if (adr && strlen(adrtype) == 0) { // If no address type specified, add WORK (mostly for Evolution) g_string_append(outcard, ";WORK"); } if (output) { // Write the value if (qp && value && strstr(value, "\r") || strstr(value,"\n")) { // Re-encode if necessary char *tmp = sync_vtype_encode_qp(value); g_string_append(outcard, ";ENCODING=QUOTED-PRINTABLE:"); g_string_append(outcard, tmp); g_free(tmp); } else { g_string_append(outcard, ":"); g_string_append(outcard, value); } g_string_append(outcard, "\r\n"); } if (addfn && value) { char last[256]="", first[256]=""; if (sscanf(value, "%255[^;];%255[^;]", last, first) > 0 || sscanf(value, ";%255[^;]", first) > 0) { g_string_append(outcard, "FN:"); if (strlen(first) > 0) g_string_append(outcard, first); if (strlen(first) > 0 && strlen(last) > 0) g_string_append(outcard, " "); if (strlen(last) > 0) g_string_append(outcard, last); g_string_append(outcard, "\r\n"); } } if (addlabel && value) { char *label, *tmp, *pos = value, *oldpos = value; int t; gchar** data = g_strsplit(value, ";", 256); if (data[0] && data[1] && data[2] && data[3] && data[4] && data[5] && data[6]) { tmp = g_strdup_printf("%s%s%s\n%s\n%s, %s%s%s\n%s", data[2], data[2][0]?" ":"", data[0], data[1], data[3], data[4], data[4][0]?" ":"", data[5], data[6]); label = sync_vtype_encode_qp(tmp); g_free(tmp); g_string_append(outcard, "LABEL;"); if (strlen(adrtype) > 0) { g_string_append(outcard, adrtype); } else g_string_append(outcard, "WORK"); g_string_sprintfa(outcard, ";ENCODING=QUOTED-PRINTABLE:%s\r\n", label); g_free(label); } g_strfreev(data); } g_free(value); } incard = endln; if (incard) incard += 1; } ret = outcard->str; g_string_free(outcard, FALSE); g_free(incard); if (alarmdescription) g_free(alarmdescription); return(ret); } // Decode QUOTED-PRINTABLE char* sync_vtype_decode_qp(char *in) { char *pos = in, *oldpos = in, *out; GString *newvalue = g_string_new(""); if (!in) return NULL; while((pos=strstr(pos, "="))) { int c; pos[0] = 0; g_string_append(newvalue, oldpos); if (pos[1] == '\r' || pos[1] == '\n') { if (pos[1] == '\r') pos++; pos+=2; } else { if (sscanf(pos+1, "%2x", &c)) { char s[2] = " "; s[0] = c; g_string_append(newvalue, s); pos+=3; } } oldpos = pos; } g_string_append(newvalue, oldpos); out = newvalue->str; g_string_free(newvalue, FALSE); return(out); } char *sync_vtype_encode_qp(char* in) { char *out; GString *newvalue = g_string_new(""); int t = 0; int count = 0; if (!in) return NULL; while (in[t]) { char hex[] = "0123456789ABCDEF"; if (in[t] > 'z' || in[t] < ' ' || in[t] == '=') { g_string_sprintfa(newvalue, "=%c%c", hex[(((unsigned char) in[t])>>4)&0xf], hex[in[t]&0xf]); count+=3; } else { g_string_sprintfa(newvalue, "%c", in[t]); count++; } if (count >= 76) { g_string_sprintfa(newvalue, "=\r\n"); count=0; } t++; } out = newvalue->str; g_string_free(newvalue, FALSE); return(out); } // Convert vCAL 1.0 RRULEto vCAL 2.0 char* sync_vtype_vcal1_to_vcal2(char* in) { gchar** bits = g_strsplit(in, " ", 256); int i; int last_bit; char frequency[256]; char* freqstring = NULL; int interval; gchar** outbits = g_malloc0(sizeof(gchar*) * 256); int outbit = 0; int count = 0; char* byday=NULL; char* bymonthday=NULL; char* byyearday=NULL; char* bymonth=NULL; gchar* out = NULL; time_t untiltime = 0; for(i=0; bits[i]; i++); last_bit = i - 1; /* get frequency and interval */ if (sscanf(bits[0], "%[A-Z]%d", frequency, &interval) < 2) { out = g_strdup(in); goto err; } /* get count */ if (sscanf(bits[last_bit], "#%d", &count) < 1) { // Not a count, assume "until" value untiltime = sync_dt_to_timet(bits[last_bit]); } if(last_bit > 1) { gchar* middlebits[last_bit]; gchar* foo; for(i=1; i < last_bit; i++) { middlebits[i-1] = bits[i]; } middlebits[last_bit-1] = NULL; foo = sync_vtype_vcal1_list_to_vcal2(middlebits); if (!strcmp(frequency, "MD")) bymonthday = foo; else if (!strcmp(frequency, "MP")) byday = foo; else if (!strcmp(frequency, "YD")) byyearday = foo; else if (!strcmp(frequency, "YM")) bymonth = foo; else byday = foo; } /* format frequency */ if(!strcmp(frequency, "D")) { freqstring = "DAILY"; } else if(!strcmp(frequency, "W")) { freqstring = "WEEKLY"; } else if(!strcmp(frequency, "MD") || !strcmp(frequency, "MP")) { freqstring = "MONTHLY"; } else if(!strcmp(frequency, "YD") || !strcmp(frequency, "YM")) { freqstring = "YEARLY"; } if (!freqstring) { // Not parsable g_strfreev(outbits); g_strfreev(bits); return(g_strdup(in)); } outbits[outbit] = g_strdup_printf("FREQ=%s", freqstring); outbit++; /* interval */ outbits[outbit] = g_strdup_printf("INTERVAL=%d", interval); outbit++; /* count */ if(count > 0) { outbits[outbit] = g_strdup_printf("COUNT=%d", count); outbit++; } else if(untiltime > 0) { /* until */ char *dt = sync_timet_to_dt_utc(untiltime); outbits[outbit] = g_strdup_printf("UNTIL=%s", dt); g_free(dt); outbit++; } if(byday) { outbits[outbit] = g_strdup_printf("BYDAY=%s", byday); g_free(byday); outbit++; } if(byyearday) { outbits[outbit] = g_strdup_printf("BYYEARDAY=%s", byyearday); g_free(byyearday); outbit++; } if(bymonth) { outbits[outbit] = g_strdup_printf("BYMONTH=%s", bymonth); g_free(bymonth); outbit++; } if(bymonthday) { outbits[outbit] = g_strdup_printf("BYMONTHDAY=%s", bymonthday); g_free(bymonthday); outbit++; } outbits[outbit] = NULL; out = g_strjoinv(";", outbits); err: g_strfreev(outbits); g_strfreev(bits); return out; } // Convert vCAL 2.0 RRULEto vCAL 1.0 char* sync_vtype_vcal2_to_vcal1(char* value) { char freq[256], *pos; if (sscanf(value, "FREQ=%255[^;]", freq) >= 1) { time_t untiltime = 0; int count = 0, interval = 1; char bywdaylist[256] = ""; char bymodaylist[256] = ""; char byyrdaylist[256] = ""; char bymolist[256] = ""; gboolean converted = FALSE; GString *newvalue = g_string_new(""); pos = strstr(value, ";"); if (pos) pos++; while(pos) { char name[256], data[256]; if (sscanf(pos, "%255[^=]=%255[^;]", name, data) == 2) { // Not complete, only what can be converted to 1.0 if (!g_strcasecmp(name, "UNTIL")) untiltime = sync_dt_to_timet(data); if (!g_strcasecmp(name, "COUNT")) sscanf(data, "%d", &count); if (!g_strcasecmp(name, "INTERVAL")) sscanf(data, "%d", &interval); if (!g_strcasecmp(name, "BYDAY")) strncpy(bywdaylist, data, 256); if (!g_strcasecmp(name, "BYMONTHDAY")) strncpy(bymodaylist, data, 256); if (!g_strcasecmp(name, "BYYEARDAY")) strncpy(byyrdaylist, data, 256); if (!g_strcasecmp(name, "BYMONTH")) strncpy(bymolist, data, 256); } pos = strstr(pos, ";"); if (pos) pos++; } if (!g_strcasecmp(freq, "DAILY")) { g_string_sprintfa(newvalue, "D%d", interval); converted = TRUE; } if (strlen(bywdaylist)) { // Convert bywdaylist to 1.0 } if (strlen(bywdaylist)) { char *tmp = sync_vtype_vcal2_list_to_vcal1(bywdaylist); strncpy(bywdaylist, tmp, 255); g_free(tmp); } if (strlen(bymodaylist)) { char *tmp = sync_vtype_vcal2_list_to_vcal1(bymodaylist); strncpy(bymodaylist, tmp, 255); g_free(tmp); } if (strlen(byyrdaylist)) { char *tmp = sync_vtype_vcal2_list_to_vcal1(byyrdaylist); strncpy(byyrdaylist, tmp, 255); g_free(tmp); } if (strlen(bymolist)) { char *tmp = sync_vtype_vcal2_list_to_vcal1(bymolist); strncpy(bymolist, tmp, 255); g_free(tmp); } if (!g_strcasecmp(freq, "WEEKLY")) { g_string_sprintfa(newvalue, "W%d", interval); if (strlen(bywdaylist)) { g_string_append(newvalue, " "); g_string_append(newvalue, bywdaylist); } converted = TRUE; } if (!g_strcasecmp(freq, "MONTHLY")) { if (strlen(bywdaylist)) { g_string_sprintfa(newvalue, "MP%d ", interval); g_string_append(newvalue, bywdaylist); converted = TRUE; } else if (strlen(bymodaylist)) { g_string_sprintfa(newvalue, "MD%d ", interval); g_string_append(newvalue, bymodaylist); converted = TRUE; } } if (!g_strcasecmp(freq, "YEARLY")) { if (strlen(bymolist)) { g_string_sprintfa(newvalue, "YM%d ", interval); g_string_append(newvalue, bymolist); converted = TRUE; } else if (strlen(byyrdaylist)) { g_string_sprintfa(newvalue, "YD%d ", interval); g_string_append(newvalue, byyrdaylist); converted = TRUE; } } if (untiltime > 0) { // Add until or count char *dt = sync_timet_to_dt_utc(untiltime); g_string_sprintfa(newvalue, " %s", dt); g_free(dt); } else { g_string_sprintfa(newvalue, " #%d", count); } if (converted) { // Conversion successful value = newvalue->str; g_string_free(newvalue, FALSE); } else { value = g_strdup(value); g_string_free(newvalue, TRUE); } } else { value = g_strdup(value); } return(value); } char* sync_vtype_vcal1_list_to_vcal2(char **bits) { GString *buf = g_string_new(""); char *value; int i; for (i = 0; bits[i]; i++) { int pos; char sign; if (i > 0) g_string_append(buf, ","); if (sscanf(bits[i], "%d%c" , &pos, &sign) == 2) { if (sign == '-') pos = -pos; g_string_sprintfa(buf, "%d", pos); if (bits[i+1] && !sscanf(bits[i+1], "%d", &pos)) { // A day follows g_string_sprintfa(buf, " %s", bits[i+1]); i++; } } else { g_string_append(buf, bits[i]); } } value = buf->str; g_string_free(buf, FALSE); return(value); } char* sync_vtype_vcal2_list_to_vcal1(char *in) { GString *buf = g_string_new(""); gchar** bits = g_strsplit(in, ",", 256); char *value; int i; for (i = 0; bits[i]; i++) { int pos = 0; char day[16] = ""; int ret = 0; if (i > 0) g_string_append(buf, " "); if ((ret = sscanf(bits[i], "%d %15s", &pos, day)) > 0) { if (pos > 0) g_string_sprintfa(buf, "%d+", pos); else g_string_sprintfa(buf, "%d-", -pos); if (ret >= 2) g_string_sprintfa(buf, " %s", day); } else { g_string_append(buf, bits[i]); } } g_strfreev(bits); value = buf->str; g_string_free(buf, FALSE); return(value); } char* sync_timet_to_dt(time_t t){ struct tm *data; data = localtime(&t); return (g_strdup_printf("%04d%02d%02dT%02d%02d%02d", data->tm_year+1900, data->tm_mon+1, data->tm_mday, data->tm_hour, data->tm_min, data->tm_sec)); } char* sync_timet_to_dt_utc(time_t t){ struct tm *data; data = gmtime(&t); return (g_strdup_printf("%04d%02d%02dT%02d%02d%02dZ", data->tm_year+1900, data->tm_mon+1, data->tm_mday, data->tm_hour, data->tm_min, data->tm_sec)); } time_t sync_dt_to_timet(char *str) { struct tm data; time_t dt; char z = ' '; tzset(); data.tm_hour = 0; data.tm_min = 0; data.tm_sec = 0; if (!str) return 0; if (sscanf(str, "%4d%2d%2dT%2d%2d%2d%c", &data.tm_year, &data.tm_mon, &data.tm_mday, &data.tm_hour, &data.tm_min, &data.tm_sec, &z) >= 3) { data.tm_year -= 1900; data.tm_mon -= 1; data.tm_wday = 0; data.tm_yday = 0; // Removed by Bo Lincoln 2004-03-31. Bug or something that changed in // recent glibc? //if (z == 'Z') // data.tm_isdst = 0; //else data.tm_isdst = -1; dt = mktime(&data); if (z == 'Z') { struct tm *local; local = localtime(&dt); dt += local->tm_gmtoff; } } return(dt); } // Convert RFC2445 duration to seconds time_t sync_dur_to_timet(char *str) { char *pos = str; char *end = NULL; char typestr[] = "D"; char type = 0; int amt = 0; int sign = 1, dur = 0; if (!str) return(0); end = str+strlen(str); if (pos < end && pos[0] == '-') { sign = -1; pos++; } else if (pos < end && pos[0] == '+') { pos++; } if (pos < end && pos[0] == 'P') { pos++; } else return(sign*dur); if (pos < end && sscanf(pos, "%d%c", &amt, &type) == 2) { if (type == 'W') dur += 3600*24*7*amt; if (type == 'D') dur += 3600*24*amt; typestr[0] = type; pos = strstr(pos, typestr)+1; } if (pos < end && pos[0] == 'T') { pos++; } else return(sign*dur); while (pos < end && sscanf(pos, "%d%c", &amt, &type) == 2) { if (type == 'H') dur += 3600*amt; if (type == 'M') dur += 60*amt; if (type == 'S') dur += amt; typestr[0] = type; pos = strstr(pos, typestr)+1; } return(sign*dur); } // Convert seconds to RFC2445 DURATION char* sync_timet_to_dur(time_t dur) { char *value; GString *ret = g_string_new(""); if (dur < 0) { g_string_append(ret, "-"); dur = -dur; } g_string_append(ret, "P"); if (dur >= 3600*24) { int d = dur/(3600*24); dur -= d*3600*24; g_string_sprintfa(ret, "%dD", d); } if (dur > 0) { g_string_append(ret, "T"); if (dur >= 3600) { int h = dur/3600; dur -= h*3600; g_string_sprintfa(ret, "%dH", h); } if (dur >= 60) { int m = dur/60; dur -= m*60; g_string_sprintfa(ret, "%dM", m); } if (dur > 0) { g_string_sprintfa(ret, "%dS", dur); dur = 0; } } value = ret->str; g_string_free(ret, FALSE); return(value); } //Return a line of data from a vCARD, vCALENDAR etc. Free the string // using g_free(). char* sync_get_key_data(char *card, char *key) { char *pos = card; int l = strlen(key); char *res = g_strdup(""); while (pos) { if (!strncmp(pos, key, l) && (pos[l] == ':' || pos[l] == ';')) { char *start, *end; start = strstr(pos+l, ":"); if (start) { char *tmp, *line, *newstart; do { start++; end = strstr(start, "\n"); if (!end) end = card+strlen(card); newstart = end+1; if (*(end-1) == '\r') end--; line = g_strndup(start, end-start); tmp = g_strdup_printf("%s%s", res, line); g_free(res); g_free(line); res = tmp; start = newstart; } while(start < card+strlen(card) && start[0] == ' '); return(res); } } pos = strstr(pos, "\n"); if (pos) pos += 1; } g_free(res); return(NULL); } gboolean sync_compare_key_data(char *obj1, char *obj2, char *key) { char *d1 = sync_get_key_data(obj1, key); char *d2 = sync_get_key_data(obj2, key); gboolean equal = FALSE; if (d1 && d2 && !g_strcasecmp(d1,d2)) equal = TRUE; if (!d1 && !d2) equal = TRUE; g_free(d1); g_free(d2); return(equal); } gboolean sync_compare_key_times(char *obj1, char *obj2, char *key) { char *d1 = sync_get_key_data(obj1, key); char *d2 = sync_get_key_data(obj2, key); gboolean equal = FALSE; if (d1 && d2 && sync_dt_to_timet(d1) == sync_dt_to_timet(d2)) equal = TRUE; if (!d1 && !d2) equal = TRUE; g_free(d1); g_free(d2); return(equal); } // Compare two objects. sync_compare_result sync_compare_objects(char *obj1, char *obj2, sync_object_type objtype) { char *data; sync_compare_result equal = SYNC_COMPARE_EQUAL; switch(objtype) { case SYNC_OBJECT_TYPE_CALENDAR: if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_data(obj1, obj2, "SUMMARY")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_data(obj1, obj2, "DESCRIPTION")) equal = SYNC_COMPARE_SIMILAR; if (!sync_compare_key_times(obj1, obj2, "DTSTART")) equal = SYNC_COMPARE_DIFFERENT; if (!sync_compare_key_times(obj1, obj2, "DTEND")) equal = SYNC_COMPARE_DIFFERENT; break; case SYNC_OBJECT_TYPE_TODO: if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_times(obj1, obj2, "DTSTART")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_data(obj1, obj2, "DESCRIPTION")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_times(obj1, obj2, "COMPLETED")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_times(obj1, obj2, "DTEND")) equal = SYNC_COMPARE_SIMILAR; if (!sync_compare_key_data(obj1, obj2, "SUMMARY")) equal = SYNC_COMPARE_DIFFERENT; break; case SYNC_OBJECT_TYPE_PHONEBOOK: if (!sync_compare_key_data(obj1, obj2, "TEL;HOME")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_data(obj1, obj2, "TEL;CELL")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_data(obj1, obj2, "TEL;PREF")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_data(obj1, obj2, "TEL;VOICE")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_data(obj1, obj2, "TEL;WORK")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_data(obj1, obj2, "TEL;FAX")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_data(obj1, obj2, "EMAIL")) equal = SYNC_COMPARE_SIMILAR; if (equal == SYNC_COMPARE_EQUAL && !sync_compare_key_data(obj1, obj2, "FN")) equal = SYNC_COMPARE_SIMILAR; if (!sync_compare_key_data(obj1, obj2, "N")) equal = SYNC_COMPARE_DIFFERENT; break; default: equal = SYNC_COMPARE_DIFFERENT; } return(equal); } void sync_append_data_line(GString *out, char* card, char *key, char *title) { char *data = sync_get_key_data(card, key); if (data) { g_string_sprintfa(out, "%s: %s\n", title, data); g_free(data); } } // Returns gmalloc'ed string with most important info from object char* sync_object_string(changed_object *obj) { GString *out = NULL; char *ret = NULL, *data; if (obj->change_type == SYNC_OBJ_SOFTDELETED || obj->change_type == SYNC_OBJ_HARDDELETED) return(g_strdup("Object deleted.")); if (!obj->comp) return(g_strdup("No data.")); out = g_string_new(""); switch(obj->object_type) { case SYNC_OBJECT_TYPE_CALENDAR: sync_append_data_line(out, obj->comp, "SUMMARY", "Summary"); data = sync_get_key_data(obj->comp, "DTSTART"); if (data) { char timestr[256]; time_t t = sync_dt_to_timet(data); g_free(data); g_string_sprintfa(out, "Starts: %s", ctime_r(&t, timestr)); } data = sync_get_key_data(obj->comp, "DTEND"); if (data) { char timestr[256]; time_t t = sync_dt_to_timet(data); g_free(data); g_string_sprintfa(out, "Ends: %s", ctime_r(&t, timestr)); } sync_append_data_line(out, obj->comp, "DESCRIPTION", "Description"); break; case SYNC_OBJECT_TYPE_TODO: sync_append_data_line(out, obj->comp, "SUMMARY", "Summary"); sync_append_data_line(out, obj->comp, "COMPLETED", "Completed"); sync_append_data_line(out, obj->comp, "DESCRIPTION", "Description"); break; case SYNC_OBJECT_TYPE_PHONEBOOK: data = sync_get_key_data(obj->comp, "FN"); if (!data) data = sync_get_key_data(obj->comp, "N"); if (data) { g_string_sprintfa(out, "Name: %s\n", data); g_free(data); } sync_append_data_line(out, obj->comp, "TEL;HOME", "Home phone"); sync_append_data_line(out, obj->comp, "TEL;CELL", "Mobile phone"); sync_append_data_line(out, obj->comp, "TEL;PREF", "Preferred phone"); sync_append_data_line(out, obj->comp, "TEL;FAX", "Fax"); sync_append_data_line(out, obj->comp, "TEL;WORK", "Work phone"); sync_append_data_line(out, obj->comp, "TEL;VOICE", "Voice phone"); sync_append_data_line(out, obj->comp, "EMAIL", "E-mail"); break; default: g_string_append(out, "Cannot disply entry."); } ret = out->str; g_string_free(out, FALSE); return(ret); }