ical: workaround for libical 1.0 builtin timezone change

libical 1.0 started to return VTIMEZONE definitions with multiple
absolute transition times instead of RRULEs. This causes problems when
exchanging data with peers (see
https://sourceforge.net/p/freeassociation/bugs/95/).

In SyncEvolution, this affected sending an event using New Zealand
time in vCalendar 1.0 format to a phone, because the internal,
out-dated definition of the time zone in libsynthesis was used as
fallback when loading RRULE-based timezone definitions from libical
failed (see "[SyncEvolution] Some events showing wrong time on
phone"). It might also affect exchanging data with CalDAV peers (not
tested).

The workaround is to include the original code from libical from
before the change in SyncEvolution and override
icaltimezone_get_component() with a version that uses the original
timezone loading code. This does not fix cases where other code causes
libical itself to load a timezone, but for libsynthesis it is good
enough because it does the loading early when no other code should
have used libical.

The downside is that now we need to maintain the RRULE heuristics and
ensure that they actually work. Copying libical/src/test/timezones.c
would be useful for that.

Long-term it would be good to enhance libical itself such that it can
return a VTIMEZONE with suitable RRULEs matching a specific event,
point in time or time range.
This commit is contained in:
Patrick Ohly 2014-03-19 13:13:50 +01:00
parent 5d43c5d7e3
commit 9d13210d0b
5 changed files with 145 additions and 7 deletions

View File

@ -145,6 +145,18 @@ AC_ARG_ENABLE(evolution-compatibility,
[build executables which only call Evolution via dlopen/dlsym: this avoids all hard dependencies on EDS shared objects, but might lead to crashes when their ABI changes; use --enable-evolution-compatibility=ical to enable a weaker mode where linking is done normally and only libical.so.0/1 enum differences are worked around (allows patching resulting executables to use either of these two)]),
enable_evolution_compatibility="$enableval", enable_evolution_compatibility="no")
AC_ARG_ENABLE(internal-icaltz,
AS_HELP_STRING([--disable-internal-icaltz],
[libical 1.0 updated its system zone data parsing code so that it produces VTIMEZONEs which are unsuitable for syncing. SyncEvolution ships with a copy of the older code and uses it by default.]),
[enable_icaltz_util="$enableval"
test "$enable_icaltz_util" = "yes" || test "$enable_icaltz_util" = "no" || AC_ERROR([invalid value of --disable-internal-icaltz: $enableval])],
enable_icaltz_util="yes")
if test "$enable_icaltz_util" = "yes"; then
AC_DEFINE(ENABLE_ICALTZ_UTIL, [1], [use internal icaltz-util.c])
AC_DEFINE(DISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY, [1], [use libical's icaltzutil_get_zone_directory()])
fi
AM_CONDITIONAL([ENABLE_ICALTZ_UTIL], [test "$enable_icaltz_util" = "yes"])
AC_ARG_ENABLE(developer-mode,
AS_HELP_STRING([--enable-developer-mode],
[The dynamic loadble backend libraries is loaded from current build directory instead of the standard library path]),
@ -163,6 +175,9 @@ AC_SUBST(MODIFY_SYNCCOMPARE)
AC_CHECK_HEADERS(signal.h dlfcn.h)
# For icaltz-util.c
AC_CHECK_HEADERS(byteswap.h endian.h sys/endian.h unistd.h stdint.h)
# cppunit-config is used even when both unit tests and integration tests are disabled.
AC_PATH_PROG([CPPUNIT_CONFIG], [cppunit-config], [no])
@ -984,16 +999,19 @@ if test "$need_glib" = "yes"; then
BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $GLIB_CFLAGS $GTHREAD_CFLAGS $GOBJECT_CFLAGS"
fi
dnl use libical if and only if required by some backend
if test "$need_ical" = "yes"; then
dnl check for libical if needed by backend or icaltz-util.
if test "$need_ical" = "yes" || test "$enable_icaltz_util" = "yes"; then
PKG_CHECK_MODULES(LIBICAL, libical,
[true],
[PKG_CHECK_MODULES(LIBICAL, libecal-1.2)])
fi
dnl Use libical in eds_abi_wrapper if and only if required by some backend.
if test "$need_ical" = "yes"; then
AC_DEFINE(ENABLE_ICAL, 1, [libical in use])
fi
AM_CONDITIONAL([ENABLE_ICAL], [test "$need_ical" = "yes"])
# Check for Qt if some backend needs it.
if test "$need_qt_modules"; then
AT_WITH_QT([-gui $need_qt_modules],

View File

@ -95,6 +95,13 @@ CORE_LDADD = $(SYNCEVOLUTION_LDADD) src/syncevo/libsyncevolution.la $(GLIB_LIBS)
CORE_DEP = $(SYNCEVOLUTION_DEP) src/syncevo/libsyncevolution.la $(SYNTHESIS_DEP)
CORE_LD_FLAGS = -Wl,-uSyncEvolution_Module_Version -Wl,--export-dynamic $(CPPUNIT_LDFLAGS) $(ADDITIONAL_LDFLAGS)
if ENABLE_ICALTZ_UTIL
# Force inclusion of our own icaltz-util.o into binaries even though
# we do not call the icaltzutil_fetch_timezone directly ourself.
# That way it is there if or when libical needs it.
CORE_LD_FLAGS += -Wl,-usyncevo_fetch_timezone
endif
# put link to static c++ library into current directory, needed if compiling with --enable-static-c++
src/libstdc++.a :
$(AM_V_GEN)path=`$(CXX) $(CORE_LDADD) $(LD_FLAGS) -print-file-name=src/libstdc++.a` && ln -s $$path .

View File

@ -39,6 +39,12 @@
#include <stdio.h>
#endif
#ifdef EVOLUTION_ICAL_COMPATIBILITY
# include "eds_abi_wrapper.h"
# else
# include <libical/icaltimezone.h>
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
@ -155,7 +161,9 @@ typedef struct
long int change;
} leap;
#ifndef EVOLUTION_ICAL_COMPATIBILITY
extern const char *ical_tzid_prefix;
#endif
static int
decode (const void *ptr)
@ -332,7 +340,9 @@ icaltzutil_fetch_timezone (const char *location)
icaltimetype dtstart, icaltime;
struct icalrecurrencetype ical_recur;
const char *basedir;
/* if (!location) return NULL; */
basedir = icaltzutil_get_zone_directory();
if (!basedir) {
icalerror_set_errno (ICAL_FILE_ERROR);
@ -563,6 +573,92 @@ error:
return tz_comp;
}
/*
* What follows is copied and slightly simplified (not thread-safe!)
* code from icaltimezone.c.
*
* This is necessary because otherwise, when libsynthesis.so calls
* icaltimezone_get_component(), libical.1.so uses its own builtin
* icaltzutil_fetch_timezone(). Apparently it exports that without
* looking it up via the dynamic linker itself. Therefore we have to
* redirect the original icaltimezone_get_component() call.
*/
static void
icaltimezone_load_builtin_timezone (icaltimezone *zone)
{
icalcomponent *subcomp;
const char *location;
/* If the location isn't set, it isn't a builtin timezone. */
location = icaltimezone_get_location(zone);
if (!location || !location[0])
return;
subcomp = icaltzutil_fetch_timezone (location);
if (!subcomp) {
icalerror_set_errno(ICAL_PARSE_ERROR);
return;
}
icaltimezone_set_component(zone, subcomp);
}
#undef icaltimezone_get_component
icalcomponent *icaltimezone_get_component(icaltimezone *zone)
{
icalcomponent *comp;
/* If this is a floating time, without a timezone, return NULL. */
if (!zone)
return NULL;
/*
* Without this check, icaltimezone_set_component() in
* icaltimezone_load_builtin_timezone() will discard the
* already loaded component of builtin timezones and replace
* it with the new one, so there is no leak. It's just
* inefficient.
*
* However, this method also gets called for non-internal
* timezones which were created from a VTIMEZONE and in
* that case not using the existing component is wrong.
*
* Hack: duplicate the internal _icaltimezone struct (from
* icaltimezoneimpl.h).
*/
struct _my_icaltimezone {
char *tzid;
char *location;
char *tznames;
double latitude;
double longitude;
icalcomponent *component;
};
comp = ((struct _my_icaltimezone *)zone)->component;
if (!comp) {
icaltimezone_load_builtin_timezone (zone);
comp = ((struct _my_icaltimezone *)zone)->component;
}
return comp;
}
/*
* For including the .o file in binaries via -Wl,-usyncevo_fetch_timezone.
* We cannot use -Wl,-uicaltzutil_fetch_timezone because that gets satisfied by
* libical itself.
*/
int syncevo_fetch_timezone;
/*
* Avoid lazy resolution of the methods that we export. client-test otherwise
* ends up calling the libical version of the methods despite having its own
* copy compiled into the executable, at least on Ubuntu Saucy and Trusty.
*/
/* void *syncevo_fetch_timezone_p = &icaltzutil_fetch_timezone; */
/* void *syncevo_get_component_p = &icaltimezone_get_component; */
#ifdef ICALTZ_UTIL_MAIN
int
main (int argc, char *argv [])

View File

@ -23,9 +23,11 @@
#ifndef ICALTZUTIL_H
#define ICALTZUTIL_H
#include <stdio.h>
#include "libical/icaltime.h"
#include "libical/icalcomponent.h"
#include <libical/ical.h>
#ifdef __cplusplus
extern "C" {
#endif
#if defined(sun) && defined(__SVR4)
#define ZONES_TAB_SYSTEM_FILENAME "tab/zone_sun.tab"
@ -33,7 +35,14 @@
#define ZONES_TAB_SYSTEM_FILENAME "zone.tab"
#endif
#if !defined(EVOLUTION_COMPATIBILITY) || !defined(ENABLE_ICAL)
const char *icaltzutil_get_zone_directory (void);
extern const char *ical_tzid_prefix;
#endif
icalcomponent *icaltzutil_fetch_timezone (const char *location);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -148,6 +148,14 @@ src_syncevo_sources += \
src/syncevo/icalstrdup.h
endif
if ENABLE_ICALTZ_UTIL
src_syncevo_sources += \
src/syncevo/icaltz-util.c \
src/syncevo/icaltz-util.h
if !ENABLE_EVOLUTION_COMPATIBILITY
src_syncevo_ldadd += $(LIBICAL_LIBS)
endif
endif
src_syncevo_libsyncevolution_includedir= $(includedir)/syncevo
src_syncevo_libsyncevolution_include_HEADERS = \