From d2944d27466f6d3d281ebcad9e9ab672b3305103 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Tue, 10 Nov 2020 21:04:14 +0100 Subject: [PATCH 2/2] icaltimezone.c: Add compatibility code around read of builtin zones by TZID Different versions of libical could you different TZID prefix for builtin zones and yet another TZID when it was configured to use internal zone data. The libical was unable to match builtin time zone by the "old" TZID when it had been reconfigured. This change adds compatibility code to make sure the time zones can be read regardless how the libical had been configured. --- src/libical/icaltimezone.c | 96 +++++++++++++++++++++++--------------- src/test/regression.c | 52 +++++++++++++++++++++ 2 files changed, 111 insertions(+), 37 deletions(-) diff --git a/src/libical/icaltimezone.c b/src/libical/icaltimezone.c index 36403f96..33f6b79d 100644 --- a/src/libical/icaltimezone.c +++ b/src/libical/icaltimezone.c @@ -63,6 +63,17 @@ static pthread_mutex_t changes_mutex = PTHREAD_MUTEX_INITIALIZER; #define BUILTIN_TZID_PREFIX_LEN 256 #define BUILTIN_TZID_PREFIX "/freeassociation.sourceforge.net/" +/* Known prefixes from the old versions of libical */ +static struct _compat_tzids { + const char *tzid; + int slashes; +} glob_compat_tzids[] = { + { "/freeassociation.sourceforge.net/Tzfile/", 3 }, + { "/freeassociation.sourceforge.net/", 2 }, + { "/citadel.org/", 3 }, /* Full TZID for this can be: "/citadel.org/20190914_1/" */ + { NULL, -1 } +}; + /* The prefix to be used for tzid's generated from system tzdata */ static char s_ical_tzid_prefix[BUILTIN_TZID_PREFIX_LEN] = {0}; @@ -1232,6 +1243,25 @@ int icaltimezone_set_component(icaltimezone *zone, icalcomponent *comp) return icaltimezone_get_vtimezone_properties(zone, comp); } +static const char *skip_slashes(const char *text, int n_slashes) +{ + const char *pp; + int num_slashes = 0; + + if(!text) + return NULL; + + for (pp = text; *pp; pp++) { + if(*pp == '/') { + num_slashes++; + if(num_slashes == n_slashes) + return pp + 1; + } + } + + return NULL; +} + const char *icaltimezone_get_display_name(icaltimezone *zone) { const char *display_name; @@ -1250,24 +1280,8 @@ const char *icaltimezone_get_display_name(icaltimezone *zone) at the end of it. */ if (display_name && !strncmp(display_name, tzid_prefix, strlen(tzid_prefix))) { -#if defined(USE_BUILTIN_TZDATA) - /* XXX The code below makes assumptions about the prefix - which don't even jive with our default declared up top */ - /* Get the location, which is after the 3rd '/' char. */ - const char *p; - int num_slashes = 0; - - for (p = display_name; *p; p++) { - if (*p == '/') { - num_slashes++; - if (num_slashes == 3) - return p + 1; - } - } -#else /* Skip past our prefix */ display_name += strlen(tzid_prefix); -#endif } } @@ -1452,11 +1466,9 @@ icaltimezone *icaltimezone_get_builtin_timezone_from_offset(int offset, const ch icaltimezone *icaltimezone_get_builtin_timezone_from_tzid(const char *tzid) { -#if defined(USE_BUILTIN_TZDATA) - int num_slashes = 0; -#endif const char *p, *zone_tzid, *tzid_prefix; icaltimezone *zone; + int compat = 0; if (!tzid || !tzid[0]) return NULL; @@ -1467,35 +1479,45 @@ icaltimezone *icaltimezone_get_builtin_timezone_from_tzid(const char *tzid) tzid_prefix = icaltimezone_tzid_prefix(); /* Check that the TZID starts with our unique prefix. */ - if (strncmp(tzid, tzid_prefix, strlen(tzid_prefix))) - return NULL; - -#if defined(USE_BUILTIN_TZDATA) - /* XXX The code below makes assumptions about the prefix - which don't even jive with our default declared up top */ - /* Get the location, which is after the 3rd '/' character. */ - for (p = tzid; *p; p++) { - if (*p == '/') { - num_slashes++; - if (num_slashes == 3) + if (strncmp(tzid, tzid_prefix, strlen(tzid_prefix))) { + int ii; + + for (ii = 0; glob_compat_tzids[ii].tzid; ii++) { + if(strncmp(tzid, glob_compat_tzids[ii].tzid, strlen(glob_compat_tzids[ii].tzid)) == 0) { + p = skip_slashes(tzid, glob_compat_tzids[ii].slashes); + if(p) { + zone = icaltimezone_get_builtin_timezone(p); + /* Do not recheck the TZID matches exactly, it does not, because + fallbacking with the compatibility timezone prefix here. */ + return zone; + } break; + } } - } - if (num_slashes != 3) return NULL; + } - p++; -#else /* Skip past our prefix */ p = tzid + strlen(tzid_prefix); -#endif + + /* Special-case "/freeassociation.sourceforge.net/Tzfile/", because it shares prefix with BUILTIN_TZID_PREFIX */ + if (strcmp(tzid_prefix, BUILTIN_TZID_PREFIX) == 0 && + strncmp(p, "Tzfile/", 7) == 0) { + p += 7; + compat = 1; + } /* Now we can use the function to get the builtin timezone from the location string. */ zone = icaltimezone_get_builtin_timezone(p); - if (!zone) - return NULL; + if (!zone || compat) + return zone; + +#if defined(USE_BUILTIN_TZDATA) + if (use_builtin_tzdata) + return zone; +#endif /* Check that the builtin TZID matches exactly. We don't want to return a different version of the VTIMEZONE. */ diff --git a/src/test/regression.c b/src/test/regression.c index 922557fb..b9e51bb3 100644 --- a/src/test/regression.c +++ b/src/test/regression.c @@ -4572,6 +4572,57 @@ void test_icalcomponent_normalize(void) str_is("Normalized components match", calStr1, calStr2); } +static void test_builtin_compat_tzid (void) +{ + struct _cases { + const char *name; + const char *tzid; + int should_match; + } cases[] = { + { "Matches current TZID", "/freeassociation.sourceforge.net/Europe/London", 1 }, + { "Matches Tzfile compat TZID", "/freeassociation.sourceforge.net/Tzfile/Europe/London", 1 }, + { "Matches citadel.org TZID", "/citadel.org/20190914_1/Europe/London", 1 }, + { "Does not match custom TZID", "/custom/test/tzid/Europe/London", 0 }, + { NULL, NULL, 0 } + }; + int ii, jj; + icaltimezone *tz; + + for (jj = 0; jj < 2; jj++) { + if(jj == 1) { + icaltimezone_set_tzid_prefix(""); + icaltimezone_free_builtin_timezones(); + } + + tz = icaltimezone_get_builtin_timezone("Europe/London"); + + for (ii = 0; cases[ii].tzid; ii++) { + icaltimezone *zone; + + if(cases[ii].should_match) { + zone = icaltimezone_get_builtin_timezone_from_tzid(cases[ii].tzid); + ok(cases[ii].name, (zone == tz)); + if(zone != tz && VERBOSE) { + printf("Returned builtin zone (%s) doesn't match expected zone for TZID '%s'\n", + zone ? icaltimezone_get_location(zone) : "NULL", + cases[ii].tzid); + } + } else { + zone = icaltimezone_get_builtin_timezone_from_tzid(cases[ii].tzid); + ok(cases[ii].name, (!zone)); + if(zone != NULL && VERBOSE) { + printf("Returned builtin zone (%s), but it should fail for TZID '%s'\n", + icaltimezone_get_location(zone), + cases[ii].tzid); + } + } + } + } + + icaltimezone_set_tzid_prefix(TESTS_TZID_PREFIX); + icaltimezone_free_builtin_timezones(); +} + int main(int argc, char *argv[]) { #if !defined(HAVE_UNISTD_H) @@ -4709,6 +4760,7 @@ int main(int argc, char *argv[]) test_run("Test icalvalue_decode_ical_string", test_icalvalue_decode_ical_string, do_test, do_header); test_run("Test icalcomponent_normalize", test_icalcomponent_normalize, do_test, do_header); + test_run("Test builtin compat TZID", test_builtin_compat_tzid, do_test, do_header); /** OPTIONAL TESTS go here... **/ -- 2.26.2