2 * Copyright (C) 2008 Novell, Inc.
4 * Authors: Patrick Ohly <patrick.ohly@gmx.de>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of version 2 of the GNU Lesser General Public
8 * License as published by the Free Software Foundation.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #ifndef HANDLE_LIBICAL_MEMORY
21 # define HANDLE_LIBICAL_MEMORY 1
23 #include <libical/ical.h>
25 #ifdef LIBICAL_MEMFIXES
26 /* avoid dependency on icalstrdup.h, works when compiling as part of EDS >= 2.22 */
27 # define ical_strdup(_x) (_x)
29 /* use icalstrdup.h to get runtime detection of memory fix patch */
30 # include "libical/icalstrdup.h"
33 #include "e-cal-check-timezones.h"
34 #include <libecal/e-cal.h>
39 * Matches a location to a system timezone definition via a fuzzy
40 * search and returns the matching TZID, or NULL if none found.
42 * Currently simply strips a suffix introduced by a hyphen,
43 * as in "America/Denver-(Standard)".
45 static const char *e_cal_match_location(const char *location)
52 icomp = icaltimezone_get_builtin_timezone (location);
54 return icaltimezone_get_tzid(icomp);
57 /* try a bit harder by stripping trailing suffix */
58 tail = strrchr(location, '-');
59 len = tail ? (tail - location) : strlen(location);
60 buffer = g_malloc(len + 1);
63 memcpy(buffer, location, len);
65 icomp = icaltimezone_get_builtin_timezone (buffer);
68 return icaltimezone_get_tzid(icomp);
77 * matches a TZID against the system timezone definitions
78 * and returns the matching TZID, or NULL if none found
80 const char *e_cal_match_tzid(const char *tzid)
84 size_t len = strlen(tzid);
88 * Try without any trailing spaces/digits: they might have been added
89 * by e_cal_check_timezones() in order to distinguish between
90 * different incompatible definitions. At that time mapping
91 * to system time zones must have failed, but perhaps now
92 * we have better code and it succeeds...
96 isdigit(tzid[eostr])) {
100 isspace(tzid[eostr])) {
103 if (eostr + 1 < len) {
104 char *strippedtzid = g_strndup(tzid, eostr + 1);
106 systzid = e_cal_match_tzid(strippedtzid);
107 g_free(strippedtzid);
115 * old-style Evolution: /softwarestudio.org/Olson_20011030_5/America/Denver
117 * jump from one slash to the next and check whether the remainder
118 * is a known location; start with the whole string (just in case)
120 for (location = tzid;
121 location && location[0];
122 location = strchr(location + 1, '/')) {
123 systzid = e_cal_match_location(location[0] == '/' ?
131 /* TODO: lookup table for Exchange TZIDs */
136 static void patch_tzids(icalcomponent *subcomp,
141 if (icalcomponent_isa(subcomp) != ICAL_VTIMEZONE_COMPONENT) {
142 icalproperty *prop = icalcomponent_get_first_property(subcomp,
145 icalparameter *param = icalproperty_get_first_parameter(prop,
146 ICAL_TZID_PARAMETER);
152 tzid = g_strdup(icalparameter_get_tzid(param));
154 if (!g_hash_table_lookup_extended(mapping,
156 (gpointer *)&oldtzid,
157 (gpointer *)&newtzid)) {
158 /* Corresponding VTIMEZONE not seen before! */
159 newtzid = e_cal_match_tzid(tzid);
162 icalparameter_set_tzid(param, newtzid);
164 param = icalproperty_get_next_parameter(prop,
165 ICAL_TZID_PARAMETER);
167 prop = icalcomponent_get_next_property(subcomp,
175 static void addsystemtz(gpointer key,
179 const char *tzid = key;
180 icalcomponent *comp = user_data;
183 zone = icaltimezone_get_builtin_timezone_from_tzid(tzid);
185 icalcomponent_add_component(comp,
186 icalcomponent_new_clone(icaltimezone_get_component(zone)));
191 * e_cal_check_timezones:
192 * @comp: a VCALENDAR containing a list of
193 * VTIMEZONE and arbitrary other components, in
194 * arbitrary order: these other components are
195 * modified by this call
196 * @comps: a list of icalcomponent instances which
197 * also have to be patched; may be NULL
198 * @tzlookup: a callback function which is called to retrieve
199 * a calendar's VTIMEZONE definition; the returned
200 * definition is *not* freed by e_cal_check_timezones()
201 * (to be compatible with e_cal_get_timezone());
202 * NULL indicates that no such timezone exists
203 * or an error occurred
204 * @custom: an arbitrary pointer which is passed through to
205 * the tzlookup function
206 * @error: an error description in case of a failure
208 * This function cleans up VEVENT, VJOURNAL, VTODO and VTIMEZONE
209 * items which are to be imported into Evolution.
211 * Using VTIMEZONE definitions is problematic because they cannot be
212 * updated properly when timezone definitions change. They are also
213 * incomplete (for compatibility reason only one set of rules for
214 * summer saving changes can be included, even if different rules
215 * apply in different years). This function looks for matches of the
216 * used TZIDs against system timezones and replaces such TZIDs with
217 * the corresponding system timezone. This works for TZIDs containing
218 * a location (found via a fuzzy string search) and for Outlook TZIDs
219 * (via a hard-coded lookup table).
221 * Some programs generate broken meeting invitations with TZID, but
222 * without including the corresponding VTIMEZONE. Importing such
223 * invitations unchanged causes problems later on (meeting displayed
224 * incorrectly, #e_cal_get_component_as_string fails). The situation
225 * where this occurred in the past (found by a SyncEvolution user) is
226 * now handled via the location based mapping.
228 * If this mapping fails, this function also deals with VTIMEZONE
229 * conflicts: such conflicts occur when the calendar already contains
230 * an old VTIMEZONE definition with the same TZID, but different
231 * summer saving rules. Replacing the VTIMEZONE potentially breaks
232 * displaying of old events, whereas not replacing it breaks the new
233 * events (the behavior in Evolution <= 2.22.1).
235 * The way this problem is resolved is by renaming the new VTIMEZONE
236 * definition until the TZID is unique. A running count is appended to
237 * the TZID. All items referencing the renamed TZID are adapted
240 * Return value: TRUE if successful, FALSE otherwise.
242 gboolean e_cal_check_timezones(icalcomponent *comp,
244 icaltimezone *(*tzlookup)(const char *tzid,
250 gboolean success = TRUE;
251 icalcomponent *subcomp = NULL;
252 icaltimezone *zone = icaltimezone_new();
253 char *key = NULL, *value = NULL;
255 char *zonestr = NULL;
259 /** a hash from old to new tzid; strings dynamically allocated */
260 GHashTable *mapping = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
262 /** a hash of all system time zone IDs which have to be added; strings are shared with mapping hash */
263 GHashTable *systemtzids = g_hash_table_new(g_str_hash, g_str_equal);
267 if (!mapping || !zone) {
271 /* iterate over all VTIMEZONE definitions */
272 subcomp = icalcomponent_get_first_component(comp,
273 ICAL_VTIMEZONE_COMPONENT);
275 if (icaltimezone_set_component(zone, subcomp)) {
277 tzid = g_strdup(icaltimezone_get_tzid(zone));
279 const char *newtzid = e_cal_match_tzid(tzid);
281 /* matched against system time zone */
283 key = g_strdup(tzid);
289 value = g_strdup(newtzid);
294 g_hash_table_insert(mapping, key, value);
295 g_hash_table_insert(systemtzids, value, NULL);
299 zonestr = ical_strdup(icalcomponent_as_ical_string(subcomp));
301 /* check for collisions with existing timezones */
304 counter < 100 /* sanity limit */;
306 icaltimezone *existing_zone;
310 value = g_strdup_printf("%s %d", tzid, counter);
312 existing_zone = tzlookup(counter ? value : tzid,
315 if (!existing_zone) {
323 buffer = ical_strdup(icalcomponent_as_ical_string(icaltimezone_get_component(existing_zone)));
326 char *fulltzid = g_strdup_printf("TZID:%s", value);
327 size_t baselen = strlen("TZID:") + strlen(tzid);
328 size_t fulllen = strlen(fulltzid);
331 * Map TZID with counter suffix back to basename.
333 tzidprop = strstr(buffer, fulltzid);
335 memmove(tzidprop + baselen,
337 strlen(tzidprop + fulllen) + 1);
344 * If the strings are identical, then the
345 * VTIMEZONE definitions are identical. If
346 * they are not identical, then VTIMEZONE
347 * definitions might still be semantically
348 * correct and we waste some space by
349 * needlesly duplicating the VTIMEZONE. This
350 * is expected to occur rarely (if at all) in
353 if (!strcmp(zonestr, buffer)) {
359 /* does not exist, nothing to do */
361 /* timezone renamed */
362 icalproperty *prop = icalcomponent_get_first_property(subcomp,
365 icalproperty_set_value_from_string(prop, value, "NO");
366 prop = icalcomponent_get_next_property(subcomp,
370 key = g_strdup(tzid);
371 g_hash_table_insert(mapping, key, value);
379 subcomp = icalcomponent_get_next_component(comp,
380 ICAL_VTIMEZONE_COMPONENT);
384 * now replace all TZID parameters in place
386 subcomp = icalcomponent_get_first_component(comp,
390 * Leave VTIMEZONE unchanged, iterate over properties of
393 * Note that no attempt is made to remove unused VTIMEZONE
394 * definitions. That would just make the code more complex for
395 * little additional gain. However, newly used time zones are
398 patch_tzids (subcomp, mapping);
399 subcomp = icalcomponent_get_next_component(comp,
403 for (l = comps; l; l = l->next) {
404 patch_tzids (l->data, mapping);
408 * add system time zones that we mapped to: adding them ensures
409 * that the VCALENDAR remains consistent
411 g_hash_table_foreach(systemtzids, addsystemtz, comp);
415 /* set gerror for "out of memory" if possible, otherwise abort via g_error() */
416 *error = g_error_new(E_CALENDAR_ERROR, E_CALENDAR_STATUS_OTHER_ERROR, "out of memory");
418 g_error("e_cal_check_timezones(): out of memory, cannot proceed - sorry!");
421 /* gerror should have been set already */
425 g_hash_table_destroy(mapping);
428 g_hash_table_destroy(systemtzids);
431 icaltimezone_free(zone, 1);
443 * e_cal_tzlookup_ecal:
444 * @custom: must be a valid ECal pointer
446 * An implementation of the tzlookup callback which clients
447 * can use. Calls #e_cal_get_timezone.
449 icaltimezone *e_cal_tzlookup_ecal(const char *tzid,
453 ECal *ecal = (ECal *)custom;
454 icaltimezone *zone = NULL;
456 if (e_cal_get_timezone(ecal, tzid, &zone, error)) {
457 g_assert(*error == NULL);
461 if ((*error)->domain == E_CALENDAR_ERROR &&
462 (*error)->code == E_CALENDAR_STATUS_OBJECT_NOT_FOUND) {
464 * we had to trigger this error to check for the timezone existance,
465 * clear it and return NULL
467 g_clear_error(error);
474 * e_cal_tzlookup_icomp:
475 * @custom: must be a icalcomponent pointer which contains
476 * either a VCALENDAR with VTIMEZONEs or VTIMEZONES
479 * An implementation of the tzlookup callback which backends
480 * like the file backend can use. Searches for the timezone
481 * in the component list.
483 icaltimezone *e_cal_tzlookup_icomp(const char *tzid,
487 icalcomponent *icomp = (icalcomponent *)custom;
489 return icalcomponent_get_timezone(icomp, (char *)tzid);