From: David Woodhouse Date: Mon, 19 Jul 2010 23:36:20 +0000 (+0100) Subject: Get C syncfolder almost working. Lots of cleanups needed. X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=30830927117059d64c54352a06c042c38940ed0a;p=users%2Fdwmw2%2Fews-sync.git Get C syncfolder almost working. Lots of cleanups needed. Surely the XML crap can be nicer than this? --- diff --git a/Makefile b/Makefile index c61fe84..3b384d3 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ CFLAGS := $(OPT_FLAGS) $(XML2_CFLAGS) $(GLIB_CFLAGS) CFLAGS_ews_syncfolder.o := $(SOUP_CFLAGS) CFLAGS_ews_autodiscover.o := $(SOUP_CFLAGS) CFLAGS_ews2ical.o := $(ICAL_CFLAGS) +CFLAGS_calitem_to_ical.o := $(ICAL_CFLAGS) %.o: %.c $(CC) -c -o $@ $(CFLAGS) $(CFLAGS_$@) $< -MD -MF .$@.dep @@ -53,11 +54,11 @@ all: $(PROGS) clean: rm -f $(PROGS) *.o .*.o.dep -ews2ical: ews2ical.o - $(CC) -o $@ $< $(LDFLAGS) $(XML2_LDFLAGS) $(ICAL_LDFLAGS) $(GLIB_LDFLAGS) +ews2ical: ews2ical.o calitem_to_ical.o + $(CC) -o $@ $^ $(LDFLAGS) $(XML2_LDFLAGS) $(ICAL_LDFLAGS) $(GLIB_LDFLAGS) ews_autodiscover: ews_autodiscover.o - $(CC) -o $@ $< $(LDFLAGS) $(XML2_LDFLAGS) $(SOUP_LDFLAGS) $(GLIB_LDFLAGS) + $(CC) -o $@ $^ $(LDFLAGS) $(XML2_LDFLAGS) $(SOUP_LDFLAGS) $(GLIB_LDFLAGS) -ews_syncfolder: ews_syncfolder.o - $(CC) -o $@ $< $(LDFLAGS) $(XML2_LDFLAGS) $(SOUP_LDFLAGS) $(GLIB_LDFLAGS) +ews_syncfolder: ews_syncfolder.o calitem_to_ical.o + $(CC) -o $@ $^ $(LDFLAGS) $(XML2_LDFLAGS) $(SOUP_LDFLAGS) $(GLIB_LDFLAGS) $(ICAL_LDFLAGS) diff --git a/calitem_to_ical.c b/calitem_to_ical.c new file mode 100644 index 0000000..3293190 --- /dev/null +++ b/calitem_to_ical.c @@ -0,0 +1,1147 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +FILE *calfile; + +int process_relativeyearlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); +int process_absoluteyearlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); +int process_relativemonthlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); +int process_absolutemonthlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); +int process_weeklyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); +int process_dailyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); + +int process_organizer(icalcomponent *comp, xmlNode *xml_node); +int process_required_attendees(icalcomponent *comp, xmlNode *xml_node); +int process_optional_attendees(icalcomponent *comp, xmlNode *xml_node); +int process_time(icalcomponent *comp, xmlNode *xml_node, icaltimetype *ical_time); +int process_truefalse(icalcomponent *comp, xmlNode *xml_node, gboolean *val); +int process_location(icalcomponent *comp, xmlNode *xml_node); +int process_body(icalcomponent *comp, xmlNode *xml_node); +int process_subject(icalcomponent *comp, xmlNode *xml_node); +int process_recurrence(icalcomponent *comp, xmlNode *xml_node, icaltimezone *zone); +int process_itemid(icalcomponent *comp, xmlNode *xmlnode); +int process_reminder_mins(icalcomponent *comp, xmlNode *xmlnode); +icaltimezone *get_timezone(xmlNode *xmlnode); +icaltimezone *get_meeting_timezone(xmlNode *xml_node); + +icalcomponent *ews_calitem_to_ical(xmlNode *xml_node); + +icalcomponent *ews_calitem_to_ical(xmlNode *xml_node) +{ + icaltimetype dtstart, dtend; + icaltimezone *icaltz; + icalcomponent *comp, *calcomp; + icalproperty *prop; + gboolean allday = FALSE; + + dtstart = dtend = icaltime_null_time(); + + calcomp = icalcomponent_new_vcalendar(); + icalcomponent_set_method(calcomp, ICAL_METHOD_PUBLISH); + prop = icalproperty_new_version("2.0"); + icalcomponent_add_property(calcomp, prop); + + icaltz = get_meeting_timezone(xml_node); + if (!icaltz) + icaltz = get_timezone(xml_node); + if (icaltz) { + icalcomponent *comp = icaltimezone_get_component(icaltz); + icalcomponent_add_component(calcomp, comp); + } + comp = icalcomponent_new(ICAL_VEVENT_COMPONENT); + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "Organizer")) + process_organizer(comp, xml_node); + else if (!strcmp((char *)xml_node->name, "RequiredAttendees")) + process_required_attendees(comp, xml_node); + else if (!strcmp((char *)xml_node->name, "OptionalAttendees")) + process_optional_attendees(comp, xml_node); + else if (!strcmp((char *)xml_node->name, "Start")) + process_time(comp, xml_node, &dtstart); + else if (!strcmp((char *)xml_node->name, "End")) + process_time(comp, xml_node, &dtend); + else if (!strcmp((char *)xml_node->name, "Body")) + process_body(comp, xml_node); + else if (!strcmp((char *)xml_node->name, "Location")) + process_location(comp, xml_node); + else if (!strcmp((char *)xml_node->name, "Subject")) + process_subject(comp, xml_node); + else if (!strcmp((char *)xml_node->name, "Recurrence")) + process_recurrence(comp, xml_node, icaltz); + else if (!strcmp((char *)xml_node->name, "ItemId")) + process_itemid(comp, xml_node); + else if (!strcmp((char *)xml_node->name, "IsAllDayEvent")) + process_truefalse(comp, xml_node, &allday); + else if (!strcmp((char *)xml_node->name, "ReminderMinutesBeforeStart")) + process_reminder_mins(comp, xml_node); + else if (!strcmp((char *)xml_node->name, "ParentFolderId") || + !strcmp((char *)xml_node->name, "DateTimeReceived") || + !strcmp((char *)xml_node->name, "Size") || + !strcmp((char *)xml_node->name, "IsSubmitted") || + !strcmp((char *)xml_node->name, "IsDraft") || + !strcmp((char *)xml_node->name, "IsFromMe") || + !strcmp((char *)xml_node->name, "IsResend") || + !strcmp((char *)xml_node->name, "IsUnmodified") || + !strcmp((char *)xml_node->name, "DateTimeSent") || + !strcmp((char *)xml_node->name, "DateTimeCreated") || + !strcmp((char *)xml_node->name, "ResponseObjects") || + !strcmp((char *)xml_node->name, "DisplayCc") || + !strcmp((char *)xml_node->name, "DisplayTo") || + !strcmp((char *)xml_node->name, "Culture") || + !strcmp((char *)xml_node->name, "IsRecurring") || + !strcmp((char *)xml_node->name, "MeetingRequestWasSent") || + !strcmp((char *)xml_node->name, "IsResponseRequested") || + !strcmp((char *)xml_node->name, "MyResponseType") || + !strcmp((char *)xml_node->name, "ConflictingMeetingCount") || + !strcmp((char *)xml_node->name, "AdjacentMeetingCount") || + !strcmp((char *)xml_node->name, "TimeZone") || + !strcmp((char *)xml_node->name, "AppointmentSequenceNumber") || + !strcmp((char *)xml_node->name, "AppointmentState")) { + /* Ignore these */ + } +#if 0 + else + fprintf(stderr, "Unhandled node type '%s'\n", xml_node->name); +#endif + } + + /* We don't handle really floating events -- which change their time + according to the time zone of the observer (like lunch at noon + under the sundial wherever you are in the world). But that's OK; + Exchange doesn't seem to either: + - AFAICT, you can't create them with Outlook. + - If you send Exchange an invitation with floating times, which + happens to have a VTIMEZONE, it'll assume that timezone (in + violation of RFC2445). + - If you send Exchange an invitation with floating times with + *no* stray VTIMEZONE in the file (which was a mistake), then + it creates an item with no timezone but does weird things -- + this invite: + DTSTART:20100720T120000 + DTEND:20100720T120010 + ... leads to an Exchange object saying... + 2010-07-20T11:00:00Z + 2010-07-20T11:00:00Z + + For any recurring object without time zones (including the last + test above, as well as all day events such as birthdays created + with Outlook/Exchange 2003, Exchange will refuse to return the + object if the field is requested, reporting + 'Corrupt Data'. + + Tested with Exchange 2007. + */ + if (icaltz && !allday) { + dtstart = icaltime_convert_to_zone(dtstart, icaltz); + dtend = icaltime_convert_to_zone(dtend, icaltz); + } + if (allday) { + dtstart.is_date = 1; + dtend.is_date = 1; + } + if (!icaltime_is_null_time(dtstart)) + icalcomponent_set_dtstart(comp, dtstart); + + if (!icaltime_is_null_time(dtend)) + icalcomponent_set_dtend(comp, dtend); + + icalcomponent_add_component(calcomp, comp); + return calcomp; +} + +int process_mailbox(xmlNode *xml_node, const char **r_name, const char **r_email) +{ + const char *type = NULL, *name = NULL, *email = NULL; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "Name")) + name = (char *)xmlNodeGetContent(xml_node); + if (!strcmp((char *)xml_node->name, "EmailAddress")) + email = (char *)xmlNodeGetContent(xml_node); + if (!strcmp((char *)xml_node->name, "RoutingType")) { + type = (char *)xmlNodeGetContent(xml_node); + } + } + + /* We seem to get EX routing for people who don't exist any more */ + if (type && strcmp(type, "SMTP")) { + if (strcmp(type, "EX")) + fprintf(stderr, "Unknown RoutingType '%s' ('%s' '%s')\n", + type, name, email); + return -1; + } + *r_name = name; + *r_email = email; + return 0; +} + +int process_organizer(icalcomponent *comp, xmlNode *xml_node) +{ + icalproperty *prop; + icalparameter *param; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "Mailbox")) { + const char *name = NULL, *email = NULL; + char *mailtoname; + if (process_mailbox(xml_node, &name, &email)) + return -1; + + mailtoname = g_strdup_printf("mailto:%s", email); + + prop = icalproperty_new_organizer(mailtoname); + free(mailtoname); + param = icalparameter_new_cn(name); + icalproperty_add_parameter(prop, param); + icalcomponent_add_property(comp, prop); + } + } + return 0; +} + +int process_attendee(icalcomponent *comp, xmlNode *xml_node, icalparameter_role role) +{ + icalproperty *prop; + icalparameter *param; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "Mailbox")) { + const char *name = NULL, *email = NULL; + char *mailtoname; + if (process_mailbox(xml_node, &name, &email)) + return -1; + + mailtoname = g_strdup_printf("mailto:%s", email); + + prop = icalproperty_new_attendee(mailtoname); + free(mailtoname); + param = icalparameter_new_cn(name); + icalproperty_add_parameter(prop, param); + param = icalparameter_new_role(role); + icalproperty_add_parameter(prop, param); + icalcomponent_add_property(comp, prop); + } + } + return 0; +} + +int process_required_attendees(icalcomponent *comp, xmlNode *xml_node) +{ + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "Attendee")) { + if (process_attendee(comp, xml_node, + ICAL_ROLE_REQPARTICIPANT)) + { } + } + } + return 0; +} + +int process_optional_attendees(icalcomponent *comp, xmlNode *xml_node) +{ + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "Attendee")) { + if (process_attendee(comp, xml_node, + ICAL_ROLE_OPTPARTICIPANT)) + { } + } + } + return 0; +} + +int process_time(icalcomponent *comp, xmlNode *xml_node, icaltimetype *ical_time) +{ + char *ews_time = (char *)xmlNodeGetContent(xml_node); + + if (!ews_time) + return -1; + *ical_time = icaltime_from_string(ews_time); + return 0; +} + +int process_truefalse(icalcomponent *comp, xmlNode *xml_node, gboolean *val) +{ + char *truth = (char *)xmlNodeGetContent(xml_node); + + if (!truth) + return -1; + if (!strcmp(truth, "true")) + *val = TRUE; + else if (!strcmp(truth, "false")) + *val = FALSE; + else { + fprintf(stderr, "Unrecognised truth value '%s' in %s node\n", + truth, xml_node->name); + return -1; + } + return 0; +} + +int process_location (icalcomponent *comp, xmlNode *xml_node) +{ + const char *loc = (char *)xmlNodeGetContent(xml_node); + + if (!loc) + return -1; + icalcomponent_set_location(comp, loc); + return 0; +} + +int process_body(icalcomponent *comp, xmlNode *xml_node) +{ + const char *body = (char *)xmlNodeGetContent(xml_node); + + if (!body) + return -1; + + if (!strncasecmp(body, "children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "WeeklyRecurrence")) { + if (process_weeklyrecurrence(xml_node, &ical_recur)) + return -1; + } else if (!strcmp((char *)xml_node->name, "DailyRecurrence")) { + if (process_dailyrecurrence(xml_node, &ical_recur)) + return -1; + } else if (!strcmp((char *)xml_node->name, "AbsoluteYearlyRecurrence")) { + if (process_absoluteyearlyrecurrence(xml_node, &ical_recur)) + return -1; + } else if (!strcmp((char *)xml_node->name, "RelativeYearlyRecurrence")) { + if (process_relativeyearlyrecurrence(xml_node, &ical_recur)) + return -1; + } else if (!strcmp((char *)xml_node->name, "AbsoluteMonthlyRecurrence")) { + if (process_absolutemonthlyrecurrence(xml_node, &ical_recur)) + return -1; + } else if (!strcmp((char *)xml_node->name, "RelativeMonthlyRecurrence")) { + if (process_relativemonthlyrecurrence(xml_node, &ical_recur)) + return -1; + } else if (!strcmp((char *)xml_node->name, "EndDateRecurrence")) { + for (xml_node2 = xml_node->children; xml_node2; + xml_node2 = xml_node2->next) { + if (xml_node2->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node2->name, "EndDate")) + end_date = (char *)xmlNodeGetContent(xml_node2); + } + } else if (!strcmp((char *)xml_node->name, "NumberedRecurrence")) { + for (xml_node2 = xml_node->children; xml_node2; + xml_node2 = xml_node2->next) { + if (xml_node2->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node2->name, "NumberOfOccurrences")) + nr_occurrences = (char *)xmlNodeGetContent(xml_node2); + } + } + } + if (ical_recur.freq == ICAL_NO_RECURRENCE) { + fprintf(stderr, "No recognised Recurrence type\n"); + return -1; + } + + if (end_date) { + if (strlen(end_date) != 11 || end_date[4] != '-' || + end_date[7] != '-' || end_date[10] != 'Z') { + fprintf(stderr, "Failed to parse Recurrence EndDate '%s'\n", + end_date); + return -1; + } + end_date = strdup(end_date); + end_date[10] = 0; + ical_recur.until = icaltime_from_string(end_date); + } else if (nr_occurrences) { + ical_recur.count = strtol(nr_occurrences, NULL, 10); + } + prop = icalproperty_new_rrule(ical_recur); + icalcomponent_add_property(comp, prop); + return 0; +} + +int process_itemid(icalcomponent *comp, xmlNode *xml_node) +{ + const char *id = (char *)xmlGetProp(xml_node, (unsigned char *)"Id"); + if (!id) + return -1; + + icalcomponent_set_uid(comp, id); + return 0; +} +int process_reminder_mins(icalcomponent *calcomp, xmlNode *xml_node) +{ + const char *minutes; + int minutesnr; + icalcomponent *comp; + icalproperty *prop; + struct icaltriggertype trig; + + minutes = (char *)xmlNodeGetContent(xml_node); + if (!minutes) + return -1; + + minutesnr = strtol(minutes, NULL, 10); + + comp = icalcomponent_new_valarm(); + prop = icalproperty_new_action(ICAL_ACTION_DISPLAY); + icalcomponent_add_property(comp, prop); + prop = icalproperty_new_description("REMINDER"); + icalcomponent_add_property(comp, prop); + trig = icaltriggertype_from_int(-minutesnr * 60); + prop = icalproperty_new_trigger(trig); + icalcomponent_add_property(comp, prop); + + icalcomponent_add_component(calcomp, comp); + return 0; +} + +static const char *ews_tz_to_ical(const char *ewstz) +{ + static struct { + const char *exch; + const char *ical; + } table[] = { + /* List found at http://forums.asp.net/p/1518462/3641104.aspx */ + { "(UTC) Casablanca", "Africa/Casablanca" }, + { "(UTC) Coordinated Universal Time", "UTC" }, + { "(UTC) Dublin, Edinburgh, Lisbon, London", "Europe/London" }, + { "(UTC) Monrovia, Reykjavik", "Atlantic/Reykjavik" }, + { "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", "Europe/Amsterdam" }, + { "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague", "Europe/Belgrade" }, + { "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris", "Europe/Brussels" }, + { "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb", "Europe/Sarajevo" }, + { "(UTC+01:00) West Central Africa", "Africa/Douala" }, + { "(UTC+02:00) Amman", "Asia/Amman" }, + { "(UTC+02:00) Athens, Bucharest, Istanbul", "Europe/Athens" }, + { "(UTC+02:00) Beirut", "Asia/Beirut" }, + { "(UTC+02:00) Cairo", "Africa/Cairo" }, + { "(UTC+02:00) Harare, Pretoria", "Africa/Harare" }, + { "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius", "Europe/Helsinki" }, + { "(UTC+02:00) Jerusalem", "Asia/Jerusalem" }, + { "(UTC+02:00) Minsk", "Europe/Minsk" }, + { "(UTC+02:00) Windhoek", "Africa/Windhoek" }, + { "(UTC+03:00) Baghdad", "Asia/Baghdad" }, + { "(UTC+03:00) Kuwait, Riyadh", "Asia/Kuwait" }, + { "(UTC+03:00) Moscow, St. Petersburg, Volgograd", "Europe/Moscow" }, + { "(UTC+03:00) Nairobi", "Africa/Nairobi" }, + { "(UTC+03:30) Tehran", "Asia/Tehran" }, + { "(UTC+04:00) Abu Dhabi, Muscat", "Asia/Muscat" }, + { "(UTC+04:00) Baku", "Asia/Baku" }, + { "(UTC+04:00) Port Louis", "Indian/Mauritius" }, + { "(UTC+04:00) Tbilisi", "Asia/Tbilisi" }, + { "(UTC+04:00) Yerevan", "Asia/Yerevan" }, + { "(UTC+04:30) Kabul", "Asia/Kabul" }, + { "(UTC+05:00) Ekaterinburg", "Asia/Yekaterinburg" }, + { "(UTC+05:00) Islamabad, Karachi", "Asia/Karachi" }, + { "(UTC+05:00) Tashkent", "Asia/Tashkent" }, + { "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi", "Asia/Kolkata" }, + { "(UTC+05:30) Sri Jayawardenepura", "Asia/Colombo" }, + { "(UTC+05:45) Kathmandu", "Asia/Katmandu" }, + { "(UTC+06:00) Astana, Dhaka", "Asia/Dhaka" }, + { "(UTC+06:00) Novosibirsk", "Asia/Novosibirsk" }, + { "(UTC+06:30) Yangon (Rangoon)", "Asia/Rangoon" }, + { "(UTC+07:00) Bangkok, Hanoi, Jakarta", "Asia/Bangkok" }, + { "(UTC+07:00) Krasnoyarsk", "Asia/Krasnoyarsk" }, + { "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi", "Asia/Shanghai" }, + { "(UTC+08:00) Irkutsk", "Asia/Irkutsk" }, + { "(UTC+08:00) Kuala Lumpur, Singapore", "Asia/Kuala_Lumpur" }, + { "(UTC+08:00) Perth", "Australia/Perth" }, + { "(UTC+08:00) Taipei", "Asia/Taipei" }, + { "(UTC+08:00) Ulaanbaatar", "Asia/Ulaanbaatar" }, + { "(UTC+09:00) Osaka, Sapporo, Tokyo", "Asia/Tokyo" }, + { "(UTC+09:00) Seoul", "Asia/Seoul" }, + { "(UTC+09:00) Yakutsk", "Asia/Yakutsk" }, + { "(UTC+09:30) Adelaide", "Australia/Adelaide" }, + { "(UTC+09:30) Darwin", "Australia/Darwin" }, + { "(UTC+10:00) Brisbane", "Australia/Brisbane" }, + { "(UTC+10:00) Canberra, Melbourne, Sydney", "Australia/Melbourne" }, + { "(UTC+10:00) Guam, Port Moresby", "Pacific/Guam" }, + { "(UTC+10:00) Hobart", "Australia/Hobart" }, + { "(UTC+10:00) Vladivostok", "Asia/Vladivostok" }, + { "(UTC+11:00) Magadan, Solomon Is., New Caledonia", "Asia/Magadan" }, + { "(UTC+12:00) Auckland, Wellington", "Pacific/Auckland" }, + { "(UTC+12:00) Fiji, Marshall Is.", "Pacific/Fiji" }, + { "(UTC+12:00) Petropavlovsk-Kamchatsky", "Asia/Kamchatka" }, + { "(UTC+13:00) Nuku'alofa", "Pacific/Tongatapu" }, + { "(UTC-01:00) Azores", "Atlantic/Azores" }, + { "(UTC-01:00) Cape Verde Is.", "Atlantic/Cape_Verde" }, + { "(UTC-02:00) Mid-Atlantic", "Atlantic/South_Georgia" }, + { "(UTC-03:00) Brasilia", "America/Sao_Paulo" }, + { "(UTC-03:00) Buenos Aires", "America/Argentina/Buenos_Aires" }, + { "(UTC-03:00) Cayenne", "America/Cayenne" }, + { "(UTC-03:00) Greenland", "America/Godthab" }, + { "(UTC-03:00) Montevideo", "America/Montevideo" }, + { "(UTC-03:30) Newfoundland", "America/St_Johns" }, + { "(UTC-04:00) Asuncion", "America/Asuncion" }, + { "(UTC-04:00) Atlantic Time (Canada)", "America/Halifax" }, + { "(UTC-04:00) Georgetown, La Paz, San Juan", "America/Argentina/San_Juan" }, + { "(UTC-04:00) Manaus", "America/Manaus" }, + { "(UTC-04:00) Santiago", "America/Santiago" }, + { "(UTC-04:30) Caracas", "America/Caracas" }, + { "(UTC-05:00) Bogota, Lima, Quito", "America/Bogota" }, + { "(UTC-05:00) Eastern Time (US & Canada)", "America/New_York" }, + { "(UTC-05:00) Indiana (East)", "America/Indiana/Indianapolis" }, + { "(UTC-06:00) Central America", "America/Costa_Rica" }, + { "(UTC-06:00) Central Time (US & Canada)", "America/Chicago" }, + /* ? */ { "(UTC-06:00) Guadalajara, Mexico City, Monterrey", "America/Mexico_City" }, + { "(UTC-06:00) Saskatchewan", "Canada/Saskatchewan" }, + { "(UTC-07:00) Arizona", "America/Phoenix" }, + { "(UTC-07:00) Chihuahua, La Paz, Mazatlan", "America/Chihuahua" }, + { "(UTC-07:00) Mountain Time (US & Canada)", "America/Denver" }, + { "(UTC-08:00) Pacific Time (US & Canada)", "America/Los_Angeles" }, + { "(UTC-08:00) Tijuana, Baja California", "America/Tijuana" }, + { "(UTC-09:00) Alaska", "America/Anchorage" }, + { "(UTC-10:00) Hawaii", "Pacific/Honolulu" }, + { "(UTC-11:00) Midway Island, Samoa", "Pacific/Midway" }, + /* ? */ { "(UTC-12:00) International Date Line West", "Pacific/Apia" }, + + /* Extra zones I've seen in testing. Is there *no* sanity in + *any* part of what Microsoft does with time zones? */ + { "(GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London", "Europe/London" }, + { "Pacific Standard Time", "America/Los_Angeles" }, + }; + int i; + int offset = 0; + + if (!ewstz) + return NULL; + + /* Sometimes it says 'UTC'; sometimes 'GMT' */ + if (!strncmp(ewstz, "(GMT", 4)) + offset = 4; + + for (i = 0; i < ARRAY_SIZE(table); i++) { + if (!strcmp(ewstz + offset, table[i].exch + offset)) + return table[i].ical; + } + + fprintf(stderr, "Unrecognised TimeZone '%s'\n", ewstz); + return NULL; + +} + +icaltimezone *get_timezone(xmlNode *xml_node) +{ + icaltimezone *zone = NULL; + const char *ews_tzname = NULL; + const char *tzname = NULL; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + else if (!strcmp((char *)xml_node->name, "TimeZone")) + break; + } + if (!xml_node) { + fprintf(stderr, "Failed to find TimeZone element; falling back to UTC\n"); + return NULL; + } + + ews_tzname = (char *)xmlNodeGetContent(xml_node); + + /* FIXME: Look for manual timezone definitions in the XML and compare against + those first, before using the standard Windows ones */ + + tzname = ews_tz_to_ical(ews_tzname); + if (!tzname) + return NULL; + + zone = icaltimezone_get_builtin_timezone(tzname); + if (zone) + return zone; + + fprintf(stderr, "Failed to load ical timezone for '%s' (%s)\n", tzname, + ews_tzname); + return NULL; +} + +int get_baseoffset(xmlNode *xml_node, int *retval) +{ + const char *baseoffset; + struct icaldurationtype ofs; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + else if (!strcmp((char *)xml_node->name, "BaseOffset")) + break; + } + if (!xml_node) { + fprintf(stderr, " has no \n"); + return -1; + } + baseoffset = (const char *)xmlNodeGetContent(xml_node); + if (!baseoffset) { + fprintf(stderr, " is empty\n"); + return -1; + } + ofs = icaldurationtype_from_string(baseoffset); + if (icaldurationtype_is_bad_duration(ofs)) { + fprintf(stderr, "Failed to parse '%s'\n", baseoffset); + return -1; + } + if (ofs.is_neg) + *retval = ofs.minutes * 60; + else + *retval = -ofs.minutes * 60; + return 0; +} + +int process_relativeyearlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) +{ + const char *week = NULL, *month = NULL, *weekday = NULL; + int weeknr, monthnr, daynr; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "DaysOfWeek")) { + weekday = (char *)xmlNodeGetContent(xml_node); + } else if (!strcmp((char *)xml_node->name, "Month")) { + month = (char *)xmlNodeGetContent(xml_node); + } else if (!strcmp((char *)xml_node->name, "DayOfWeekIndex")) { + week = (char *)xmlNodeGetContent(xml_node); + } + } + if (!week || !month || !weekday) { + fprintf(stderr, "RelativeYearlyRecurrence missing essential fields (%s,%s,%s)\n", + week, month, weekday); + return -1; + } + monthnr = month_to_number(month); + if (!month) + return -1; + + weeknr = weekindex_to_ical(week); + if (!weeknr) + return -1; + + daynr = weekday_to_number(weekday, 10); + if (!daynr) + return -1; + + icalrecurrencetype_clear(ical_recur); + ical_recur->freq = ICAL_YEARLY_RECURRENCE; + ical_recur->by_month[0] = monthnr; + + + if (daynr < 8) { + if (weeknr > 0) + ical_recur->by_day[0] = daynr + (weeknr * 8); + else + ical_recur->by_day[0] = -8 - daynr; + } else if (daynr == 8) { /* Day */ + ical_recur->by_month_day[0] = weeknr; + } else if (daynr == 9) { /* Weekday */ + ical_recur->by_day[0] = 2; + ical_recur->by_day[1] = 3; + ical_recur->by_day[2] = 4; + ical_recur->by_day[3] = 5; + ical_recur->by_day[4] = 6; + ical_recur->by_set_pos[0] = weeknr; + } else if (daynr == 10) { /* WeekendDay */ + ical_recur->by_day[0] = 1; + ical_recur->by_day[1] = 7; + ical_recur->by_set_pos[0] = weeknr; + } + + return 0; +} +int process_absoluteyearlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) +{ + const char *day_of_month = NULL; + const char *month = NULL; + int daynr, monthnr; + + for (xml_node = xml_node->children; xml_node; + xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "DayOfMonth")) + day_of_month = (char *)xmlNodeGetContent(xml_node); + else if (!strcmp((char *)xml_node->name, "Month")) + month = (char *)xmlNodeGetContent(xml_node); + } + if (!day_of_month || !month) { + fprintf(stderr, "AbsoluteYearlyRecurrence missing essential fields (%s,%s)\n", + day_of_month, month); + return -1; + } + daynr = strtol(day_of_month, NULL, 10); + if (!daynr) { + fprintf(stderr, "Failed to parse DayOfMonth '%s'\n", day_of_month); + return -1; + } + monthnr = month_to_number(month); + if (!month) + return -1; + + icalrecurrencetype_clear(ical_recur); + ical_recur->freq = ICAL_YEARLY_RECURRENCE; + ical_recur->by_month[0] = monthnr; + ical_recur->by_month_day[0] = daynr; + return 0; +} + +int process_relativemonthlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) +{ + const char *week = NULL, *interval = NULL, *weekday = NULL; + int weeknr, intervalnr, daynr; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "DaysOfWeek")) { + weekday = (char *)xmlNodeGetContent(xml_node); + } else if (!strcmp((char *)xml_node->name, "Interval")) { + interval = (char *)xmlNodeGetContent(xml_node); + } else if (!strcmp((char *)xml_node->name, "DayOfWeekIndex")) { + week = (char *)xmlNodeGetContent(xml_node); + } + } + if (!week || !interval || !weekday) { + fprintf(stderr, "RelativeMonthlyRecurrence missing essential fields (%s,%s,%s)\n", + week, interval, weekday); + return -1; + } + intervalnr = strtol(interval, NULL, 10); + if (!intervalnr) { + fprintf(stderr, "Failed to parse Interval '%s'\n", interval); + return -1; + } + weeknr = weekindex_to_ical(week); + if (!weeknr) + return -1; + + daynr = weekday_to_number(weekday, 10); + if (!daynr) + return -1; + + icalrecurrencetype_clear(ical_recur); + ical_recur->freq = ICAL_MONTHLY_RECURRENCE; + ical_recur->interval = intervalnr; + if (daynr < 8) { + if (weeknr > 0) + ical_recur->by_day[0] = daynr + (weeknr * 8); + else + ical_recur->by_day[0] = -8 - daynr; + } else if (daynr == 8) { /* Day */ + ical_recur->by_month_day[0] = weeknr; + } else if (daynr == 9) { /* Weekday */ + ical_recur->by_day[0] = 2; + ical_recur->by_day[1] = 3; + ical_recur->by_day[2] = 4; + ical_recur->by_day[3] = 5; + ical_recur->by_day[4] = 6; + ical_recur->by_set_pos[0] = weeknr; + } else if (daynr == 10) { /* WeekendDay */ + ical_recur->by_day[0] = 1; + ical_recur->by_day[1] = 7; + ical_recur->by_set_pos[0] = weeknr; + } + + return 0; +} +int process_absolutemonthlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) +{ + const char *interval = NULL, *monthday = NULL; + int intervalnr, monthdaynr; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "DayOfMonth")) { + monthday = (char *)xmlNodeGetContent(xml_node); + } else if (!strcmp((char *)xml_node->name, "Interval")) { + interval = (char *)xmlNodeGetContent(xml_node); + } + } + if (!interval || !monthday) { + fprintf(stderr, "AbsoluteMonthlyRecurrence missing essential fields (%s,%s)\n", + interval, monthday); + return -1; + } + intervalnr = strtol(interval, NULL, 10); + if (!intervalnr) { + fprintf(stderr, "Failed to parse Interval '%s'\n", interval); + return -1; + } + monthdaynr = strtol(monthday, NULL, 10); + if (!monthday) { + fprintf(stderr, "Failed to parse DayOfMonth '%s'\n", monthday); + return -1; + } + + icalrecurrencetype_clear(ical_recur); + ical_recur->freq = ICAL_MONTHLY_RECURRENCE; + ical_recur->interval = intervalnr; + ical_recur->by_month_day[0] = monthdaynr; + return 0; +} + +static int weekdays_to_recur_byday(const char *days, struct icalrecurrencetype *ical_recur) +{ + const char *space; + const char *day; + int count = 0; + int daynr; + + do { + space = strchr(days, ' '); + if (space) + day = g_strndup(days, space - days); + else + day = days; + + daynr = weekday_to_number(day, 7); + if (!daynr) + return -1; + + if (count == ICAL_BY_DAY_SIZE) { + fprintf(stderr, "Too many days in DaysOfWeek list\n"); + return -1; + } + + ical_recur->by_day[count++] = daynr; + if (space) { + free((char *)day); + days = space + 1; + } + } while (space); + return 0; +} + + + +int process_weeklyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) +{ + const char *interval = NULL, *weekday = NULL, *firstday = NULL; + int intervalnr, firstdaynr; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "DaysOfWeek")) { + weekday = (char *)xmlNodeGetContent(xml_node); + } else if (!strcmp((char *)xml_node->name, "Interval")) { + interval = (char *)xmlNodeGetContent(xml_node); + } else if (!strcmp((char *)xml_node->name, "FirstDayOfWeek")) { + firstday = (char *)xmlNodeGetContent(xml_node); + } + } + if (!interval || !weekday) { + fprintf(stderr, "WeeklyRecurrence missing essential fields (%s,%s)\n", + interval, weekday); + return -1; + } + intervalnr = strtol(interval, NULL, 10); + if (!intervalnr) { + fprintf(stderr, "Failed to parse Interval '%s'\n", interval); + return -1; + } + if (firstday) + firstdaynr = weekday_to_number(firstday, 7); + else + firstdaynr = 0; + + icalrecurrencetype_clear(ical_recur); + ical_recur->interval = intervalnr; + ical_recur->week_start = firstdaynr; + + if (weekdays_to_recur_byday(weekday, ical_recur)) + return -1; + + ical_recur->freq = ICAL_WEEKLY_RECURRENCE; + return 0; +} +int process_dailyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) +{ + const char *interval = NULL; + int intervalnr; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "Interval")) { + interval = (char *)xmlNodeGetContent(xml_node); + } + } + if (!interval) { + fprintf(stderr, "DailyRecurrence missing essential fields (%s)\n", + interval); + return -1; + } + intervalnr = strtol(interval, NULL, 10); + if (!intervalnr) { + fprintf(stderr, "Failed to parse Interval '%s'\n", interval); + return -1; + } + + icalrecurrencetype_clear(ical_recur); + ical_recur->freq = ICAL_DAILY_RECURRENCE; + ical_recur->interval = intervalnr; + + return 0; +} +icalcomponent *process_timezone_rule(xmlNode *xml_node, + icalcomponent_kind kind, int *offset) +{ + icalcomponent *comp = icalcomponent_new(kind); + char *tzname; + icalproperty *prop; + + tzname = (char *)xmlGetProp(xml_node, (xmlChar *)"TimeZoneName"); + if (tzname) { + prop = icalproperty_new_tzname(tzname); + icalcomponent_add_property(comp, prop); + } + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + else if (!strcmp((char *)xml_node->name, "Offset")) { + char *ofs_string = (char *)xmlNodeGetContent(xml_node); + struct icaldurationtype ofs = icaldurationtype_from_string(ofs_string); + if (ofs.is_neg) + *offset += ofs.minutes * 60; + else + *offset -= ofs.minutes * 60; + + prop = icalproperty_new_tzoffsetto(*offset); + icalcomponent_add_property(comp, prop); + } else if (!strcmp((char *)xml_node->name, "RelativeYearlyRecurrence")) { + struct icalrecurrencetype ical_recur; + + if (process_relativeyearlyrecurrence(xml_node, &ical_recur)) + return NULL; + prop = icalproperty_new_rrule(ical_recur); + icalcomponent_add_property(comp, prop); + } else if (!strcmp((char *)xml_node->name, "AbsoluteDate")) { + /* Are there really timezones which change on the same date + every year? */ + fprintf(stderr, "Don't know how to handle AbsoluteDate for timezone change: '%s'\n", + xmlNodeGetContent(xml_node)); + } else if (!strcmp((char *)xml_node->name, "Time")) { + struct icaltimetype dtstart; + char *time_string = (char *)xmlNodeGetContent(xml_node); + + if (strlen(time_string) != 8 || time_string[2] != ':' || + time_string[5] != ':') { + fprintf(stderr, "Cannot parse dst change time '%s'\n", + time_string); + return NULL; + } + memset(&dtstart, 0, sizeof(dtstart)); + dtstart.year = 1900; + dtstart.month = 1; + dtstart.day = 1; + dtstart.hour = strtol(time_string, NULL, 10); + dtstart.minute = strtol(time_string + 3, NULL, 10); + dtstart.second = strtol(time_string + 6, NULL, 10); + + prop = icalproperty_new_dtstart(dtstart); + icalcomponent_add_property(comp, prop); + } + } + return comp; +} + +icaltimezone *get_meeting_timezone(xmlNode *xml_node) +{ + icalcomponent *comp = NULL, *dst_zone = NULL, *std_zone = NULL; + icalproperty *prop; + icaltimezone *z; + const char *tzname = NULL; + + int std_offset, dst_offset; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + else if (!strcmp((char *)xml_node->name, "MeetingTimeZone")) + break; + } + if (!xml_node) + return NULL; + comp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT); + + tzname = (const char *)xmlGetProp(xml_node, (xmlChar *)"TimeZoneName"); + + prop = icalproperty_new_tzid(tzname); + icalcomponent_add_property(comp, prop); + + if (get_baseoffset(xml_node, &std_offset)) + return NULL; + dst_offset = std_offset; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)xml_node->name, "BaseOffset")) + continue; + else if (!strcmp((char *)xml_node->name, "Standard")) { + std_zone = process_timezone_rule(xml_node, + ICAL_XSTANDARD_COMPONENT, + &std_offset); + } else if (!strcmp((char *)xml_node->name, "Daylight")) { + dst_zone = process_timezone_rule(xml_node, + ICAL_XDAYLIGHT_COMPONENT, + &dst_offset); + } else { + fprintf(stderr, "Unknown element in MeetingTimeZone: %s\n", + xml_node->name); + } + } + + if (std_zone && dst_zone) { + prop = icalproperty_new_tzoffsetfrom(dst_offset); + icalcomponent_add_property(std_zone, prop); + + prop = icalproperty_new_tzoffsetfrom(std_offset); + icalcomponent_add_property(dst_zone, prop); + + icalcomponent_add_component(comp, std_zone); + icalcomponent_add_component(comp, dst_zone); + } else { + if (!std_zone) { + std_zone = icalcomponent_new(ICAL_XSTANDARD_COMPONENT); + + prop = icalproperty_new_tzoffsetto(std_offset); + icalcomponent_add_property(std_zone, prop); + + prop = icalproperty_new_tzoffsetfrom(std_offset); + icalcomponent_add_property(std_zone, prop); + } + icalcomponent_add_component(comp, std_zone); + } + + z = icaltimezone_new(); + icaltimezone_set_component(z, comp); + return z; +} diff --git a/ews2ical.c b/ews2ical.c index 56ae8d2..532fdc9 100644 --- a/ews2ical.c +++ b/ews2ical.c @@ -19,28 +19,7 @@ FILE *calfile; -int process_relativeyearlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); -int process_absoluteyearlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); -int process_relativemonthlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); -int process_absolutemonthlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); -int process_weeklyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); -int process_dailyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur); - -int process_organizer(icalcomponent *comp, xmlNode *xml_node); -int process_required_attendees(icalcomponent *comp, xmlNode *xml_node); -int process_optional_attendees(icalcomponent *comp, xmlNode *xml_node); -int process_time(icalcomponent *comp, xmlNode *xml_node, icaltimetype *ical_time); -int process_truefalse(icalcomponent *comp, xmlNode *xml_node, gboolean *val); -int process_location(icalcomponent *comp, xmlNode *xml_node); -int process_body(icalcomponent *comp, xmlNode *xml_node); -int process_subject(icalcomponent *comp, xmlNode *xml_node); -int process_recurrence(icalcomponent *comp, xmlNode *xml_node, icaltimezone *zone); -int process_itemid(icalcomponent *comp, xmlNode *xmlnode); -int process_reminder_mins(icalcomponent *comp, xmlNode *xmlnode); -icaltimezone *get_timezone(xmlNode *xmlnode); -icaltimezone *get_meeting_timezone(xmlNode *xml_node); - -icalcomponent *ews_calitem_to_ical(xmlNode *xml_node); +extern icalcomponent *ews_calitem_to_ical(xmlNode *xml_node); int main(int argc, char **argv) { @@ -153,1106 +132,3 @@ int main(int argc, char **argv) return 0; } -icalcomponent *ews_calitem_to_ical(xmlNode *xml_node) -{ - icaltimetype dtstart, dtend; - icaltimezone *icaltz; - icalcomponent *comp, *calcomp; - icalproperty *prop; - gboolean allday = FALSE; - - dtstart = dtend = icaltime_null_time(); - - calcomp = icalcomponent_new_vcalendar(); - icalcomponent_set_method(calcomp, ICAL_METHOD_PUBLISH); - prop = icalproperty_new_version("2.0"); - icalcomponent_add_property(calcomp, prop); - - icaltz = get_meeting_timezone(xml_node); - if (!icaltz) - icaltz = get_timezone(xml_node); - if (icaltz) { - icalcomponent *comp = icaltimezone_get_component(icaltz); - icalcomponent_add_component(calcomp, comp); - } - comp = icalcomponent_new(ICAL_VEVENT_COMPONENT); - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "Organizer")) - process_organizer(comp, xml_node); - else if (!strcmp((char *)xml_node->name, "RequiredAttendees")) - process_required_attendees(comp, xml_node); - else if (!strcmp((char *)xml_node->name, "OptionalAttendees")) - process_optional_attendees(comp, xml_node); - else if (!strcmp((char *)xml_node->name, "Start")) - process_time(comp, xml_node, &dtstart); - else if (!strcmp((char *)xml_node->name, "End")) - process_time(comp, xml_node, &dtend); - else if (!strcmp((char *)xml_node->name, "Body")) - process_body(comp, xml_node); - else if (!strcmp((char *)xml_node->name, "Location")) - process_location(comp, xml_node); - else if (!strcmp((char *)xml_node->name, "Subject")) - process_subject(comp, xml_node); - else if (!strcmp((char *)xml_node->name, "Recurrence")) - process_recurrence(comp, xml_node, icaltz); - else if (!strcmp((char *)xml_node->name, "ItemId")) - process_itemid(comp, xml_node); - else if (!strcmp((char *)xml_node->name, "IsAllDayEvent")) - process_truefalse(comp, xml_node, &allday); - else if (!strcmp((char *)xml_node->name, "ReminderMinutesBeforeStart")) - process_reminder_mins(comp, xml_node); - else if (!strcmp((char *)xml_node->name, "ParentFolderId") || - !strcmp((char *)xml_node->name, "DateTimeReceived") || - !strcmp((char *)xml_node->name, "Size") || - !strcmp((char *)xml_node->name, "IsSubmitted") || - !strcmp((char *)xml_node->name, "IsDraft") || - !strcmp((char *)xml_node->name, "IsFromMe") || - !strcmp((char *)xml_node->name, "IsResend") || - !strcmp((char *)xml_node->name, "IsUnmodified") || - !strcmp((char *)xml_node->name, "DateTimeSent") || - !strcmp((char *)xml_node->name, "DateTimeCreated") || - !strcmp((char *)xml_node->name, "ResponseObjects") || - !strcmp((char *)xml_node->name, "DisplayCc") || - !strcmp((char *)xml_node->name, "DisplayTo") || - !strcmp((char *)xml_node->name, "Culture") || - !strcmp((char *)xml_node->name, "IsRecurring") || - !strcmp((char *)xml_node->name, "MeetingRequestWasSent") || - !strcmp((char *)xml_node->name, "IsResponseRequested") || - !strcmp((char *)xml_node->name, "MyResponseType") || - !strcmp((char *)xml_node->name, "ConflictingMeetingCount") || - !strcmp((char *)xml_node->name, "AdjacentMeetingCount") || - !strcmp((char *)xml_node->name, "TimeZone") || - !strcmp((char *)xml_node->name, "AppointmentSequenceNumber") || - !strcmp((char *)xml_node->name, "AppointmentState")) { - /* Ignore these */ - } -#if 0 - else - fprintf(stderr, "Unhandled node type '%s'\n", xml_node->name); -#endif - } - - /* We don't handle really floating events -- which change their time - according to the time zone of the observer (like lunch at noon - under the sundial wherever you are in the world). But that's OK; - Exchange doesn't seem to either: - - AFAICT, you can't create them with Outlook. - - If you send Exchange an invitation with floating times, which - happens to have a VTIMEZONE, it'll assume that timezone (in - violation of RFC2445). - - If you send Exchange an invitation with floating times with - *no* stray VTIMEZONE in the file (which was a mistake), then - it creates an item with no timezone but does weird things -- - this invite: - DTSTART:20100720T120000 - DTEND:20100720T120010 - ... leads to an Exchange object saying... - 2010-07-20T11:00:00Z - 2010-07-20T11:00:00Z - - For any recurring object without time zones (including the last - test above, as well as all day events such as birthdays created - with Outlook/Exchange 2003, Exchange will refuse to return the - object if the field is requested, reporting - 'Corrupt Data'. - - Tested with Exchange 2007. - */ - if (icaltz && !allday) { - dtstart = icaltime_convert_to_zone(dtstart, icaltz); - dtend = icaltime_convert_to_zone(dtend, icaltz); - } - if (allday) { - dtstart.is_date = 1; - dtend.is_date = 1; - } - if (!icaltime_is_null_time(dtstart)) - icalcomponent_set_dtstart(comp, dtstart); - - if (!icaltime_is_null_time(dtend)) - icalcomponent_set_dtend(comp, dtend); - - icalcomponent_add_component(calcomp, comp); - return calcomp; -} - -int process_mailbox(xmlNode *xml_node, const char **r_name, const char **r_email) -{ - const char *type = NULL, *name = NULL, *email = NULL; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "Name")) - name = (char *)xmlNodeGetContent(xml_node); - if (!strcmp((char *)xml_node->name, "EmailAddress")) - email = (char *)xmlNodeGetContent(xml_node); - if (!strcmp((char *)xml_node->name, "RoutingType")) { - type = (char *)xmlNodeGetContent(xml_node); - } - } - - /* We seem to get EX routing for people who don't exist any more */ - if (type && strcmp(type, "SMTP")) { - if (strcmp(type, "EX")) - fprintf(stderr, "Unknown RoutingType '%s' ('%s' '%s')\n", - type, name, email); - return -1; - } - *r_name = name; - *r_email = email; - return 0; -} - -int process_organizer(icalcomponent *comp, xmlNode *xml_node) -{ - icalproperty *prop; - icalparameter *param; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "Mailbox")) { - const char *name = NULL, *email = NULL; - char *mailtoname; - if (process_mailbox(xml_node, &name, &email)) - return -1; - - mailtoname = g_strdup_printf("mailto:%s", email); - - prop = icalproperty_new_organizer(mailtoname); - free(mailtoname); - param = icalparameter_new_cn(name); - icalproperty_add_parameter(prop, param); - icalcomponent_add_property(comp, prop); - } - } - return 0; -} - -int process_attendee(icalcomponent *comp, xmlNode *xml_node, icalparameter_role role) -{ - icalproperty *prop; - icalparameter *param; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "Mailbox")) { - const char *name = NULL, *email = NULL; - char *mailtoname; - if (process_mailbox(xml_node, &name, &email)) - return -1; - - mailtoname = g_strdup_printf("mailto:%s", email); - - prop = icalproperty_new_attendee(mailtoname); - free(mailtoname); - param = icalparameter_new_cn(name); - icalproperty_add_parameter(prop, param); - param = icalparameter_new_role(role); - icalproperty_add_parameter(prop, param); - icalcomponent_add_property(comp, prop); - } - } - return 0; -} - -int process_required_attendees(icalcomponent *comp, xmlNode *xml_node) -{ - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "Attendee")) { - if (process_attendee(comp, xml_node, - ICAL_ROLE_REQPARTICIPANT)) - { } - } - } - return 0; -} - -int process_optional_attendees(icalcomponent *comp, xmlNode *xml_node) -{ - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "Attendee")) { - if (process_attendee(comp, xml_node, - ICAL_ROLE_OPTPARTICIPANT)) - { } - } - } - return 0; -} - -int process_time(icalcomponent *comp, xmlNode *xml_node, icaltimetype *ical_time) -{ - char *ews_time = (char *)xmlNodeGetContent(xml_node); - - if (!ews_time) - return -1; - *ical_time = icaltime_from_string(ews_time); - return 0; -} - -int process_truefalse(icalcomponent *comp, xmlNode *xml_node, gboolean *val) -{ - char *truth = (char *)xmlNodeGetContent(xml_node); - - if (!truth) - return -1; - if (!strcmp(truth, "true")) - *val = TRUE; - else if (!strcmp(truth, "false")) - *val = FALSE; - else { - fprintf(stderr, "Unrecognised truth value '%s' in %s node\n", - truth, xml_node->name); - return -1; - } - return 0; -} - -int process_location (icalcomponent *comp, xmlNode *xml_node) -{ - const char *loc = (char *)xmlNodeGetContent(xml_node); - - if (!loc) - return -1; - icalcomponent_set_location(comp, loc); - return 0; -} - -int process_body(icalcomponent *comp, xmlNode *xml_node) -{ - const char *body = (char *)xmlNodeGetContent(xml_node); - - if (!body) - return -1; - - if (!strncasecmp(body, "children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "WeeklyRecurrence")) { - if (process_weeklyrecurrence(xml_node, &ical_recur)) - return -1; - } else if (!strcmp((char *)xml_node->name, "DailyRecurrence")) { - if (process_dailyrecurrence(xml_node, &ical_recur)) - return -1; - } else if (!strcmp((char *)xml_node->name, "AbsoluteYearlyRecurrence")) { - if (process_absoluteyearlyrecurrence(xml_node, &ical_recur)) - return -1; - } else if (!strcmp((char *)xml_node->name, "RelativeYearlyRecurrence")) { - if (process_relativeyearlyrecurrence(xml_node, &ical_recur)) - return -1; - } else if (!strcmp((char *)xml_node->name, "AbsoluteMonthlyRecurrence")) { - if (process_absolutemonthlyrecurrence(xml_node, &ical_recur)) - return -1; - } else if (!strcmp((char *)xml_node->name, "RelativeMonthlyRecurrence")) { - if (process_relativemonthlyrecurrence(xml_node, &ical_recur)) - return -1; - } else if (!strcmp((char *)xml_node->name, "EndDateRecurrence")) { - for (xml_node2 = xml_node->children; xml_node2; - xml_node2 = xml_node2->next) { - if (xml_node2->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node2->name, "EndDate")) - end_date = (char *)xmlNodeGetContent(xml_node2); - } - } else if (!strcmp((char *)xml_node->name, "NumberedRecurrence")) { - for (xml_node2 = xml_node->children; xml_node2; - xml_node2 = xml_node2->next) { - if (xml_node2->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node2->name, "NumberOfOccurrences")) - nr_occurrences = (char *)xmlNodeGetContent(xml_node2); - } - } - } - if (ical_recur.freq == ICAL_NO_RECURRENCE) { - fprintf(stderr, "No recognised Recurrence type\n"); - return -1; - } - - if (end_date) { - if (strlen(end_date) != 11 || end_date[4] != '-' || - end_date[7] != '-' || end_date[10] != 'Z') { - fprintf(stderr, "Failed to parse Recurrence EndDate '%s'\n", - end_date); - return -1; - } - end_date = strdup(end_date); - end_date[10] = 0; - ical_recur.until = icaltime_from_string(end_date); - } else if (nr_occurrences) { - ical_recur.count = strtol(nr_occurrences, NULL, 10); - } - prop = icalproperty_new_rrule(ical_recur); - icalcomponent_add_property(comp, prop); - return 0; -} - -int process_itemid(icalcomponent *comp, xmlNode *xml_node) -{ - const char *id = (char *)xmlGetProp(xml_node, (unsigned char *)"Id"); - if (!id) - return -1; - - icalcomponent_set_uid(comp, id); - return 0; -} -int process_reminder_mins(icalcomponent *calcomp, xmlNode *xml_node) -{ - const char *minutes; - int minutesnr; - icalcomponent *comp; - icalproperty *prop; - struct icaltriggertype trig; - - minutes = (char *)xmlNodeGetContent(xml_node); - if (!minutes) - return -1; - - minutesnr = strtol(minutes, NULL, 10); - - comp = icalcomponent_new_valarm(); - prop = icalproperty_new_action(ICAL_ACTION_DISPLAY); - icalcomponent_add_property(comp, prop); - prop = icalproperty_new_description("REMINDER"); - icalcomponent_add_property(comp, prop); - trig = icaltriggertype_from_int(-minutesnr * 60); - prop = icalproperty_new_trigger(trig); - icalcomponent_add_property(comp, prop); - - icalcomponent_add_component(calcomp, comp); - return 0; -} - -static const char *ews_tz_to_ical(const char *ewstz) -{ - static struct { - const char *exch; - const char *ical; - } table[] = { - /* List found at http://forums.asp.net/p/1518462/3641104.aspx */ - { "(UTC) Casablanca", "Africa/Casablanca" }, - { "(UTC) Coordinated Universal Time", "UTC" }, - { "(UTC) Dublin, Edinburgh, Lisbon, London", "Europe/London" }, - { "(UTC) Monrovia, Reykjavik", "Atlantic/Reykjavik" }, - { "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", "Europe/Amsterdam" }, - { "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague", "Europe/Belgrade" }, - { "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris", "Europe/Brussels" }, - { "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb", "Europe/Sarajevo" }, - { "(UTC+01:00) West Central Africa", "Africa/Douala" }, - { "(UTC+02:00) Amman", "Asia/Amman" }, - { "(UTC+02:00) Athens, Bucharest, Istanbul", "Europe/Athens" }, - { "(UTC+02:00) Beirut", "Asia/Beirut" }, - { "(UTC+02:00) Cairo", "Africa/Cairo" }, - { "(UTC+02:00) Harare, Pretoria", "Africa/Harare" }, - { "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius", "Europe/Helsinki" }, - { "(UTC+02:00) Jerusalem", "Asia/Jerusalem" }, - { "(UTC+02:00) Minsk", "Europe/Minsk" }, - { "(UTC+02:00) Windhoek", "Africa/Windhoek" }, - { "(UTC+03:00) Baghdad", "Asia/Baghdad" }, - { "(UTC+03:00) Kuwait, Riyadh", "Asia/Kuwait" }, - { "(UTC+03:00) Moscow, St. Petersburg, Volgograd", "Europe/Moscow" }, - { "(UTC+03:00) Nairobi", "Africa/Nairobi" }, - { "(UTC+03:30) Tehran", "Asia/Tehran" }, - { "(UTC+04:00) Abu Dhabi, Muscat", "Asia/Muscat" }, - { "(UTC+04:00) Baku", "Asia/Baku" }, - { "(UTC+04:00) Port Louis", "Indian/Mauritius" }, - { "(UTC+04:00) Tbilisi", "Asia/Tbilisi" }, - { "(UTC+04:00) Yerevan", "Asia/Yerevan" }, - { "(UTC+04:30) Kabul", "Asia/Kabul" }, - { "(UTC+05:00) Ekaterinburg", "Asia/Yekaterinburg" }, - { "(UTC+05:00) Islamabad, Karachi", "Asia/Karachi" }, - { "(UTC+05:00) Tashkent", "Asia/Tashkent" }, - { "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi", "Asia/Kolkata" }, - { "(UTC+05:30) Sri Jayawardenepura", "Asia/Colombo" }, - { "(UTC+05:45) Kathmandu", "Asia/Katmandu" }, - { "(UTC+06:00) Astana, Dhaka", "Asia/Dhaka" }, - { "(UTC+06:00) Novosibirsk", "Asia/Novosibirsk" }, - { "(UTC+06:30) Yangon (Rangoon)", "Asia/Rangoon" }, - { "(UTC+07:00) Bangkok, Hanoi, Jakarta", "Asia/Bangkok" }, - { "(UTC+07:00) Krasnoyarsk", "Asia/Krasnoyarsk" }, - { "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi", "Asia/Shanghai" }, - { "(UTC+08:00) Irkutsk", "Asia/Irkutsk" }, - { "(UTC+08:00) Kuala Lumpur, Singapore", "Asia/Kuala_Lumpur" }, - { "(UTC+08:00) Perth", "Australia/Perth" }, - { "(UTC+08:00) Taipei", "Asia/Taipei" }, - { "(UTC+08:00) Ulaanbaatar", "Asia/Ulaanbaatar" }, - { "(UTC+09:00) Osaka, Sapporo, Tokyo", "Asia/Tokyo" }, - { "(UTC+09:00) Seoul", "Asia/Seoul" }, - { "(UTC+09:00) Yakutsk", "Asia/Yakutsk" }, - { "(UTC+09:30) Adelaide", "Australia/Adelaide" }, - { "(UTC+09:30) Darwin", "Australia/Darwin" }, - { "(UTC+10:00) Brisbane", "Australia/Brisbane" }, - { "(UTC+10:00) Canberra, Melbourne, Sydney", "Australia/Melbourne" }, - { "(UTC+10:00) Guam, Port Moresby", "Pacific/Guam" }, - { "(UTC+10:00) Hobart", "Australia/Hobart" }, - { "(UTC+10:00) Vladivostok", "Asia/Vladivostok" }, - { "(UTC+11:00) Magadan, Solomon Is., New Caledonia", "Asia/Magadan" }, - { "(UTC+12:00) Auckland, Wellington", "Pacific/Auckland" }, - { "(UTC+12:00) Fiji, Marshall Is.", "Pacific/Fiji" }, - { "(UTC+12:00) Petropavlovsk-Kamchatsky", "Asia/Kamchatka" }, - { "(UTC+13:00) Nuku'alofa", "Pacific/Tongatapu" }, - { "(UTC-01:00) Azores", "Atlantic/Azores" }, - { "(UTC-01:00) Cape Verde Is.", "Atlantic/Cape_Verde" }, - { "(UTC-02:00) Mid-Atlantic", "Atlantic/South_Georgia" }, - { "(UTC-03:00) Brasilia", "America/Sao_Paulo" }, - { "(UTC-03:00) Buenos Aires", "America/Argentina/Buenos_Aires" }, - { "(UTC-03:00) Cayenne", "America/Cayenne" }, - { "(UTC-03:00) Greenland", "America/Godthab" }, - { "(UTC-03:00) Montevideo", "America/Montevideo" }, - { "(UTC-03:30) Newfoundland", "America/St_Johns" }, - { "(UTC-04:00) Asuncion", "America/Asuncion" }, - { "(UTC-04:00) Atlantic Time (Canada)", "America/Halifax" }, - { "(UTC-04:00) Georgetown, La Paz, San Juan", "America/Argentina/San_Juan" }, - { "(UTC-04:00) Manaus", "America/Manaus" }, - { "(UTC-04:00) Santiago", "America/Santiago" }, - { "(UTC-04:30) Caracas", "America/Caracas" }, - { "(UTC-05:00) Bogota, Lima, Quito", "America/Bogota" }, - { "(UTC-05:00) Eastern Time (US & Canada)", "America/New_York" }, - { "(UTC-05:00) Indiana (East)", "America/Indiana/Indianapolis" }, - { "(UTC-06:00) Central America", "America/Costa_Rica" }, - { "(UTC-06:00) Central Time (US & Canada)", "America/Chicago" }, - /* ? */ { "(UTC-06:00) Guadalajara, Mexico City, Monterrey", "America/Mexico_City" }, - { "(UTC-06:00) Saskatchewan", "Canada/Saskatchewan" }, - { "(UTC-07:00) Arizona", "America/Phoenix" }, - { "(UTC-07:00) Chihuahua, La Paz, Mazatlan", "America/Chihuahua" }, - { "(UTC-07:00) Mountain Time (US & Canada)", "America/Denver" }, - { "(UTC-08:00) Pacific Time (US & Canada)", "America/Los_Angeles" }, - { "(UTC-08:00) Tijuana, Baja California", "America/Tijuana" }, - { "(UTC-09:00) Alaska", "America/Anchorage" }, - { "(UTC-10:00) Hawaii", "Pacific/Honolulu" }, - { "(UTC-11:00) Midway Island, Samoa", "Pacific/Midway" }, - /* ? */ { "(UTC-12:00) International Date Line West", "Pacific/Apia" }, - - /* Extra zones I've seen in testing. Is there *no* sanity in - *any* part of what Microsoft does with time zones? */ - { "(GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London", "Europe/London" }, - { "Pacific Standard Time", "America/Los_Angeles" }, - }; - int i; - int offset = 0; - - if (!ewstz) - return NULL; - - /* Sometimes it says 'UTC'; sometimes 'GMT' */ - if (!strncmp(ewstz, "(GMT", 4)) - offset = 4; - - for (i = 0; i < ARRAY_SIZE(table); i++) { - if (!strcmp(ewstz + offset, table[i].exch + offset)) - return table[i].ical; - } - - fprintf(stderr, "Unrecognised TimeZone '%s'\n", ewstz); - return NULL; - -} - -icaltimezone *get_timezone(xmlNode *xml_node) -{ - icaltimezone *zone = NULL; - const char *ews_tzname = NULL; - const char *tzname = NULL; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - else if (!strcmp((char *)xml_node->name, "TimeZone")) - break; - } - if (!xml_node) { - fprintf(stderr, "Failed to find TimeZone element; falling back to UTC\n"); - return NULL; - } - - ews_tzname = (char *)xmlNodeGetContent(xml_node); - - /* FIXME: Look for manual timezone definitions in the XML and compare against - those first, before using the standard Windows ones */ - - tzname = ews_tz_to_ical(ews_tzname); - if (!tzname) - return NULL; - - zone = icaltimezone_get_builtin_timezone(tzname); - if (zone) - return zone; - - fprintf(stderr, "Failed to load ical timezone for '%s' (%s)\n", tzname, - ews_tzname); - return NULL; -} - -int get_baseoffset(xmlNode *xml_node, int *retval) -{ - const char *baseoffset; - struct icaldurationtype ofs; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - else if (!strcmp((char *)xml_node->name, "BaseOffset")) - break; - } - if (!xml_node) { - fprintf(stderr, " has no \n"); - return -1; - } - baseoffset = (const char *)xmlNodeGetContent(xml_node); - if (!baseoffset) { - fprintf(stderr, " is empty\n"); - return -1; - } - ofs = icaldurationtype_from_string(baseoffset); - if (icaldurationtype_is_bad_duration(ofs)) { - fprintf(stderr, "Failed to parse '%s'\n", baseoffset); - return -1; - } - if (ofs.is_neg) - *retval = ofs.minutes * 60; - else - *retval = -ofs.minutes * 60; - return 0; -} - -int process_relativeyearlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) -{ - const char *week = NULL, *month = NULL, *weekday = NULL; - int weeknr, monthnr, daynr; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "DaysOfWeek")) { - weekday = (char *)xmlNodeGetContent(xml_node); - } else if (!strcmp((char *)xml_node->name, "Month")) { - month = (char *)xmlNodeGetContent(xml_node); - } else if (!strcmp((char *)xml_node->name, "DayOfWeekIndex")) { - week = (char *)xmlNodeGetContent(xml_node); - } - } - if (!week || !month || !weekday) { - fprintf(stderr, "RelativeYearlyRecurrence missing essential fields (%s,%s,%s)\n", - week, month, weekday); - return -1; - } - monthnr = month_to_number(month); - if (!month) - return -1; - - weeknr = weekindex_to_ical(week); - if (!weeknr) - return -1; - - daynr = weekday_to_number(weekday, 10); - if (!daynr) - return -1; - - icalrecurrencetype_clear(ical_recur); - ical_recur->freq = ICAL_YEARLY_RECURRENCE; - ical_recur->by_month[0] = monthnr; - - - if (daynr < 8) { - if (weeknr > 0) - ical_recur->by_day[0] = daynr + (weeknr * 8); - else - ical_recur->by_day[0] = -8 - daynr; - } else if (daynr == 8) { /* Day */ - ical_recur->by_month_day[0] = weeknr; - } else if (daynr == 9) { /* Weekday */ - ical_recur->by_day[0] = 2; - ical_recur->by_day[1] = 3; - ical_recur->by_day[2] = 4; - ical_recur->by_day[3] = 5; - ical_recur->by_day[4] = 6; - ical_recur->by_set_pos[0] = weeknr; - } else if (daynr == 10) { /* WeekendDay */ - ical_recur->by_day[0] = 1; - ical_recur->by_day[1] = 7; - ical_recur->by_set_pos[0] = weeknr; - } - - return 0; -} -int process_absoluteyearlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) -{ - const char *day_of_month = NULL; - const char *month = NULL; - int daynr, monthnr; - - for (xml_node = xml_node->children; xml_node; - xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "DayOfMonth")) - day_of_month = (char *)xmlNodeGetContent(xml_node); - else if (!strcmp((char *)xml_node->name, "Month")) - month = (char *)xmlNodeGetContent(xml_node); - } - if (!day_of_month || !month) { - fprintf(stderr, "AbsoluteYearlyRecurrence missing essential fields (%s,%s)\n", - day_of_month, month); - return -1; - } - daynr = strtol(day_of_month, NULL, 10); - if (!daynr) { - fprintf(stderr, "Failed to parse DayOfMonth '%s'\n", day_of_month); - return -1; - } - monthnr = month_to_number(month); - if (!month) - return -1; - - icalrecurrencetype_clear(ical_recur); - ical_recur->freq = ICAL_YEARLY_RECURRENCE; - ical_recur->by_month[0] = monthnr; - ical_recur->by_month_day[0] = daynr; - return 0; -} - -int process_relativemonthlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) -{ - const char *week = NULL, *interval = NULL, *weekday = NULL; - int weeknr, intervalnr, daynr; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "DaysOfWeek")) { - weekday = (char *)xmlNodeGetContent(xml_node); - } else if (!strcmp((char *)xml_node->name, "Interval")) { - interval = (char *)xmlNodeGetContent(xml_node); - } else if (!strcmp((char *)xml_node->name, "DayOfWeekIndex")) { - week = (char *)xmlNodeGetContent(xml_node); - } - } - if (!week || !interval || !weekday) { - fprintf(stderr, "RelativeMonthlyRecurrence missing essential fields (%s,%s,%s)\n", - week, interval, weekday); - return -1; - } - intervalnr = strtol(interval, NULL, 10); - if (!intervalnr) { - fprintf(stderr, "Failed to parse Interval '%s'\n", interval); - return -1; - } - weeknr = weekindex_to_ical(week); - if (!weeknr) - return -1; - - daynr = weekday_to_number(weekday, 10); - if (!daynr) - return -1; - - icalrecurrencetype_clear(ical_recur); - ical_recur->freq = ICAL_MONTHLY_RECURRENCE; - ical_recur->interval = intervalnr; - if (daynr < 8) { - if (weeknr > 0) - ical_recur->by_day[0] = daynr + (weeknr * 8); - else - ical_recur->by_day[0] = -8 - daynr; - } else if (daynr == 8) { /* Day */ - ical_recur->by_month_day[0] = weeknr; - } else if (daynr == 9) { /* Weekday */ - ical_recur->by_day[0] = 2; - ical_recur->by_day[1] = 3; - ical_recur->by_day[2] = 4; - ical_recur->by_day[3] = 5; - ical_recur->by_day[4] = 6; - ical_recur->by_set_pos[0] = weeknr; - } else if (daynr == 10) { /* WeekendDay */ - ical_recur->by_day[0] = 1; - ical_recur->by_day[1] = 7; - ical_recur->by_set_pos[0] = weeknr; - } - - return 0; -} -int process_absolutemonthlyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) -{ - const char *interval = NULL, *monthday = NULL; - int intervalnr, monthdaynr; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "DayOfMonth")) { - monthday = (char *)xmlNodeGetContent(xml_node); - } else if (!strcmp((char *)xml_node->name, "Interval")) { - interval = (char *)xmlNodeGetContent(xml_node); - } - } - if (!interval || !monthday) { - fprintf(stderr, "AbsoluteMonthlyRecurrence missing essential fields (%s,%s)\n", - interval, monthday); - return -1; - } - intervalnr = strtol(interval, NULL, 10); - if (!intervalnr) { - fprintf(stderr, "Failed to parse Interval '%s'\n", interval); - return -1; - } - monthdaynr = strtol(monthday, NULL, 10); - if (!monthday) { - fprintf(stderr, "Failed to parse DayOfMonth '%s'\n", monthday); - return -1; - } - - icalrecurrencetype_clear(ical_recur); - ical_recur->freq = ICAL_MONTHLY_RECURRENCE; - ical_recur->interval = intervalnr; - ical_recur->by_month_day[0] = monthdaynr; - return 0; -} - -static int weekdays_to_recur_byday(const char *days, struct icalrecurrencetype *ical_recur) -{ - const char *space; - const char *day; - int count = 0; - int daynr; - - do { - space = strchr(days, ' '); - if (space) - day = g_strndup(days, space - days); - else - day = days; - - daynr = weekday_to_number(day, 7); - if (!daynr) - return -1; - - if (count == ICAL_BY_DAY_SIZE) { - fprintf(stderr, "Too many days in DaysOfWeek list\n"); - return -1; - } - - ical_recur->by_day[count++] = daynr; - if (space) { - free((char *)day); - days = space + 1; - } - } while (space); - return 0; -} - - - -int process_weeklyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) -{ - const char *interval = NULL, *weekday = NULL, *firstday = NULL; - int intervalnr, firstdaynr; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "DaysOfWeek")) { - weekday = (char *)xmlNodeGetContent(xml_node); - } else if (!strcmp((char *)xml_node->name, "Interval")) { - interval = (char *)xmlNodeGetContent(xml_node); - } else if (!strcmp((char *)xml_node->name, "FirstDayOfWeek")) { - firstday = (char *)xmlNodeGetContent(xml_node); - } - } - if (!interval || !weekday) { - fprintf(stderr, "WeeklyRecurrence missing essential fields (%s,%s)\n", - interval, weekday); - return -1; - } - intervalnr = strtol(interval, NULL, 10); - if (!intervalnr) { - fprintf(stderr, "Failed to parse Interval '%s'\n", interval); - return -1; - } - if (firstday) - firstdaynr = weekday_to_number(firstday, 7); - else - firstdaynr = 0; - - icalrecurrencetype_clear(ical_recur); - ical_recur->interval = intervalnr; - ical_recur->week_start = firstdaynr; - - if (weekdays_to_recur_byday(weekday, ical_recur)) - return -1; - - ical_recur->freq = ICAL_WEEKLY_RECURRENCE; - return 0; -} -int process_dailyrecurrence(xmlNode *xml_node, struct icalrecurrencetype *ical_recur) -{ - const char *interval = NULL; - int intervalnr; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "Interval")) { - interval = (char *)xmlNodeGetContent(xml_node); - } - } - if (!interval) { - fprintf(stderr, "DailyRecurrence missing essential fields (%s)\n", - interval); - return -1; - } - intervalnr = strtol(interval, NULL, 10); - if (!intervalnr) { - fprintf(stderr, "Failed to parse Interval '%s'\n", interval); - return -1; - } - - icalrecurrencetype_clear(ical_recur); - ical_recur->freq = ICAL_DAILY_RECURRENCE; - ical_recur->interval = intervalnr; - - return 0; -} -icalcomponent *process_timezone_rule(xmlNode *xml_node, - icalcomponent_kind kind, int *offset) -{ - icalcomponent *comp = icalcomponent_new(kind); - char *tzname; - icalproperty *prop; - - tzname = (char *)xmlGetProp(xml_node, (xmlChar *)"TimeZoneName"); - if (tzname) { - prop = icalproperty_new_tzname(tzname); - icalcomponent_add_property(comp, prop); - } - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - else if (!strcmp((char *)xml_node->name, "Offset")) { - char *ofs_string = (char *)xmlNodeGetContent(xml_node); - struct icaldurationtype ofs = icaldurationtype_from_string(ofs_string); - if (ofs.is_neg) - *offset += ofs.minutes * 60; - else - *offset -= ofs.minutes * 60; - - prop = icalproperty_new_tzoffsetto(*offset); - icalcomponent_add_property(comp, prop); - } else if (!strcmp((char *)xml_node->name, "RelativeYearlyRecurrence")) { - struct icalrecurrencetype ical_recur; - - if (process_relativeyearlyrecurrence(xml_node, &ical_recur)) - return NULL; - prop = icalproperty_new_rrule(ical_recur); - icalcomponent_add_property(comp, prop); - } else if (!strcmp((char *)xml_node->name, "AbsoluteDate")) { - /* Are there really timezones which change on the same date - every year? */ - fprintf(stderr, "Don't know how to handle AbsoluteDate for timezone change: '%s'\n", - xmlNodeGetContent(xml_node)); - } else if (!strcmp((char *)xml_node->name, "Time")) { - struct icaltimetype dtstart; - char *time_string = (char *)xmlNodeGetContent(xml_node); - - if (strlen(time_string) != 8 || time_string[2] != ':' || - time_string[5] != ':') { - fprintf(stderr, "Cannot parse dst change time '%s'\n", - time_string); - return NULL; - } - memset(&dtstart, 0, sizeof(dtstart)); - dtstart.year = 1900; - dtstart.month = 1; - dtstart.day = 1; - dtstart.hour = strtol(time_string, NULL, 10); - dtstart.minute = strtol(time_string + 3, NULL, 10); - dtstart.second = strtol(time_string + 6, NULL, 10); - - prop = icalproperty_new_dtstart(dtstart); - icalcomponent_add_property(comp, prop); - } - } - return comp; -} - -icaltimezone *get_meeting_timezone(xmlNode *xml_node) -{ - icalcomponent *comp = NULL, *dst_zone = NULL, *std_zone = NULL; - icalproperty *prop; - icaltimezone *z; - const char *tzname = NULL; - - int std_offset, dst_offset; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - else if (!strcmp((char *)xml_node->name, "MeetingTimeZone")) - break; - } - if (!xml_node) - return NULL; - comp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT); - - tzname = (const char *)xmlGetProp(xml_node, (xmlChar *)"TimeZoneName"); - - prop = icalproperty_new_tzid(tzname); - icalcomponent_add_property(comp, prop); - - if (get_baseoffset(xml_node, &std_offset)) - return NULL; - dst_offset = std_offset; - - for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { - if (xml_node->type != XML_ELEMENT_NODE) - continue; - if (!strcmp((char *)xml_node->name, "BaseOffset")) - continue; - else if (!strcmp((char *)xml_node->name, "Standard")) { - std_zone = process_timezone_rule(xml_node, - ICAL_XSTANDARD_COMPONENT, - &std_offset); - } else if (!strcmp((char *)xml_node->name, "Daylight")) { - dst_zone = process_timezone_rule(xml_node, - ICAL_XDAYLIGHT_COMPONENT, - &dst_offset); - } else { - fprintf(stderr, "Unknown element in MeetingTimeZone: %s\n", - xml_node->name); - } - } - - if (std_zone && dst_zone) { - prop = icalproperty_new_tzoffsetfrom(dst_offset); - icalcomponent_add_property(std_zone, prop); - - prop = icalproperty_new_tzoffsetfrom(std_offset); - icalcomponent_add_property(dst_zone, prop); - - icalcomponent_add_component(comp, std_zone); - icalcomponent_add_component(comp, dst_zone); - } else { - if (!std_zone) { - std_zone = icalcomponent_new(ICAL_XSTANDARD_COMPONENT); - - prop = icalproperty_new_tzoffsetto(std_offset); - icalcomponent_add_property(std_zone, prop); - - prop = icalproperty_new_tzoffsetfrom(std_offset); - icalcomponent_add_property(std_zone, prop); - } - icalcomponent_add_component(comp, std_zone); - } - - z = icaltimezone_new(); - icaltimezone_set_component(z, comp); - return z; -} diff --git a/ews_syncfolder.c b/ews_syncfolder.c index 63a4cb4..b671945 100644 --- a/ews_syncfolder.c +++ b/ews_syncfolder.c @@ -2,8 +2,25 @@ #include #include #include +#include #include #include +#include +#define ITEM_CREATE 1 +#define ITEM_DELETE 2 +#define ITEM_UPDATE 3 + +extern icalcomponent *ews_calitem_to_ical(xmlNode *xml_node); + +struct item_change { + struct item_change *next; + int type; + char itemid[0]; +}; + +int process_changes(xmlNode *node, struct item_change **changes); +int fetch_xml_item(SoupSession *sess, char *url, const char *itemid, + const char *xml_filename, const char *ics_filename); struct ews_auth { const char *username; @@ -29,6 +46,11 @@ int main(int argc, char **argv) struct ews_auth auth; char *url; char *responseclass; + struct item_change *changes = NULL; + gchar *syncstate = NULL; + gboolean last_in_range = TRUE; + char *statefilename; + gsize length; if (argc != 4) { usage: @@ -49,6 +71,9 @@ int main(int argc, char **argv) url = argv[1]; + statefilename = g_build_filename (g_get_home_dir() , ".ews-syncstate", NULL); + g_file_get_contents(statefilename, &syncstate, &length, NULL); + sess = soup_session_sync_new_with_options(SOUP_SESSION_USE_NTLM, TRUE, NULL); g_signal_connect (sess, "authenticate", G_CALLBACK(souptest_authenticate), &auth); @@ -83,6 +108,10 @@ int main(int argc, char **argv) child = xmlNewChild (node, messages_ns, (xmlChar *)"SyncFolderId", NULL); xmlNewProp(xmlNewChild(child, types_ns, (xmlChar *)"DistinguishedFolderId", NULL), (xmlChar *)"Id", (xmlChar *)"calendar"); + if (syncstate) + child = xmlNewTextChild (node, messages_ns, (xmlChar *)"SyncState", + (xmlChar *)syncstate); + child = xmlNewTextChild (node, messages_ns, (xmlChar *)"MaxChangesReturned", (xmlChar *)"5"); @@ -170,7 +199,262 @@ int main(int argc, char **argv) responseclass); exit(1); } - fprintf(stderr, "WRITE ME\n"); + for (node = node->children; node; node = node->next) { + if (node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)node->name, "LastItemInRange")) { + const char *truth = (const char *)xmlNodeGetContent(node); + if (!strcmp(truth, "true")) + last_in_range = TRUE; + else if (!strcmp(truth, "false")) + last_in_range = FALSE; + else { + fprintf(stderr, "Invalid value for : %s\n", + truth); + exit(1); + } + } else if (!strcmp((char *)node->name, "SyncState")) { + syncstate = (char *)xmlNodeGetContent(node); + } else if (!strcmp((char *)node->name, "Changes")) { + if (process_changes(node, &changes)) + exit(1); + } + } + xmlFreeDoc(doc); + + while (changes) { + struct item_change *this = changes; + char *xml_filename = g_strdup_printf("%s/ews-sync/%s.xml", + g_get_home_dir(), this->itemid); + char *ics_filename = g_strdup_printf("%s/ews-sync/%s.ics", + g_get_home_dir(), this->itemid); + if (this->type == ITEM_DELETE) { + unlink(xml_filename); + unlink(ics_filename); + } else { + if (fetch_xml_item(sess, url, this->itemid, + xml_filename, ics_filename)) + return -1; + } + changes = this->next; + free(this); + } + g_file_set_contents(statefilename, syncstate, strlen(syncstate), NULL); + return 0; +} +int fetch_xml_item(SoupSession *sess, char *url, const char *itemid, + const char *xml_filename, const char *ics_filename) +{ + SoupMessage *msg; + xmlDoc *doc; + xmlNode *node, *child; + xmlNs *soap_ns, *types_ns, *messages_ns; + xmlOutputBuffer *buf; + int status; + char *responseclass; + icalcomponent *calcomp; + char *outbuf; + + msg = soup_message_new("POST", url); + + soup_message_headers_append (msg->request_headers, + "User-Agent", "libews/0.1"); + + doc = xmlNewDoc((xmlChar *) "1.0"); + node = xmlNewDocNode(doc, NULL, (xmlChar *)"Envelope", NULL); + xmlDocSetRootElement(doc, node); + + soap_ns = xmlNewNs (node, (xmlChar *)"http://schemas.xmlsoap.org/soap/envelope/", + (xmlChar *)"soap"); + xmlSetNs(node, soap_ns); + types_ns = xmlNewNs (node, (xmlChar *)"http://schemas.microsoft.com/exchange/services/2006/types", + (xmlChar *)"types"); + node = xmlNewChild(node, soap_ns, (xmlChar *)"Body", NULL); + node = xmlNewChild(node, NULL, (xmlChar *)"GetItem", NULL); + + messages_ns = xmlNewNs (node, (xmlChar *)"http://schemas.microsoft.com/exchange/services/2006/messages", NULL); + xmlSetNs(node, messages_ns); + child = xmlNewChild (node, messages_ns, (xmlChar *)"ItemShape", NULL); + xmlNewTextChild(child, types_ns, (xmlChar *)"BaseShape", (xmlChar *)"AllProperties"); + xmlNewTextChild(child, types_ns, (xmlChar *)"BodyType", (xmlChar *)"Text"); + child = xmlNewChild (node, messages_ns, (xmlChar *)"ItemIds", NULL); + xmlNewProp(xmlNewChild(child, types_ns, (xmlChar *)"ItemId", NULL), + (xmlChar *)"Id", (xmlChar *)itemid); + + + buf = xmlAllocOutputBuffer(NULL); + xmlNodeDumpOutput(buf, doc, xmlDocGetRootElement(doc), 0, 1, NULL); + xmlOutputBufferFlush(buf); + + soup_message_set_request(msg, "text/xml", SOUP_MEMORY_COPY, + (gchar *)buf->buffer->content, + buf->buffer->use); + + status = soup_session_send_message(sess, msg); + + xmlOutputBufferClose (buf); + xmlFreeDoc (doc); + + if (status != 200) { + fprintf(stderr, "Unexpected response from server: %d\n", status); + exit(1); + } + g_file_set_contents(xml_filename, msg->response_body->data, + msg->response_body->length, NULL); + printf("Got XML file %s\n", itemid); + doc = xmlReadMemory (msg->response_body->data, msg->response_body->length, + "syncresponse.xml", NULL, 0); + if (!doc) { + fprintf(stderr, "Failed to parse autodiscover response XML\n"); + exit(1); + } + node = xmlDocGetRootElement(doc); + if (strcmp((char *)node->name, "Envelope")) { + fprintf(stderr, "Failed to find SOAP element\n"); + exit (1); + } + for (node = node->children; node; node = node->next) { + if (node->type == XML_ELEMENT_NODE && + !strcmp((char *)node->name, "Body")) + break; + } + if (!node) { + fprintf(stderr, "Failed to find SOAP element\n"); + exit (1); + } + for (node = node->children; node; node = node->next) { + if (node->type == XML_ELEMENT_NODE && + !strcmp((char *)node->name, "GetItemResponse")) + break; + } + if (!node) { + fprintf(stderr, "Failed to find element\n"); + exit (1); + } + for (node = node->children; node; node = node->next) { + if (node->type == XML_ELEMENT_NODE && + !strcmp((char *)node->name, "ResponseMessages")) + break; + } + if (!node) { + fprintf(stderr, "Failed to find element\n"); + exit (1); + } + for (node = node->children; node; node = node->next) { + if (node->type == XML_ELEMENT_NODE && + !strcmp((char *)node->name, "GetItemResponseMessage")) + break; + } + responseclass = (char *)xmlGetProp(node, (xmlChar *)"ResponseClass"); + if (!strcmp(responseclass, "Error")) { + for (node = node->children; node; node = node->next) { + if (node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)node->name, "MessageText")) { + fprintf(stderr, "Server returned error: %s\n", + xmlNodeGetContent(node)); + } else if (!strcmp((char *)node->name, "ResponseCode")) { + fprintf(stderr, "Response code: %s\n", + xmlNodeGetContent(node)); + } + } + exit(1); + } else if (strcmp(responseclass, "Success")) { + fprintf(stderr, "Unknown response class '%s' from server\n", + responseclass); + exit(1); + } + + for (node = node->children; node; node = node->next) { + if (node->type == XML_ELEMENT_NODE && + !strcmp((char *)node->name, "Items")) + break; + } + if (!node) { + fprintf(stderr, "Failed to find element\n"); + exit (1); + } + + for (node = node->children; node; node = node->next) { + if (node->type == XML_ELEMENT_NODE && + !strcmp((char *)node->name, "CalendarItem")) + break; + } + if (!node) { + fprintf(stderr, "Failed to find element\n"); + exit (1); + } + + calcomp = ews_calitem_to_ical(node); + + outbuf = icalcomponent_as_ical_string_r(calcomp); + + g_file_set_contents(ics_filename, outbuf, strlen(outbuf), NULL); + printf("Got ICS file %s\n", ics_filename); + free(outbuf); + + + xmlFreeDoc(doc); + + + return 0; +} +int process_changes(xmlNode *node, struct item_change **changes) +{ + xmlNode *node2; + + for (node = node->children; node; node = node->next) { + int type; + char *itemid; + struct item_change *new_change; + + if (node->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)node->name, "Create")) + type = ITEM_CREATE; + else if (!strcmp((char *)node->name, "Delete")) + type = ITEM_DELETE; + else if (!strcmp((char *)node->name, "Update")) + type = ITEM_UPDATE; + else { + fprintf(stderr, "Unknown change type '%s'\n", + node->name); + return -1; + } + for (node2 = node->children; node2; node2 = node2->next) { + if (node2->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)node2->name, "CalendarItem")) + break; + } + if (!node2) { + fprintf(stderr, "<%s> node has no child\n", + node->name); + return -1; + } + for (node2 = node2->children; node2; node2 = node2->next) { + if (node2->type != XML_ELEMENT_NODE) + continue; + if (!strcmp((char *)node2->name, "ItemId")) + break; + } + if (!node2) { + fprintf(stderr, "<%s> node has no child\n", + node->name); + return -1; + } + itemid = (char *)xmlGetProp(node2, (xmlChar *)"Id"); + + new_change = malloc(sizeof(*new_change) + strlen(itemid) + 1); + if (!new_change) { + fprintf(stderr, "Out of memory\n"); + return -1; + } + new_change->next = *changes; + new_change->type = type; + strcpy(new_change->itemid, itemid); + *changes = new_change; + } return 0; }