]> www.infradead.org Git - users/dwmw2/ews-sync.git/commitdiff
Get C syncfolder almost working. Lots of cleanups needed.
authorDavid Woodhouse <dwmw2@infradead.org>
Mon, 19 Jul 2010 23:36:20 +0000 (00:36 +0100)
committerDavid Woodhouse <dwmw2@infradead.org>
Mon, 19 Jul 2010 23:36:20 +0000 (00:36 +0100)
Surely the XML crap can be nicer than this?

Makefile
calitem_to_ical.c [new file with mode: 0644]
ews2ical.c
ews_syncfolder.c

index c61fe84b615c98d22f8c8cd4d932a04c36d400ec..3b384d3057c3434ac8f937b9d366400b76eaf8cb 100644 (file)
--- 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 (file)
index 0000000..3293190
--- /dev/null
@@ -0,0 +1,1147 @@
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#include <libical/icaltime.h>
+#include <libical/icalduration.h>
+#include <libical/icaltimezone.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+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...
+               <t:Start>2010-07-20T11:00:00Z</t:Start>
+               <t:End>2010-07-20T11:00:00Z</t:End>
+
+          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 <Recurrence> 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, "<html", 5)) {
+#if 0 /* XX: libical doesn't seem to escape this properly */
+               icalproperty *prop;
+               icalparameter *param;
+
+               prop = icalproperty_new_x(body);
+               icalproperty_set_x_name(prop, "X-ALT-DESC");
+
+               param = icalparameter_new_fmttype("text/html");
+               icalproperty_add_parameter(prop, param);
+               icalcomponent_add_property(comp, prop);
+#endif
+               /* FIXME: html2text */
+               icalcomponent_set_description(comp, body);
+       } else {
+               icalcomponent_set_description(comp, body);
+       }
+
+
+       return 0;
+}
+
+int process_subject(icalcomponent *comp, xmlNode *xml_node)
+{
+       const char *subject = (char *)xmlNodeGetContent(xml_node);
+
+       if (!subject)
+               return -1;
+
+       icalcomponent_set_summary(comp, subject);
+       return 0;
+}
+
+
+static int month_to_number(const char *month)
+{
+       static char *months[] = {
+               "January", "February", "March", "April", "May", "June", "July", 
+               "August", "September", "October", "November", "December"
+       };
+       int monthnr;
+       for (monthnr = 0; monthnr < 12; monthnr++) {
+               if (!strcmp(month, months[monthnr]))
+                       return monthnr + 1;
+       }
+
+       fprintf(stderr, "Unrecognised month name '%s'\n", month);
+       return 0;
+}
+static int weekday_to_number(const char *day, int accept)
+{
+       static char *days[] = {
+               "Sunday", "Monday", "Tuesday", "Wednesday",
+               "Thursday", "Friday", "Saturday",
+               "Day", "Weekday", "WeekendDay"
+       };
+       int daynr;
+       for (daynr = 0; daynr < accept; daynr++) {
+               if (!strcmp(day, days[daynr]))
+                       return daynr + 1;
+       }
+
+       fprintf(stderr, "Unrecognised day name '%s'\n", day);
+       return 0;
+}
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+
+static int weekindex_to_ical(const char *week)
+{
+       static struct {
+               char *exch;
+               int week;
+       } table[] = {
+               { "First", 1 },
+               { "Second", 2 },
+               { "Third", 3 },
+               { "Fourth", 4 },
+               { "Last", -1 }
+       };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(table); i++) {
+               if (!strcmp(week, table[i].exch))
+                       return table[i].week;
+       }
+       fprintf(stderr, "Unrecognised DayOfWeekIndex '%s'\n", week);
+       return 0;
+}      
+               
+int process_recurrence(icalcomponent *comp, xmlNode *xml_node, icaltimezone *zone)
+{
+       struct icalrecurrencetype ical_recur;
+       char *end_date = NULL, *nr_occurrences = NULL;
+       icalproperty *prop;
+       xmlNode *xml_node2;
+
+       ical_recur.freq = ICAL_NO_RECURRENCE;
+       if (!zone)
+               fprintf(stderr, "Recurrence with no recognised TimeZone. Hope this is an all-day event\n");
+               
+       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, "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, "<MeetingTimeZone> has no <BaseOffset>\n");
+               return -1;
+       }
+       baseoffset = (const char *)xmlNodeGetContent(xml_node);
+       if (!baseoffset) {
+               fprintf(stderr, "<BaseOffset> is empty\n");
+               return -1;
+       }
+       ofs = icaldurationtype_from_string(baseoffset);
+       if (icaldurationtype_is_bad_duration(ofs)) {
+               fprintf(stderr, "Failed to parse <BaseOffset> '%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;
+}
index 56ae8d2a52ec8445a4f4617578bde40b45bee475..532fdc9aeaec35bd202a3feb7a0d4765dac46f1a 100644 (file)
 
 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...
-               <t:Start>2010-07-20T11:00:00Z</t:Start>
-               <t:End>2010-07-20T11:00:00Z</t:End>
-
-          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 <Recurrence> 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, "<html", 5)) {
-#if 0 /* XX: libical doesn't seem to escape this properly */
-               icalproperty *prop;
-               icalparameter *param;
-
-               prop = icalproperty_new_x(body);
-               icalproperty_set_x_name(prop, "X-ALT-DESC");
-
-               param = icalparameter_new_fmttype("text/html");
-               icalproperty_add_parameter(prop, param);
-               icalcomponent_add_property(comp, prop);
-#endif
-               /* FIXME: html2text */
-               icalcomponent_set_description(comp, body);
-       } else {
-               icalcomponent_set_description(comp, body);
-       }
-
-
-       return 0;
-}
-
-int process_subject(icalcomponent *comp, xmlNode *xml_node)
-{
-       const char *subject = (char *)xmlNodeGetContent(xml_node);
-
-       if (!subject)
-               return -1;
-
-       icalcomponent_set_summary(comp, subject);
-       return 0;
-}
-
-
-static int month_to_number(const char *month)
-{
-       static char *months[] = {
-               "January", "February", "March", "April", "May", "June", "July", 
-               "August", "September", "October", "November", "December"
-       };
-       int monthnr;
-       for (monthnr = 0; monthnr < 12; monthnr++) {
-               if (!strcmp(month, months[monthnr]))
-                       return monthnr + 1;
-       }
-
-       fprintf(stderr, "Unrecognised month name '%s'\n", month);
-       return 0;
-}
-static int weekday_to_number(const char *day, int accept)
-{
-       static char *days[] = {
-               "Sunday", "Monday", "Tuesday", "Wednesday",
-               "Thursday", "Friday", "Saturday",
-               "Day", "Weekday", "WeekendDay"
-       };
-       int daynr;
-       for (daynr = 0; daynr < accept; daynr++) {
-               if (!strcmp(day, days[daynr]))
-                       return daynr + 1;
-       }
-
-       fprintf(stderr, "Unrecognised day name '%s'\n", day);
-       return 0;
-}
-#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
-
-static int weekindex_to_ical(const char *week)
-{
-       static struct {
-               char *exch;
-               int week;
-       } table[] = {
-               { "First", 1 },
-               { "Second", 2 },
-               { "Third", 3 },
-               { "Fourth", 4 },
-               { "Last", -1 }
-       };
-       int i;
-
-       for (i = 0; i < ARRAY_SIZE(table); i++) {
-               if (!strcmp(week, table[i].exch))
-                       return table[i].week;
-       }
-       fprintf(stderr, "Unrecognised DayOfWeekIndex '%s'\n", week);
-       return 0;
-}      
-               
-int process_recurrence(icalcomponent *comp, xmlNode *xml_node, icaltimezone *zone)
-{
-       struct icalrecurrencetype ical_recur;
-       char *end_date = NULL, *nr_occurrences = NULL;
-       icalproperty *prop;
-       xmlNode *xml_node2;
-
-       ical_recur.freq = ICAL_NO_RECURRENCE;
-       if (!zone)
-               fprintf(stderr, "Recurrence with no recognised TimeZone. Hope this is an all-day event\n");
-               
-       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, "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, "<MeetingTimeZone> has no <BaseOffset>\n");
-               return -1;
-       }
-       baseoffset = (const char *)xmlNodeGetContent(xml_node);
-       if (!baseoffset) {
-               fprintf(stderr, "<BaseOffset> is empty\n");
-               return -1;
-       }
-       ofs = icaldurationtype_from_string(baseoffset);
-       if (icaldurationtype_is_bad_duration(ofs)) {
-               fprintf(stderr, "Failed to parse <BaseOffset> '%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;
-}
index 63a4cb4278e590aa0a9615aa0db8d1067d8fec81..b67194525b7cbdef6e87a1072ed1862d849cd875 100644 (file)
@@ -2,8 +2,25 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 #include <libxml/parser.h>
 #include <libxml/tree.h>
+#include <libical/icalcomponent.h>
+#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 <LastItemInRange>: %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 <Envelope> 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 <Body> 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 <GetItemResponse> 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 <ResponseMessages> 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 <Items> 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 <CalendarItem> 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 <CalendarItem> 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 <ItemId> 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;
 }