Coverage Report

Created: 2023-03-26 07:19

/src/libical/src/libical/icaltimezone.c
Line
Count
Source (jump to first uncovered line)
1
/*======================================================================
2
 FILE: icaltimezone.c
3
 CREATOR: Damon Chaplin 15 March 2001
4
5
 SPDX-FileCopyrightText: 2001, Damon Chaplin <damon@ximian.com>
6
7
 SPDX-License-Identifier: LGPL-2.1-only OR MPL-2.0
8
9
======================================================================*/
10
//krazy:excludeall=cpp
11
12
#ifdef HAVE_CONFIG_H
13
#include <config.h>
14
#endif
15
16
#include "icaltimezone.h"
17
#include "icaltimezoneimpl.h"
18
#include "icalarray.h"
19
#include "icalerror.h"
20
#include "icalparser.h"
21
#include "icalmemory.h"
22
#include "icaltz-util.h"
23
24
#include <ctype.h>
25
#include <stddef.h>     /* for ptrdiff_t */
26
#include <stdlib.h>
27
#include <limits.h>
28
29
#if ICAL_SYNC_MODE == ICAL_SYNC_MODE_PTHREAD
30
#include <pthread.h>
31
#if defined(PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP)
32
// It seems the same thread can attempt to lock builtin_mutex multiple times
33
// (at least when using builtin tzdata), so make it builtin_mutex recursive:
34
static pthread_mutex_t builtin_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
35
#else
36
static pthread_mutex_t builtin_mutex = PTHREAD_MUTEX_INITIALIZER;
37
#endif
38
// To avoid use-after-free in multithreaded applications when accessing icaltimezone::changes
39
static pthread_mutex_t changes_mutex = PTHREAD_MUTEX_INITIALIZER;
40
#endif
41
42
#if defined(_WIN32)
43
#if !defined(_WIN32_WCE)
44
#include <mbstring.h>
45
#endif
46
#include <windows.h>
47
#endif
48
49
/** This is the toplevel directory where the timezone data is installed in. */
50
0
#define ZONEINFO_DIRECTORY      PACKAGE_DATA_DIR "/zoneinfo"
51
52
/** The prefix we use to uniquely identify TZIDs.
53
    It must begin and end with forward slashes.
54
 */
55
0
#define BUILTIN_TZID_PREFIX_LEN 256
56
0
#define BUILTIN_TZID_PREFIX     "/freeassociation.sourceforge.net/"
57
58
/* Known prefixes from the old versions of libical */
59
static const struct _compat_tzids {
60
    const char *tzid;
61
    int slashes;
62
} glob_compat_tzids[] = {
63
    { "/freeassociation.sourceforge.net/Tzfile/", 3 },
64
    { "/freeassociation.sourceforge.net/", 2 },
65
    { "/citadel.org/", 3 }, /* Full TZID for this can be: "/citadel.org/20190914_1/" */
66
    { NULL, -1 }
67
};
68
69
/* The prefix to be used for tzid's generated from system tzdata */
70
static ICAL_GLOBAL_VAR char s_ical_tzid_prefix[BUILTIN_TZID_PREFIX_LEN] = {0};
71
72
/** This is the filename of the file containing the city names and
73
    coordinates of all the builtin timezones. */
74
0
#define ZONES_TAB_FILENAME      "zones.tab"
75
76
/** This is the number of years of extra coverage we do when expanding
77
    the timezone changes. */
78
0
#define ICALTIMEZONE_EXTRA_COVERAGE     5
79
80
#if (SIZEOF_ICALTIME_T > 4)
81
/** Arbitrarily go up to 1000th anniversary of Gregorian calendar, since
82
    64-bit icaltime_t values get us up to the tm_year limit of 2+ billion years. */
83
0
#define ICALTIMEZONE_MAX_YEAR           2582
84
#else
85
/** This is the maximum year we will expand to, since 32-bit icaltime_t values
86
    only go up to the start of 2038. */
87
#define ICALTIMEZONE_MAX_YEAR           2037
88
#endif
89
90
typedef struct _icaltimezonechange icaltimezonechange;
91
92
struct _icaltimezonechange
93
{
94
    int utc_offset;
95
    /**< The offset to add to UTC to get local time, in seconds. */
96
97
    int prev_utc_offset;
98
    /**< The offset to add to UTC, before this change, in seconds. */
99
100
    int year;                   /**< Actual year, e.g. 2001. */
101
    int month;                  /**< 1 (Jan) to 12 (Dec). */
102
    int day;
103
    int hour;
104
    int minute;
105
    int second;
106
    /**< The time that the change came into effect, in UTC.
107
       Note that the prev_utc_offset applies to this local time,
108
       since we haven't changed to the new offset yet. */
109
110
    int is_daylight;
111
    /**< Whether this is STANDARD or DAYLIGHT time. */
112
};
113
114
/** An array of icaltimezones for the builtin timezones. */
115
static ICAL_GLOBAL_VAR icalarray *builtin_timezones = NULL;
116
117
/** This is the special UTC timezone, which isn't in builtin_timezones. */
118
static ICAL_GLOBAL_VAR icaltimezone utc_timezone = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
119
120
static ICAL_GLOBAL_VAR char *zone_files_directory = NULL;
121
122
#if defined(USE_BUILTIN_TZDATA)
123
static ICAL_GLOBAL_VAR int use_builtin_tzdata = 1;
124
#else
125
static ICAL_GLOBAL_VAR int use_builtin_tzdata = 0;
126
#endif
127
128
static void icaltimezone_reset(icaltimezone *zone);
129
static void icaltimezone_expand_changes(icaltimezone *zone, int end_year);
130
static int icaltimezone_compare_change_fn(const void *elem1, const void *elem2);
131
132
static size_t icaltimezone_find_nearby_change(icaltimezone *zone, icaltimezonechange *change);
133
134
static void icaltimezone_adjust_change(icaltimezonechange *tt,
135
                                       int days, int hours, int minutes, int seconds);
136
137
static void icaltimezone_init(icaltimezone *zone);
138
139
/** @brief Gets the TZID, LOCATION/X-LIC-LOCATION, and TZNAME properties from
140
 * the VTIMEZONE component and places them in the icaltimezone.
141
 *
142
 * @returns 1 on success, or 0 if the TZID can't be found.
143
 */
144
static int icaltimezone_get_vtimezone_properties(icaltimezone *zone, icalcomponent *component)
145
#if defined(THREAD_SANITIZER)
146
__attribute__((no_sanitize("thread")))
147
#endif
148
;
149
150
static void icaltimezone_load_builtin_timezone(icaltimezone *zone)
151
#if defined(THREAD_SANITIZER)
152
__attribute__((no_sanitize("thread")))
153
#endif
154
;
155
156
static void icaltimezone_ensure_coverage(icaltimezone *zone, int end_year);
157
158
static void icaltimezone_init_builtin_timezones(void);
159
160
static void icaltimezone_parse_zone_tab(void);
161
162
static char *icaltimezone_load_get_line_fn(char *s, size_t size, void *data);
163
164
static void format_utc_offset(int utc_offset, char *buffer, size_t buffer_size);
165
static const char *get_zone_directory_builtin(void);
166
167
static void icaltimezone_builtin_lock(void)
168
1
{
169
1
#if ICAL_SYNC_MODE == ICAL_SYNC_MODE_PTHREAD
170
1
    pthread_mutex_lock(&builtin_mutex);
171
1
#endif
172
1
}
173
174
static void icaltimezone_builtin_unlock(void)
175
1
{
176
1
#if ICAL_SYNC_MODE == ICAL_SYNC_MODE_PTHREAD
177
1
    pthread_mutex_unlock(&builtin_mutex);
178
1
#endif
179
1
}
180
181
static void icaltimezone_changes_lock(void)
182
0
{
183
0
#if ICAL_SYNC_MODE == ICAL_SYNC_MODE_PTHREAD
184
0
    pthread_mutex_lock(&changes_mutex);
185
0
#endif
186
0
}
187
188
static void icaltimezone_changes_unlock(void)
189
0
{
190
0
#if ICAL_SYNC_MODE == ICAL_SYNC_MODE_PTHREAD
191
0
    pthread_mutex_unlock(&changes_mutex);
192
0
#endif
193
0
}
194
195
const char *icaltimezone_tzid_prefix(void)
196
0
{
197
0
    if (s_ical_tzid_prefix[0] == '\0') {
198
0
        strncpy(s_ical_tzid_prefix, BUILTIN_TZID_PREFIX, BUILTIN_TZID_PREFIX_LEN-1);
199
0
    }
200
0
    return s_ical_tzid_prefix;
201
0
}
202
203
icaltimezone *icaltimezone_new(void)
204
0
{
205
0
    icaltimezone *zone;
206
207
0
    zone = (icaltimezone *) icalmemory_new_buffer(sizeof(icaltimezone));
208
0
    if (!zone) {
209
0
        icalerror_set_errno(ICAL_NEWFAILED_ERROR);
210
0
        return NULL;
211
0
    }
212
213
0
    icaltimezone_init(zone);
214
215
0
    return zone;
216
0
}
217
218
icaltimezone *icaltimezone_copy(icaltimezone *originalzone)
219
0
{
220
0
    icaltimezone *zone;
221
222
0
    zone = (icaltimezone *) icalmemory_new_buffer(sizeof(icaltimezone));
223
0
    if (!zone) {
224
0
        icalerror_set_errno(ICAL_NEWFAILED_ERROR);
225
0
        return NULL;
226
0
    }
227
228
0
    memcpy(zone, originalzone, sizeof(icaltimezone));
229
0
    if (zone->tzid != NULL) {
230
0
        zone->tzid = icalmemory_strdup(zone->tzid);
231
0
    }
232
0
    if (zone->location != NULL) {
233
0
        zone->location = icalmemory_strdup(zone->location);
234
0
    }
235
0
    if (zone->tznames != NULL) {
236
0
        zone->tznames = icalmemory_strdup(zone->tznames);
237
0
    }
238
239
0
    icaltimezone_changes_lock();
240
0
    if (zone->changes != NULL) {
241
0
        zone->changes = icalarray_copy(zone->changes);
242
0
    }
243
0
    icaltimezone_changes_unlock();
244
245
    /* Let the caller set the component because then they will
246
       know to be careful not to free this reference twice. */
247
0
    zone->component = NULL;
248
249
0
    return zone;
250
0
}
251
252
void icaltimezone_free(icaltimezone *zone, int free_struct)
253
113k
{
254
113k
    icaltimezone_reset(zone);
255
113k
    if (free_struct)
256
0
        icalmemory_free_buffer(zone);
257
113k
}
258
259
/** @brief Resets the icaltimezone to the initial state, freeing most of the
260
 * fields.
261
 */
262
static void icaltimezone_reset(icaltimezone *zone)
263
113k
{
264
113k
    if (zone->tzid)
265
113k
        icalmemory_free_buffer(zone->tzid);
266
267
113k
    if (zone->location)
268
389
        icalmemory_free_buffer(zone->location);
269
270
113k
    if (zone->tznames)
271
1.16k
        icalmemory_free_buffer(zone->tznames);
272
273
113k
    if (zone->component)
274
113k
        icalcomponent_free(zone->component);
275
276
    //    icaltimezone_changes_lock();
277
113k
    if (zone->changes) {
278
0
        icalarray_free(zone->changes);
279
0
        zone->changes = NULL;
280
0
    }
281
    //    icaltimezone_changes_unlock();
282
283
113k
    icaltimezone_init(zone);
284
113k
}
285
286
/** @brief Initializes an icaltimezone. */
287
static void icaltimezone_init(icaltimezone *zone)
288
228k
{
289
228k
    zone->tzid = NULL;
290
228k
    zone->location = NULL;
291
228k
    zone->tznames = NULL;
292
228k
    zone->latitude = 0.0;
293
228k
    zone->longitude = 0.0;
294
228k
    zone->component = NULL;
295
228k
    zone->builtin_timezone = NULL;
296
228k
    zone->end_year = 0;
297
228k
    zone->changes = NULL;
298
228k
}
299
300
/** @brief Gets the TZID, LOCATION/X-LIC-LOCATION and TZNAME properties of
301
 * the VTIMEZONE component and stores them in the icaltimezone.
302
 *
303
 * @returns 1 on success, or 0 if the TZID can't be found.
304
 *
305
 * Note that it expects the zone to be initialized or reset - it doesn't free
306
 * any old values.
307
 */
308
static int icaltimezone_get_vtimezone_properties(icaltimezone *zone, icalcomponent *component)
309
114k
{
310
114k
    icalproperty *prop;
311
114k
    const char *tzid;
312
313
114k
    prop = icalcomponent_get_first_property(component, ICAL_TZID_PROPERTY);
314
114k
    if (!prop)
315
1.04k
        return 0;
316
317
    /* A VTIMEZONE MUST have a TZID, or a lot of our code won't work. */
318
113k
    tzid = icalproperty_get_tzid(prop);
319
113k
    if (!tzid) {
320
0
        return 0;
321
0
    }
322
323
113k
    if (zone->tzid) {
324
0
        icalmemory_free_buffer(zone->tzid);
325
0
    }
326
113k
    zone->tzid = icalmemory_strdup(tzid);
327
328
113k
    if (zone->component) {
329
0
        icalcomponent_free(zone->component);
330
0
    }
331
113k
    zone->component = component;
332
333
113k
    if (zone->location) {
334
0
        icalmemory_free_buffer(zone->location);
335
0
    }
336
113k
    zone->location = icaltimezone_get_location_from_vtimezone(component);
337
338
113k
    if (zone->tznames) {
339
0
        icalmemory_free_buffer(zone->tznames);
340
0
    }
341
113k
    zone->tznames = icaltimezone_get_tznames_from_vtimezone(component);
342
343
113k
    return 1;
344
113k
}
345
346
char *icaltimezone_get_location_from_vtimezone(icalcomponent *component)
347
113k
{
348
113k
    icalproperty *prop;
349
113k
    const char *location;
350
113k
    const char *name;
351
352
113k
    prop = icalcomponent_get_first_property(component, ICAL_LOCATION_PROPERTY);
353
113k
    if (prop) {
354
195
        location = icalproperty_get_location(prop);
355
195
        if (location)
356
195
            return icalmemory_strdup(location);
357
195
    }
358
359
113k
    prop = icalcomponent_get_first_property(component, ICAL_X_PROPERTY);
360
114k
    while (prop) {
361
1.36k
        name = icalproperty_get_x_name(prop);
362
1.36k
        if (name && !strcasecmp(name, "X-LIC-LOCATION")) {
363
568
            location = icalproperty_get_x(prop);
364
568
            if (location)
365
194
                return icalmemory_strdup(location);
366
568
        }
367
1.17k
        prop = icalcomponent_get_next_property(component, ICAL_X_PROPERTY);
368
1.17k
    }
369
370
113k
    return NULL;
371
113k
}
372
373
char *icaltimezone_get_tznames_from_vtimezone(icalcomponent *component)
374
113k
{
375
113k
    icalcomponent *comp;
376
113k
    icalcomponent_kind type;
377
113k
    icalproperty *prop;
378
113k
    struct icaltimetype dtstart;
379
113k
    struct icaldatetimeperiodtype rdate;
380
113k
    const char *current_tzname;
381
113k
    const char *standard_tzname = NULL, *daylight_tzname = NULL;
382
113k
    struct icaltimetype standard_max_date, daylight_max_date;
383
113k
    struct icaltimetype current_max_date;
384
385
113k
    standard_max_date = icaltime_null_time();
386
113k
    daylight_max_date = icaltime_null_time();
387
388
    /* Step through the STANDARD & DAYLIGHT subcomponents. */
389
113k
    comp = icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT);
390
119k
    while (comp) {
391
5.89k
        type = icalcomponent_isa(comp);
392
5.89k
        if (type == ICAL_XSTANDARD_COMPONENT || type == ICAL_XDAYLIGHT_COMPONENT) {
393
4.04k
            current_max_date = icaltime_null_time();
394
4.04k
            current_tzname = NULL;
395
396
            /* Step through the properties. We want to find the TZNAME, and
397
               the largest DTSTART or RDATE. */
398
4.04k
            prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
399
14.0k
            while (prop) {
400
10.0k
                switch (icalproperty_isa(prop)) {
401
3.14k
                case ICAL_TZNAME_PROPERTY:
402
3.14k
                    current_tzname = icalproperty_get_tzname(prop);
403
3.14k
                    break;
404
405
914
                case ICAL_DTSTART_PROPERTY:
406
914
                    dtstart = icalproperty_get_dtstart(prop);
407
914
                    if (icaltime_compare(dtstart, current_max_date) > 0)
408
506
                        current_max_date = dtstart;
409
410
914
                    break;
411
412
4.92k
                case ICAL_RDATE_PROPERTY:
413
4.92k
                    rdate = icalproperty_get_rdate(prop);
414
4.92k
                    if (icaltime_compare(rdate.time, current_max_date) > 0)
415
2.24k
                        current_max_date = rdate.time;
416
417
4.92k
                    break;
418
419
1.04k
                default:
420
1.04k
                    break;
421
10.0k
                }
422
423
10.0k
                prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY);
424
10.0k
            }
425
426
4.04k
            if (current_tzname) {
427
2.77k
                if (type == ICAL_XSTANDARD_COMPONENT) {
428
1.49k
                    if (!standard_tzname ||
429
1.49k
                        icaltime_compare(current_max_date, standard_max_date) > 0) {
430
1.29k
                        standard_max_date = current_max_date;
431
1.29k
                        standard_tzname = current_tzname;
432
1.29k
                    }
433
1.49k
                } else {
434
1.28k
                    if (!daylight_tzname ||
435
1.28k
                        icaltime_compare(current_max_date, daylight_max_date) > 0) {
436
985
                        daylight_max_date = current_max_date;
437
985
                        daylight_tzname = current_tzname;
438
985
                    }
439
1.28k
                }
440
2.77k
            }
441
4.04k
        }
442
443
5.89k
        comp = icalcomponent_get_next_component(component, ICAL_ANY_COMPONENT);
444
5.89k
    }
445
446
    /* Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME
447
       strings, which is totally useless. So we return NULL in that case. */
448
113k
    if (standard_tzname && !strcmp(standard_tzname, "Standard Time"))
449
194
        return NULL;
450
451
    /* If both standard and daylight TZNAMEs were found, if they are the same
452
       we return just one, else we format them like "EST/EDT". */
453
113k
    if (standard_tzname && daylight_tzname) {
454
536
        size_t standard_len, daylight_len;
455
536
        char *tznames;
456
457
536
        if (!strcmp(standard_tzname, daylight_tzname))
458
242
            return icalmemory_strdup(standard_tzname);
459
460
294
        standard_len = strlen(standard_tzname);
461
294
        daylight_len = strlen(daylight_tzname);
462
294
        tznames = icalmemory_new_buffer(standard_len + daylight_len + 2);
463
294
        strcpy(tznames, standard_tzname);
464
294
        tznames[standard_len] = '/';
465
294
        strcpy(tznames + standard_len + 1, daylight_tzname);
466
294
        return tznames;
467
112k
    } else {
468
112k
        const char *tznames;
469
470
        /* If either of the TZNAMEs was found just return that, else NULL. */
471
112k
        tznames = standard_tzname ? standard_tzname : daylight_tzname;
472
112k
        return tznames ? icalmemory_strdup(tznames) : NULL;
473
112k
    }
474
113k
}
475
476
static void icaltimezone_ensure_coverage(icaltimezone *zone, int end_year)
477
0
{
478
    /* When we expand timezone changes we always expand at least up to this
479
       year, plus ICALTIMEZONE_EXTRA_COVERAGE. */
480
0
    static ICAL_GLOBAL_VAR int icaltimezone_minimum_expansion_year = -1;
481
482
0
    int changes_end_year;
483
484
0
    icaltimezone_load_builtin_timezone(zone);
485
486
0
    if (icaltimezone_minimum_expansion_year == -1) {
487
0
        struct icaltimetype today = icaltime_today();
488
489
0
        icaltimezone_minimum_expansion_year = today.year;
490
0
    }
491
492
0
    changes_end_year = end_year;
493
0
    if (changes_end_year < icaltimezone_minimum_expansion_year)
494
0
        changes_end_year = icaltimezone_minimum_expansion_year;
495
496
0
    changes_end_year += ICALTIMEZONE_EXTRA_COVERAGE;
497
498
0
    if (changes_end_year > ICALTIMEZONE_MAX_YEAR)
499
0
        changes_end_year = ICALTIMEZONE_MAX_YEAR;
500
501
0
    if (!zone->changes || zone->end_year < end_year)
502
0
        icaltimezone_expand_changes(zone, changes_end_year);
503
0
}
504
505
/* Hold the icaltimezone_changes_lock(); before calling this function */
506
static void icaltimezone_expand_changes(icaltimezone *zone, int end_year)
507
0
{
508
0
    icalarray *changes;
509
0
    icalcomponent *comp;
510
511
#if 0
512
    printf("\nExpanding changes for: %s to year: %i\n", zone->tzid, end_year);
513
#endif
514
515
0
    changes = icalarray_new(sizeof(icaltimezonechange), 32);
516
0
    if (!changes)
517
0
        return;
518
519
    /* Scan the STANDARD and DAYLIGHT subcomponents. */
520
0
    comp = icalcomponent_get_first_component(zone->component, ICAL_ANY_COMPONENT);
521
0
    while (comp) {
522
0
        icaltimezone_expand_vtimezone(comp, end_year, changes);
523
0
        comp = icalcomponent_get_next_component(zone->component, ICAL_ANY_COMPONENT);
524
0
    }
525
526
    /* Sort the changes. We may have duplicates but I don't think it will
527
       matter. */
528
0
    icalarray_sort(changes, icaltimezone_compare_change_fn);
529
530
0
    if (zone->changes) {
531
0
        icalarray_free(zone->changes);
532
0
        zone->changes = 0;
533
0
    }
534
535
0
    zone->changes = changes;
536
0
    zone->end_year = end_year;
537
0
}
538
539
void icaltimezone_expand_vtimezone(icalcomponent *comp, int end_year, icalarray *changes)
540
0
{
541
0
    icaltimezonechange change;
542
0
    icalproperty *prop;
543
0
    struct icaltimetype dtstart, occ;
544
0
    struct icalrecurrencetype rrule;
545
0
    icalrecur_iterator *rrule_iterator;
546
0
    struct icaldatetimeperiodtype rdate;
547
0
    int found_dtstart = 0, found_tzoffsetto = 0, found_tzoffsetfrom = 0;
548
0
    int has_rdate = 0, has_rrule = 0;
549
550
    /* First we check if it is a STANDARD or DAYLIGHT component, and
551
       just return if it isn't. */
552
0
    if (icalcomponent_isa(comp) == ICAL_XSTANDARD_COMPONENT) {
553
0
        change.is_daylight = 0;
554
0
    } else if (icalcomponent_isa(comp) == ICAL_XDAYLIGHT_COMPONENT) {
555
0
        change.is_daylight = 1;
556
0
    } else {
557
0
        return;
558
0
    }
559
560
    /* Step through each of the properties to find the DTSTART,
561
       TZOFFSETFROM and TZOFFSETTO. We can't expand recurrences here
562
       since we need these properties before we can do that. */
563
0
    prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
564
0
    while (prop) {
565
0
        switch (icalproperty_isa(prop)) {
566
0
        case ICAL_DTSTART_PROPERTY:
567
0
            dtstart = icalproperty_get_dtstart(prop);
568
0
            found_dtstart = 1;
569
0
            break;
570
0
        case ICAL_TZOFFSETTO_PROPERTY:
571
0
            change.utc_offset = icalproperty_get_tzoffsetto(prop);
572
            /*printf ("Found TZOFFSETTO: %i\n", change.utc_offset); */
573
0
            found_tzoffsetto = 1;
574
0
            break;
575
0
        case ICAL_TZOFFSETFROM_PROPERTY:
576
0
            change.prev_utc_offset = icalproperty_get_tzoffsetfrom(prop);
577
            /*printf ("Found TZOFFSETFROM: %i\n", change.prev_utc_offset); */
578
0
            found_tzoffsetfrom = 1;
579
0
            break;
580
0
        case ICAL_RDATE_PROPERTY:
581
0
            has_rdate = 1;
582
0
            break;
583
0
        case ICAL_RRULE_PROPERTY:
584
0
            has_rrule = 1;
585
0
            break;
586
0
        default:
587
            /* Just ignore any other properties. */
588
0
            break;
589
0
        }
590
591
0
        prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY);
592
0
    }
593
594
    /* Microsoft Outlook for Mac (and possibly other versions) will create
595
       timezones without a tzoffsetfrom property if it's a timezone that
596
       doesn't change for DST. */
597
0
    if (found_tzoffsetto && !found_tzoffsetfrom) {
598
0
        change.prev_utc_offset = change.utc_offset;
599
0
        found_tzoffsetfrom = 1;
600
0
    }
601
602
    /* If we didn't find a DTSTART, TZOFFSETTO and TZOFFSETFROM we have to
603
       ignore the component. FIXME: Add an error property? */
604
0
    if (!found_dtstart || !found_tzoffsetto || !found_tzoffsetfrom)
605
0
        return;
606
607
#if 0
608
    printf("\n Expanding component DTSTART (Y/M/D): %i/%i/%i %i:%02i:%02i\n",
609
           dtstart.year, dtstart.month, dtstart.day, dtstart.hour, dtstart.minute, dtstart.second);
610
#endif
611
612
    /* If the STANDARD/DAYLIGHT component has no recurrence rule, we add
613
       a single change for the DTSTART. */
614
0
    if (!has_rrule) {
615
0
        change.year = dtstart.year;
616
0
        change.month = dtstart.month;
617
0
        change.day = dtstart.day;
618
0
        change.hour = dtstart.hour;
619
0
        change.minute = dtstart.minute;
620
0
        change.second = dtstart.second;
621
622
        /* Convert to UTC. */
623
0
        icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset);
624
625
#if 0
626
        printf("  Appending single DTSTART (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
627
               change.year, change.month, change.day, change.hour, change.minute, change.second);
628
#endif
629
630
        /* Add the change to the array. */
631
0
        icalarray_append(changes, &change);
632
0
    }
633
634
    /* The component has recurrence data, so we expand that now. */
635
0
    prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
636
0
    while (prop && (has_rdate || has_rrule)) {
637
#if 0
638
        printf("Expanding property...\n");
639
#endif
640
0
        switch (icalproperty_isa(prop)) {
641
0
        case ICAL_RDATE_PROPERTY:
642
0
            rdate = icalproperty_get_rdate(prop);
643
0
            change.year = rdate.time.year;
644
0
            change.month = rdate.time.month;
645
0
            change.day = rdate.time.day;
646
            /* RDATEs with a DATE value inherit the time from
647
               the DTSTART. */
648
0
            if (icaltime_is_date(rdate.time)) {
649
0
                change.hour = dtstart.hour;
650
0
                change.minute = dtstart.minute;
651
0
                change.second = dtstart.second;
652
0
            } else {
653
0
                change.hour = rdate.time.hour;
654
0
                change.minute = rdate.time.minute;
655
0
                change.second = rdate.time.second;
656
657
                /* The spec was a bit vague about whether RDATEs were in local
658
                   time or UTC so we support both to be safe. So if it is in
659
                   UTC we have to add the UTC offset to get a local time. */
660
0
                if (!icaltime_is_utc(rdate.time))
661
0
                    icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset);
662
0
            }
663
664
#if 0
665
            printf("  Appending RDATE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
666
                   change.year, change.month, change.day,
667
                   change.hour, change.minute, change.second);
668
#endif
669
670
0
            icalarray_append(changes, &change);
671
0
            break;
672
0
        case ICAL_RRULE_PROPERTY:
673
0
            rrule = icalproperty_get_rrule(prop);
674
675
            /* If the rrule UNTIL value is set and is in UTC, we convert it to
676
               a local time, since the recurrence code has no way to convert
677
               it itself. */
678
0
            if (!icaltime_is_null_time(rrule.until) && icaltime_is_utc(rrule.until)) {
679
#if 0
680
                printf("  Found RRULE UNTIL in UTC.\n");
681
#endif
682
683
                /* To convert from UTC to a local time, we use the TZOFFSETFROM
684
                   since that is the offset from UTC that will be in effect
685
                   when each of the RRULE occurrences happens. */
686
0
                icaltime_adjust(&rrule.until, 0, 0, 0, change.prev_utc_offset);
687
0
                rrule.until.zone = NULL;
688
0
            }
689
690
            /* Add the dtstart to changes, otherwise some oddly-defined VTIMEZONE
691
               components can cause the first year to get skipped. */
692
0
            change.year = dtstart.year;
693
0
            change.month = dtstart.month;
694
0
            change.day = dtstart.day;
695
0
            change.hour = dtstart.hour;
696
0
            change.minute = dtstart.minute;
697
0
            change.second = dtstart.second;
698
699
#if 0
700
            printf("  Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
701
                   change.year, change.month, change.day,
702
                   change.hour, change.minute, change.second);
703
#endif
704
705
0
            icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset);
706
707
0
            icalarray_append(changes, &change);
708
709
0
            rrule_iterator = icalrecur_iterator_new(rrule, dtstart);
710
0
            for (; rrule_iterator;) {
711
0
                occ = icalrecur_iterator_next(rrule_iterator);
712
                /* Skip dtstart since we just added it */
713
0
                if (icaltime_compare(dtstart, occ) == 0) {
714
0
                    continue;
715
0
                }
716
0
                if (occ.year > end_year || icaltime_is_null_time(occ)) {
717
0
                    break;
718
0
                }
719
0
                change.year = occ.year;
720
0
                change.month = occ.month;
721
0
                change.day = occ.day;
722
0
                change.hour = occ.hour;
723
0
                change.minute = occ.minute;
724
0
                change.second = occ.second;
725
726
#if 0
727
                printf("  Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
728
                       change.year, change.month, change.day,
729
                       change.hour, change.minute, change.second);
730
#endif
731
732
0
                icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset);
733
734
0
                icalarray_append(changes, &change);
735
0
            }
736
737
0
            icalrecur_iterator_free(rrule_iterator);
738
0
            break;
739
0
        default:
740
0
            break;
741
0
        }
742
743
0
        prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY);
744
0
    }
745
0
}
746
747
/** @brief A function to compare 2 icaltimezonechange elements, used for
748
 * qsort().
749
 */
750
static int icaltimezone_compare_change_fn(const void *elem1, const void *elem2)
751
0
{
752
0
    const icaltimezonechange *change1, *change2;
753
0
    int retval;
754
755
0
    change1 = (const icaltimezonechange *)elem1;
756
0
    change2 = (const icaltimezonechange *)elem2;
757
758
0
    if (change1->year < change2->year) {
759
0
        retval = -1;
760
0
    } else if (change1->year > change2->year) {
761
0
        retval = 1;
762
0
    } else if (change1->month < change2->month) {
763
0
        retval = -1;
764
0
    } else if (change1->month > change2->month) {
765
0
        retval = 1;
766
0
    } else if (change1->day < change2->day) {
767
0
        retval = -1;
768
0
    } else if (change1->day > change2->day) {
769
0
        retval = 1;
770
0
    } else if (change1->hour < change2->hour) {
771
0
        retval = -1;
772
0
    } else if (change1->hour > change2->hour) {
773
0
        retval = 1;
774
0
    } else if (change1->minute < change2->minute) {
775
0
        retval = -1;
776
0
    } else if (change1->minute > change2->minute) {
777
0
        retval = 1;
778
0
    } else if (change1->second < change2->second) {
779
0
        retval = -1;
780
0
    } else if (change1->second > change2->second) {
781
0
        retval = 1;
782
0
    } else {
783
0
        retval = 0;
784
0
    }
785
786
0
    return retval;
787
0
}
788
789
void icaltimezone_convert_time(struct icaltimetype *tt,
790
                               icaltimezone *from_zone, icaltimezone *to_zone)
791
0
{
792
0
    int utc_offset, is_daylight;
793
794
    /* If the time is a DATE value or both timezones are the same, or we are
795
       converting a floating time, we don't need to do anything. */
796
0
    if (icaltime_is_date(*tt) || from_zone == to_zone || from_zone == NULL)
797
0
        return;
798
799
    /* Convert the time to UTC by getting the UTC offset and subtracting it. */
800
0
    utc_offset = icaltimezone_get_utc_offset(from_zone, tt, NULL);
801
0
    icaltime_adjust(tt, 0, 0, 0, -utc_offset);
802
803
    /* Now we convert the time to the new timezone by getting the UTC offset
804
       of our UTC time and adding it. */
805
0
    utc_offset = icaltimezone_get_utc_offset_of_utc_time(to_zone, tt, &is_daylight);
806
0
    tt->is_daylight = is_daylight;
807
0
    icaltime_adjust(tt, 0, 0, 0, utc_offset);
808
0
}
809
810
int icaltimezone_get_utc_offset(icaltimezone *zone, struct icaltimetype *tt, int *is_daylight)
811
0
{
812
0
    icaltimezonechange *zone_change, *prev_zone_change, tt_change, tmp_change;
813
0
    size_t change_num, change_num_to_use;
814
0
    int found_change;
815
0
    int step, utc_offset_change, cmp;
816
0
    int want_daylight;
817
818
0
    if (tt == NULL)
819
0
        return 0;
820
821
0
    if (is_daylight)
822
0
        *is_daylight = 0;
823
824
    /* For local times and UTC return 0. */
825
0
    if (zone == NULL || zone == &utc_timezone)
826
0
        return 0;
827
828
    /* Use the builtin icaltimezone if possible. */
829
0
    if (zone->builtin_timezone)
830
0
        zone = zone->builtin_timezone;
831
832
0
    icaltimezone_changes_lock();
833
834
    /* Make sure the changes array is expanded up to the given time. */
835
0
    icaltimezone_ensure_coverage(zone, tt->year);
836
837
0
    if (!zone->changes || zone->changes->num_elements == 0) {
838
0
        icaltimezone_changes_unlock();
839
0
        return 0;
840
0
    }
841
842
    /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
843
       can use our comparison function on it. */
844
0
    tt_change.year = tt->year;
845
0
    tt_change.month = tt->month;
846
0
    tt_change.day = tt->day;
847
0
    tt_change.hour = tt->hour;
848
0
    tt_change.minute = tt->minute;
849
0
    tt_change.second = tt->second;
850
851
    /* This should find a change close to the time, either the change before
852
       it or the change after it. */
853
0
    change_num = icaltimezone_find_nearby_change(zone, &tt_change);
854
855
    /* Now move backwards or forwards to find the timezone change that applies
856
       to tt. It should only have to do 1 or 2 steps. */
857
0
    zone_change = icalarray_element_at(zone->changes, change_num);
858
0
    step = 1;
859
0
    found_change = 0;
860
0
    change_num_to_use = -1;
861
0
    for (;;) {
862
        /* Copy the change, so we can adjust it. */
863
0
        tmp_change = *zone_change;
864
865
        /* If the clock is going backward, check if it is in the region of time
866
           that is used twice. If it is, use the change with the daylight
867
           setting which matches tt, or use standard if we don't know. */
868
0
        if (tmp_change.utc_offset < tmp_change.prev_utc_offset) {
869
            /* If the time change is at 2:00AM local time and the clock is
870
               going back to 1:00AM we adjust the change to 1:00AM. We may
871
               have the wrong change but we'll figure that out later. */
872
0
            icaltimezone_adjust_change(&tmp_change, 0, 0, 0, tmp_change.utc_offset);
873
0
        } else {
874
0
            icaltimezone_adjust_change(&tmp_change, 0, 0, 0, tmp_change.prev_utc_offset);
875
0
        }
876
877
0
        cmp = icaltimezone_compare_change_fn(&tt_change, &tmp_change);
878
879
        /* If the given time is on or after this change, then this change may
880
           apply, but we continue as a later change may be the right one.
881
           If the given time is before this change, then if we have already
882
           found a change which applies we can use that, else we need to step
883
           backwards. */
884
0
        if (cmp >= 0) {
885
0
            found_change = 1;
886
0
            change_num_to_use = change_num;
887
0
        } else {
888
0
            step = -1;
889
0
        }
890
891
        /* If we are stepping backwards through the changes and we have found
892
           a change that applies, then we know this is the change to use so
893
           we exit the loop. */
894
0
        if (step == -1 && found_change == 1)
895
0
            break;
896
897
        /* If we go past the start of the changes array, then we have no data
898
           for this time so we return the prev UTC offset. */
899
0
        if (change_num == 0 && step < 0) {
900
0
            if (is_daylight) {
901
0
                *is_daylight = ! tmp_change.is_daylight;
902
0
            }
903
904
0
            icaltimezone_changes_unlock();
905
906
0
            return tmp_change.prev_utc_offset;
907
0
        }
908
909
0
        change_num += step;
910
911
0
        if (change_num >= zone->changes->num_elements)
912
0
            break;
913
914
0
        zone_change = icalarray_element_at(zone->changes, change_num);
915
0
    }
916
917
    /* If we didn't find a change to use, then we have a bug! */
918
0
    icalerror_assert(found_change == 1, "No applicable timezone change found");
919
920
    /* Now we just need to check if the time is in the overlapped region of
921
       time when clocks go back. */
922
0
    zone_change = icalarray_element_at(zone->changes, change_num_to_use);
923
924
0
    utc_offset_change = zone_change->utc_offset - zone_change->prev_utc_offset;
925
0
    if (utc_offset_change < 0 && change_num_to_use > 0) {
926
0
        tmp_change = *zone_change;
927
0
        icaltimezone_adjust_change(&tmp_change, 0, 0, 0, tmp_change.prev_utc_offset);
928
929
0
        if (icaltimezone_compare_change_fn(&tt_change, &tmp_change) < 0) {
930
            /* The time is in the overlapped region, so we may need to use
931
               either the current zone_change or the previous one. If the
932
               time has the is_daylight field set we use the matching change,
933
               else we use the change with standard time. */
934
0
            prev_zone_change = icalarray_element_at(zone->changes, change_num_to_use - 1);
935
936
            /* I was going to add an is_daylight flag to struct icaltimetype,
937
               but iCalendar doesn't let us distinguish between standard and
938
               daylight time anyway, so there's no point. So we just use the
939
               standard time instead. */
940
0
            want_daylight = (tt->is_daylight == 1) ? 1 : 0;
941
942
#if 0
943
            if (zone_change->is_daylight == prev_zone_change->is_daylight) {
944
                printf(" **** Same is_daylight setting\n");
945
            }
946
#endif
947
948
0
            if (zone_change->is_daylight != want_daylight &&
949
0
                prev_zone_change->is_daylight == want_daylight) {
950
0
                zone_change = prev_zone_change;
951
0
            }
952
0
        }
953
0
    }
954
955
    /* Now we know exactly which timezone change applies to the time, so
956
       we can return the UTC offset and whether it is a daylight time. */
957
0
    if (is_daylight) {
958
0
        *is_daylight = zone_change->is_daylight;
959
0
    }
960
0
    utc_offset_change = zone_change->utc_offset;
961
962
0
    icaltimezone_changes_unlock();
963
964
0
    return utc_offset_change;
965
0
}
966
967
int icaltimezone_get_utc_offset_of_utc_time(icaltimezone *zone,
968
                                            struct icaltimetype *tt, int *is_daylight)
969
0
{
970
0
    icaltimezonechange *zone_change, tt_change, tmp_change;
971
0
    size_t change_num, change_num_to_use;
972
0
    int found_change = 1;
973
0
    int step, utc_offset;
974
975
0
    if (is_daylight)
976
0
        *is_daylight = 0;
977
978
    /* For local times and UTC return 0. */
979
0
    if (zone == NULL || zone == &utc_timezone)
980
0
        return 0;
981
982
    /* Use the builtin icaltimezone if possible. */
983
0
    if (zone->builtin_timezone)
984
0
        zone = zone->builtin_timezone;
985
986
0
    icaltimezone_changes_lock();
987
988
    /* Make sure the changes array is expanded up to the given time. */
989
0
    icaltimezone_ensure_coverage(zone, tt->year);
990
991
0
    if (!zone->changes || zone->changes->num_elements == 0) {
992
0
        icaltimezone_changes_unlock();
993
0
        return 0;
994
0
    }
995
996
    /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
997
       can use our comparison function on it. */
998
0
    tt_change.year = tt->year;
999
0
    tt_change.month = tt->month;
1000
0
    tt_change.day = tt->day;
1001
0
    tt_change.hour = tt->hour;
1002
0
    tt_change.minute = tt->minute;
1003
0
    tt_change.second = tt->second;
1004
1005
    /* This should find a change close to the time, either the change before
1006
       it or the change after it. */
1007
0
    change_num = icaltimezone_find_nearby_change(zone, &tt_change);
1008
1009
    /* Now move backwards or forwards to find the timezone change that applies
1010
       to tt. It should only have to do 1 or 2 steps. */
1011
0
    zone_change = icalarray_element_at(zone->changes, change_num);
1012
0
    step = 1;
1013
0
    found_change = 0;
1014
0
    change_num_to_use = -1;
1015
0
    for (;;) {
1016
        /* Copy the change and adjust it to UTC. */
1017
0
        tmp_change = *zone_change;
1018
1019
        /* If the given time is on or after this change, then this change may
1020
           apply, but we continue as a later change may be the right one.
1021
           If the given time is before this change, then if we have already
1022
           found a change which applies we can use that, else we need to step
1023
           backwards. */
1024
0
        if (icaltimezone_compare_change_fn(&tt_change, &tmp_change) >= 0) {
1025
0
            found_change = 1;
1026
0
            change_num_to_use = change_num;
1027
0
        } else {
1028
0
            step = -1;
1029
0
        }
1030
1031
        /* If we are stepping backwards through the changes and we have found
1032
           a change that applies, then we know this is the change to use so
1033
           we exit the loop. */
1034
0
        if (step == -1 && found_change == 1)
1035
0
            break;
1036
1037
        /* If we go past the start of the changes array, then we have no data
1038
           for this time so we return the prev UTC offset. */
1039
0
        if (change_num == 0 && step < 0) {
1040
0
            if (is_daylight) {
1041
0
                *is_daylight = ! tmp_change.is_daylight;
1042
0
            }
1043
1044
0
            icaltimezone_changes_unlock();
1045
1046
0
            return tmp_change.prev_utc_offset;
1047
0
        }
1048
1049
0
        change_num += step;
1050
1051
0
        if (change_num >= zone->changes->num_elements)
1052
0
            break;
1053
1054
0
        zone_change = icalarray_element_at(zone->changes, change_num);
1055
0
    }
1056
1057
    /* If we didn't find a change to use, then we have a bug! */
1058
0
    icalerror_assert(found_change == 1, "No applicable timezone change found");
1059
1060
    /* Now we know exactly which timezone change applies to the time, so
1061
       we can return the UTC offset and whether it is a daylight time. */
1062
0
    zone_change = icalarray_element_at(zone->changes, change_num_to_use);
1063
0
    if (is_daylight) {
1064
0
        *is_daylight = zone_change->is_daylight;
1065
0
    }
1066
0
    utc_offset = zone_change->utc_offset;
1067
1068
0
    icaltimezone_changes_unlock();
1069
1070
0
    return utc_offset;
1071
0
}
1072
1073
/** @brief Returns the index of a timezone change which is close to the time
1074
 * given in change.
1075
 *
1076
 * Hold icaltimezone_changes_lock(); before calling this function.
1077
*/
1078
static size_t icaltimezone_find_nearby_change(icaltimezone *zone, icaltimezonechange * change)
1079
0
{
1080
0
    icaltimezonechange *zone_change;
1081
0
    size_t lower, middle, upper;
1082
0
    int cmp;
1083
1084
    /* Do a simple binary search. */
1085
0
    lower = middle = 0;
1086
0
    upper = zone->changes->num_elements;
1087
1088
0
    while (lower < upper) {
1089
0
        middle = (lower + upper) / 2;
1090
0
        zone_change = icalarray_element_at(zone->changes, middle);
1091
0
        cmp = icaltimezone_compare_change_fn(change, zone_change);
1092
0
        if (cmp == 0) {
1093
0
            break;
1094
0
        } else if (cmp < 0) {
1095
0
            upper = middle;
1096
0
        } else {
1097
0
            lower = middle + 1;
1098
0
        }
1099
0
    }
1100
1101
0
    return middle;
1102
0
}
1103
1104
/** @brief Adds (or subtracts) a time from an icaltimezonechange.
1105
 *
1106
 * NOTE: This function is exactly the same as icaltime_adjust() except
1107
 * for the type of the first parameter.
1108
 */
1109
static void icaltimezone_adjust_change(icaltimezonechange *tt,
1110
                                       int days, int hours, int minutes, int seconds)
1111
0
{
1112
0
    int second, minute, hour, day;
1113
0
    int minutes_overflow, hours_overflow, days_overflow;
1114
0
    int days_in_month;
1115
1116
    /* Add on the seconds. */
1117
0
    second = tt->second + seconds;
1118
0
    tt->second = second % 60;
1119
0
    minutes_overflow = second / 60;
1120
0
    if (tt->second < 0) {
1121
0
        tt->second += 60;
1122
0
        minutes_overflow--;
1123
0
    }
1124
1125
    /* Add on the minutes. */
1126
0
    minute = tt->minute + minutes + minutes_overflow;
1127
0
    tt->minute = minute % 60;
1128
0
    hours_overflow = minute / 60;
1129
0
    if (tt->minute < 0) {
1130
0
        tt->minute += 60;
1131
0
        hours_overflow--;
1132
0
    }
1133
1134
    /* Add on the hours. */
1135
0
    hour = tt->hour + hours + hours_overflow;
1136
0
    tt->hour = hour % 24;
1137
0
    days_overflow = hour / 24;
1138
0
    if (tt->hour < 0) {
1139
0
        tt->hour += 24;
1140
0
        days_overflow--;
1141
0
    }
1142
1143
    /* Add on the days. */
1144
0
    day = tt->day + days + days_overflow;
1145
0
    if (day > 0) {
1146
0
        for (;;) {
1147
0
            days_in_month = icaltime_days_in_month(tt->month, tt->year);
1148
0
            if (day <= days_in_month)
1149
0
                break;
1150
1151
0
            tt->month++;
1152
0
            if (tt->month >= 13) {
1153
0
                tt->year++;
1154
0
                tt->month = 1;
1155
0
            }
1156
1157
0
            day -= days_in_month;
1158
0
        }
1159
0
    } else {
1160
0
        while (day <= 0) {
1161
0
            if (tt->month == 1) {
1162
0
                tt->year--;
1163
0
                tt->month = 12;
1164
0
            } else {
1165
0
                tt->month--;
1166
0
            }
1167
1168
0
            day += icaltime_days_in_month(tt->month, tt->year);
1169
0
        }
1170
0
    }
1171
0
    tt->day = day;
1172
0
}
1173
1174
const char *icaltimezone_get_tzid(icaltimezone *zone)
1175
0
{
1176
    /* If this is a floating time, without a timezone, return NULL. */
1177
0
    if (!zone)
1178
0
        return NULL;
1179
1180
0
    icaltimezone_load_builtin_timezone(zone);
1181
1182
0
    return zone->tzid;
1183
0
}
1184
1185
const char *icaltimezone_get_location(icaltimezone *zone)
1186
0
{
1187
    /* If this is a floating time, without a timezone, return NULL. */
1188
0
    if (!zone)
1189
0
        return NULL;
1190
1191
    /* Note that for builtin timezones this comes from zones.tab so we don't
1192
       need to check the timezone is loaded here. */
1193
0
    return zone->location;
1194
0
}
1195
1196
const char *icaltimezone_get_tznames(icaltimezone *zone)
1197
0
{
1198
    /* If this is a floating time, without a timezone, return NULL. */
1199
0
    if (!zone)
1200
0
        return NULL;
1201
1202
0
    icaltimezone_load_builtin_timezone(zone);
1203
1204
0
    return zone->tznames;
1205
0
}
1206
1207
double icaltimezone_get_latitude(icaltimezone *zone)
1208
0
{
1209
    /* If this is a floating time, without a timezone, return 0. */
1210
0
    if (!zone)
1211
0
        return 0.0;
1212
1213
    /* Note that for builtin timezones this comes from zones.tab so we don't
1214
       need to check the timezone is loaded here. */
1215
0
    return zone->latitude;
1216
0
}
1217
1218
double icaltimezone_get_longitude(icaltimezone *zone)
1219
0
{
1220
    /* If this is a floating time, without a timezone, return 0. */
1221
0
    if (!zone)
1222
0
        return 0.0;
1223
1224
    /* Note that for builtin timezones this comes from zones.tab so we don't
1225
       need to check the timezone is loaded here. */
1226
0
    return zone->longitude;
1227
0
}
1228
1229
icalcomponent *icaltimezone_get_component(icaltimezone *zone)
1230
572M
{
1231
    /* If this is a floating time, without a timezone, return NULL. */
1232
572M
    if (!zone)
1233
0
        return NULL;
1234
1235
572M
    icaltimezone_load_builtin_timezone(zone);
1236
1237
572M
    return zone->component;
1238
572M
}
1239
1240
int icaltimezone_set_component(icaltimezone *zone, icalcomponent *comp)
1241
0
{
1242
0
    icaltimezone_reset(zone);
1243
0
    return icaltimezone_get_vtimezone_properties(zone, comp);
1244
0
}
1245
1246
static const char *skip_slashes(const char *text, int n_slashes)
1247
0
{
1248
0
    const char *pp;
1249
0
    int num_slashes = 0;
1250
1251
0
    if(!text)
1252
0
        return NULL;
1253
1254
0
    for (pp = text; *pp; pp++) {
1255
0
        if(*pp == '/') {
1256
0
            num_slashes++;
1257
0
            if(num_slashes == n_slashes)
1258
0
                return pp + 1;
1259
0
        }
1260
0
    }
1261
1262
0
    return NULL;
1263
0
}
1264
1265
const char *icaltimezone_get_display_name(icaltimezone *zone)
1266
0
{
1267
0
    const char *display_name;
1268
0
    const char *tzid_prefix;
1269
1270
0
    display_name = icaltimezone_get_location(zone);
1271
0
    if (!display_name) {
1272
0
        display_name = icaltimezone_get_tznames(zone);
1273
0
    }
1274
0
    if (!display_name) {
1275
0
        display_name = icaltimezone_get_tzid(zone);
1276
0
        tzid_prefix = icaltimezone_tzid_prefix();
1277
        /* Outlook will strip out X-LIC-LOCATION property and so all
1278
           we get back in the iTIP replies is the TZID. So we see if
1279
           this is one of our TZIDs and if so we jump to the city name
1280
           at the end of it. */
1281
0
        if (display_name &&
1282
0
            !strncmp(display_name, tzid_prefix, strlen(tzid_prefix))) {
1283
            /* Skip past our prefix */
1284
0
            display_name += strlen(tzid_prefix);
1285
0
        }
1286
0
    }
1287
1288
0
    return display_name;
1289
0
}
1290
1291
icalarray *icaltimezone_array_new(void)
1292
1.80k
{
1293
1.80k
    return icalarray_new(sizeof(icaltimezone), 16);
1294
1.80k
}
1295
1296
void icaltimezone_array_append_from_vtimezone(icalarray *timezones, icalcomponent *child)
1297
114k
{
1298
114k
    icaltimezone zone;
1299
1300
114k
    icaltimezone_init(&zone);
1301
114k
    if (icaltimezone_get_vtimezone_properties(&zone, child))
1302
113k
        icalarray_append(timezones, &zone);
1303
114k
}
1304
1305
void icaltimezone_array_free(icalarray *timezones)
1306
1.80k
{
1307
1.80k
    icaltimezone *zone;
1308
1.80k
    size_t i;
1309
1310
1.80k
    if (timezones) {
1311
1.80k
        for (i = 0; i < timezones->num_elements; i++) {
1312
0
            zone = icalarray_element_at(timezones, i);
1313
0
            icaltimezone_free(zone, 0);
1314
0
        }
1315
1316
1.80k
        icalarray_free(timezones);
1317
1.80k
    }
1318
1.80k
}
1319
1320
/*
1321
 * BUILTIN TIMEZONE HANDLING
1322
 */
1323
1324
icalarray *icaltimezone_get_builtin_timezones(void)
1325
0
{
1326
0
    if (!builtin_timezones)
1327
0
        icaltimezone_init_builtin_timezones();
1328
1329
0
    return builtin_timezones;
1330
0
}
1331
1332
void icaltimezone_free_builtin_timezones(void)
1333
0
{
1334
0
    icaltimezone_array_free(builtin_timezones);
1335
0
    builtin_timezones = 0;
1336
0
}
1337
1338
icaltimezone *icaltimezone_get_builtin_timezone(const char *location)
1339
0
{
1340
0
    icalcomponent *comp;
1341
0
    icaltimezone *zone;
1342
0
    size_t lower;
1343
0
    const char *zone_location;
1344
1345
0
    if (!location || !location[0])
1346
0
        return NULL;
1347
1348
0
    if (!builtin_timezones)
1349
0
        icaltimezone_init_builtin_timezones();
1350
1351
0
    if (strcmp(location, "UTC") == 0 || strcmp(location, "GMT") == 0)
1352
0
        return &utc_timezone;
1353
1354
#if 0
1355
    /* Do a simple binary search. */
1356
    lower = middle = 0;
1357
    upper = builtin_timezones->num_elements;
1358
1359
    while (lower < upper) {
1360
        middle = (lower + upper) / 2;
1361
        zone = icalarray_element_at(builtin_timezones, middle);
1362
        zone_location = icaltimezone_get_location(zone);
1363
        cmp = strcmp(location, zone_location);
1364
        if (cmp == 0) {
1365
            return zone;
1366
        } else if (cmp < 0) {
1367
            upper = middle;
1368
        } else {
1369
            lower = middle + 1;
1370
        }
1371
    }
1372
#endif
1373
1374
    /* The zones from the system are not stored in alphabetical order,
1375
       so we just do a sequential search */
1376
0
    for (lower = 0; lower < builtin_timezones->num_elements; lower++) {
1377
0
        zone = icalarray_element_at(builtin_timezones, lower);
1378
0
        zone_location = icaltimezone_get_location(zone);
1379
0
        if (strcmp(location, zone_location) == 0)
1380
0
            return zone;
1381
0
    }
1382
1383
    /* Check whether file exists, but is not mentioned in zone.tab.
1384
       It means it's a deprecated timezone, but still available. */
1385
0
    comp = icaltzutil_fetch_timezone(location);
1386
0
    if (comp) {
1387
0
        icaltimezone tz;
1388
1389
0
        icaltimezone_init(&tz);
1390
0
        if (icaltimezone_set_component(&tz, comp)) {
1391
0
            icalarray_append(builtin_timezones, &tz);
1392
0
            return icalarray_element_at(builtin_timezones, builtin_timezones->num_elements - 1);
1393
0
        } else {
1394
0
            icalcomponent_free(comp);
1395
0
        }
1396
0
    }
1397
1398
0
    return NULL;
1399
0
}
1400
1401
static struct icaltimetype tm_to_icaltimetype(struct tm *tm)
1402
0
{
1403
0
    struct icaltimetype itt;
1404
1405
0
    memset(&itt, 0, sizeof(struct icaltimetype));
1406
1407
    /* cppcheck-suppress ctuuninitvar */
1408
0
    itt.second = tm->tm_sec;
1409
0
    itt.minute = tm->tm_min;
1410
0
    itt.hour = tm->tm_hour;
1411
1412
0
    itt.day = tm->tm_mday;
1413
0
    itt.month = tm->tm_mon + 1;
1414
0
    itt.year = tm->tm_year + 1900;
1415
1416
0
    itt.is_date = 0;
1417
1418
0
    return itt;
1419
0
}
1420
1421
static int get_offset(icaltimezone *zone)
1422
0
{
1423
0
    struct tm local;
1424
0
    struct icaltimetype tt;
1425
0
    int offset;
1426
0
    const icaltime_t now = icaltime(NULL);
1427
1428
0
    if (!icalgmtime_r(&now, &local))
1429
0
        return 0;
1430
1431
0
    tt = tm_to_icaltimetype(&local);
1432
0
    offset = icaltimezone_get_utc_offset(zone, &tt, NULL);
1433
1434
0
    return offset;
1435
0
}
1436
1437
icaltimezone *icaltimezone_get_builtin_timezone_from_offset(int offset, const char *tzname)
1438
0
{
1439
0
    icaltimezone *zone = NULL;
1440
0
    size_t i, count;
1441
1442
0
    if (!builtin_timezones)
1443
0
        icaltimezone_init_builtin_timezones();
1444
1445
0
    if (offset == 0)
1446
0
        return &utc_timezone;
1447
1448
0
    if (!tzname)
1449
0
        return NULL;
1450
1451
0
    count = builtin_timezones->num_elements;
1452
1453
0
    for (i = 0; i < count; i++) {
1454
0
        int z_offset;
1455
1456
0
        zone = icalarray_element_at(builtin_timezones, i);
1457
0
        icaltimezone_load_builtin_timezone(zone);
1458
1459
0
        z_offset = get_offset(zone);
1460
1461
0
        if (z_offset == offset && zone->tznames && !strcmp(tzname, zone->tznames))
1462
0
            return zone;
1463
0
    }
1464
1465
0
    return NULL;
1466
0
}
1467
1468
icaltimezone *icaltimezone_get_builtin_timezone_from_tzid(const char *tzid)
1469
0
{
1470
0
    const char *p, *zone_tzid, *tzid_prefix;
1471
0
    icaltimezone *zone;
1472
0
    int compat = 0;
1473
1474
0
    if (!tzid || !tzid[0])
1475
0
        return NULL;
1476
1477
0
    if (strcmp(tzid, "UTC") == 0 || strcmp(tzid, "GMT") == 0) {
1478
0
        return icaltimezone_get_builtin_timezone(tzid);
1479
0
    }
1480
1481
0
    tzid_prefix = icaltimezone_tzid_prefix();
1482
    /* Check that the TZID starts with our unique prefix. */
1483
0
    if (strncmp(tzid, tzid_prefix, strlen(tzid_prefix))) {
1484
0
        int ii;
1485
1486
0
        for (ii = 0; glob_compat_tzids[ii].tzid; ii++) {
1487
0
            if(strncmp(tzid, glob_compat_tzids[ii].tzid, strlen(glob_compat_tzids[ii].tzid)) == 0) {
1488
0
                p = skip_slashes(tzid, glob_compat_tzids[ii].slashes);
1489
0
                if(p) {
1490
0
                    zone = icaltimezone_get_builtin_timezone(p);
1491
                    /* Do not recheck the TZID matches exactly, it does not, because
1492
                       fallbacking with the compatibility timezone prefix here. */
1493
0
                    return zone;
1494
0
                }
1495
0
                break;
1496
0
            }
1497
0
        }
1498
1499
0
        return NULL;
1500
0
    }
1501
1502
    /* Skip past our prefix */
1503
0
    p = tzid + strlen(tzid_prefix);
1504
1505
    /* Special-case "/freeassociation.sourceforge.net/Tzfile/"
1506
       because it shares prefix with BUILTIN_TZID_PREFIX */
1507
0
    if (strcmp(tzid_prefix, BUILTIN_TZID_PREFIX) == 0 &&
1508
0
        strncmp(p, "Tzfile/", 7) == 0) {
1509
0
        p += 7;
1510
0
        compat = 1;
1511
0
    }
1512
1513
    /* Now we can use the function to get the builtin timezone from the
1514
       location string. */
1515
0
    zone = icaltimezone_get_builtin_timezone(p);
1516
0
    if (!zone || compat)
1517
0
        return zone;
1518
1519
#if defined(USE_BUILTIN_TZDATA)
1520
    if (use_builtin_tzdata)
1521
        return zone;
1522
#endif
1523
1524
    /* Check that the builtin TZID matches exactly. We don't want to return
1525
       a different version of the VTIMEZONE. */
1526
0
    zone_tzid = icaltimezone_get_tzid(zone);
1527
0
    if (!strcmp(zone_tzid, tzid)) {
1528
0
        return zone;
1529
0
    } else {
1530
0
        return NULL;
1531
0
    }
1532
0
}
1533
1534
icaltimezone *icaltimezone_get_utc_timezone(void)
1535
454
{
1536
454
    if (!builtin_timezones)
1537
1
        icaltimezone_init_builtin_timezones();
1538
1539
454
    return &utc_timezone;
1540
454
}
1541
1542
/** @brief Initializes the builtin timezone data, i.e. the
1543
 * builtin_timezones array and the special UTC timezone.
1544
 *
1545
 * It should be called before any code that uses the timezone functions.
1546
 */
1547
static void icaltimezone_init_builtin_timezones(void)
1548
1
{
1549
    /* Initialize the special UTC timezone. */
1550
1
    utc_timezone.tzid = (char *)"UTC";
1551
1552
1
    icaltimezone_builtin_lock();
1553
1
    if (!builtin_timezones) {
1554
1
        icaltimezone_parse_zone_tab();
1555
1
    }
1556
1
    icaltimezone_builtin_unlock();
1557
1
}
1558
1559
static int parse_coord(char *coord, int len, int *degrees, int *minutes, int *seconds)
1560
838
{
1561
838
    if (len == 5) {
1562
365
        sscanf(coord + 1, "%2d%2d", degrees, minutes);
1563
473
    } else if (len == 6) {
1564
365
        sscanf(coord + 1, "%3d%2d", degrees, minutes);
1565
365
    } else if (len == 7) {
1566
54
        sscanf(coord + 1, "%2d%2d%2d", degrees, minutes, seconds);
1567
54
    } else if (len == 8) {
1568
54
        sscanf(coord + 1, "%3d%2d%2d", degrees, minutes, seconds);
1569
54
    } else {
1570
0
        icalerrprintf("Invalid coordinate: %s\n", coord);
1571
0
        return 1;
1572
0
    }
1573
1574
838
    if (coord[0] == '-')
1575
312
        *degrees = -*degrees;
1576
1577
838
    return 0;
1578
838
}
1579
1580
static int fetch_lat_long_from_string(const char *str,
1581
                                      int *latitude_degrees, int *latitude_minutes,
1582
                                      int *latitude_seconds,
1583
                                      int *longitude_degrees, int *longitude_minutes,
1584
                                      int *longitude_seconds,
1585
                                      char *location)
1586
419
{
1587
419
    size_t len;
1588
419
    char *sptr, *lat, *lon, *loc, *temp;
1589
1590
    /* We need to parse the latitude/longitude coordinates and location fields  */
1591
419
    sptr = (char *)str;
1592
1.25k
    while ((*sptr != '\t') && (*sptr != '\0')) {
1593
838
        sptr++;
1594
838
    }
1595
419
    temp = ++sptr;
1596
5.24k
    while (*sptr != '\t' && *sptr != '\0') {
1597
4.82k
        sptr++;
1598
4.82k
    }
1599
419
    len = (ptrdiff_t) (sptr - temp);
1600
419
    lat = (char *)icalmemory_new_buffer(len + 1);
1601
419
    memset(lat, '\0', len + 1);
1602
419
    strncpy(lat, temp, len);
1603
419
    lat[len] = '\0';
1604
419
    while ((*sptr != '\t') && (*sptr != '\0')) {
1605
0
        sptr++;
1606
0
    }
1607
419
    loc = ++sptr;
1608
6.90k
    while (!isspace((int)(*sptr)) && (*sptr != '\0')) {
1609
6.48k
        sptr++;
1610
6.48k
    }
1611
419
    len = (ptrdiff_t)(sptr - loc);
1612
419
    strncpy(location, loc, len);
1613
419
    location[len] = '\0';
1614
1615
#if defined(sun) && defined(__SVR4)
1616
    /* Handle EET, MET and WET in zone_sun.tab. */
1617
    if (!strcmp(location, "Europe/")) {
1618
        while ((*sptr != '\t') && (*sptr != '\0')) {
1619
            sptr++;
1620
        }
1621
        loc = ++sptr;
1622
        while (!isspace(*sptr) && (*sptr != '\0')) {
1623
            sptr++;
1624
        }
1625
        len = (ptrdiff_t)(sptr - loc);
1626
        strncpy(location, loc, len);
1627
        location[len] = '\0';
1628
    }
1629
#endif
1630
1631
419
    lon = lat + 1;
1632
2.20k
    while (*lon != '\0' && *lon != '+' && *lon != '-') {
1633
1.78k
        lon++;
1634
1.78k
    }
1635
1636
419
    if (parse_coord(lat, (int)(lon - lat),
1637
419
                    latitude_degrees,
1638
419
                    latitude_minutes,
1639
419
                    latitude_seconds) == 1 ||
1640
419
        parse_coord(lon, (int)strlen(lon),
1641
419
                    longitude_degrees, longitude_minutes, longitude_seconds) == 1) {
1642
0
        icalmemory_free_buffer(lat);
1643
0
        return 1;
1644
0
    }
1645
1646
419
    icalmemory_free_buffer(lat);
1647
1648
419
    return 0;
1649
419
}
1650
1651
/** @brief Parses the zones.tab file containing the names and locations
1652
 * of the builtin timezones.
1653
 *
1654
 * It creates the builtin_timezones array
1655
 * which is an icalarray of icaltimezone structs. It only fills in the
1656
 * location, latitude and longtude fields; the rest are left
1657
 * blank. The VTIMEZONE component is loaded later if it is needed. The
1658
 * timezones in the zones.tab file are sorted by their name, which is
1659
 * useful for binary searches.
1660
 */
1661
static void icaltimezone_parse_zone_tab(void)
1662
1
{
1663
1
    const char *zonedir, *zonetab;
1664
1
    char *filename;
1665
1
    FILE *fp;
1666
1
    char buf[1024];     /* Used to store each line of zones.tab as it is read. */
1667
1
    char location[1024];        /* Stores the city name when parsing buf. */
1668
1
    size_t filename_len;
1669
1
    int latitude_degrees = 0, latitude_minutes = 0, latitude_seconds = 0;
1670
1
    int longitude_degrees = 0, longitude_minutes = 0, longitude_seconds = 0;
1671
1
    icaltimezone zone;
1672
1673
1
    icalerror_assert(builtin_timezones == NULL, "Parsing zones.tab file multiple times");
1674
1675
1
    builtin_timezones = icalarray_new(sizeof(icaltimezone), 1024);
1676
1677
1
    if (!use_builtin_tzdata) {
1678
1
        zonedir = icaltzutil_get_zone_directory();
1679
1
        zonetab = ZONES_TAB_SYSTEM_FILENAME;
1680
1
    } else {
1681
0
        zonedir = get_zone_directory_builtin();
1682
0
        zonetab = ZONES_TAB_FILENAME;
1683
0
    }
1684
1685
1
    filename_len = 0;
1686
1
    if (zonedir) {
1687
1
        filename_len = strlen(zonedir);
1688
1
    }
1689
1690
1
    icalerror_assert(filename_len > 0, "Unable to locate a zoneinfo dir");
1691
1
    if (filename_len == 0) {
1692
0
        icalerror_set_errno(ICAL_INTERNAL_ERROR);\
1693
0
        return;
1694
0
    }
1695
1696
1
    filename_len += strlen(zonetab);
1697
1
    filename_len += 2; /* for dir separator and final '\0' */
1698
1699
1
    filename = (char *)icalmemory_new_buffer(filename_len);
1700
1
    if (!filename) {
1701
0
        icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1702
0
        return;
1703
0
    }
1704
1
    snprintf(filename, filename_len, "%s/%s", zonedir, zonetab);
1705
1706
1
    fp = fopen(filename, "r");
1707
1
    icalmemory_free_buffer(filename);
1708
1
    icalerror_assert(fp, "Cannot open the zonetab file for reading");
1709
1
    if (!fp) {
1710
0
        icalerror_set_errno(ICAL_INTERNAL_ERROR);
1711
0
        return;
1712
0
    }
1713
1714
450
    while (fgets(buf, (int)sizeof(buf), fp)) {
1715
449
        if (*buf == '#')
1716
30
            continue;
1717
1718
419
        if (use_builtin_tzdata) {
1719
            /* The format of each line is: "[ latitude longitude ] location". */
1720
0
            if (buf[0] != '+' && buf[0] != '-') {
1721
0
                latitude_degrees = longitude_degrees = 360;
1722
0
                latitude_minutes = longitude_minutes = 0;
1723
0
                latitude_seconds = longitude_seconds = 0;
1724
0
                if (sscanf(buf, "%1000s", location) != 1) {     /*limit location to 1000chars */
1725
                    /*increase as needed */
1726
                    /*see location and buf declarations */
1727
0
                    icalerrprintf("Invalid timezone description line: %s\n", buf);
1728
0
                    continue;
1729
0
                }
1730
0
            } else if (sscanf(buf, "%4d%2d%2d %4d%2d%2d %1000s", /*limit location to 1000chars */
1731
                              /*increase as needed */
1732
                              /*see location and buf declarations */
1733
0
                              &latitude_degrees, &latitude_minutes,
1734
0
                              &latitude_seconds,
1735
0
                              &longitude_degrees, &longitude_minutes,
1736
0
                              &longitude_seconds, location) != 7) {
1737
0
                icalerrprintf("Invalid timezone description line: %s\n", buf);
1738
0
                continue;
1739
0
            }
1740
419
        } else {
1741
            /* coverity[tainted_data] */
1742
419
            if (fetch_lat_long_from_string(buf, &latitude_degrees, &latitude_minutes,
1743
419
                                           &latitude_seconds,
1744
419
                                           &longitude_degrees, &longitude_minutes,
1745
419
                                           &longitude_seconds, location)) {
1746
0
                icalerrprintf("Invalid timezone description line: %s\n", buf);
1747
0
                continue;
1748
0
            }
1749
419
        }
1750
1751
419
        icaltimezone_init(&zone);
1752
419
        zone.location = icalmemory_strdup(location);
1753
1754
419
        if (latitude_degrees >= 0) {
1755
306
            zone.latitude =
1756
306
                (double)latitude_degrees +
1757
306
                (double)latitude_minutes / 60 +
1758
306
                (double)latitude_seconds / 3600;
1759
306
        } else {
1760
113
            zone.latitude =
1761
113
                (double)latitude_degrees -
1762
113
                (double)latitude_minutes / 60 -
1763
113
                (double)latitude_seconds / 3600;
1764
113
        }
1765
1766
419
        if (longitude_degrees >= 0) {
1767
225
            zone.longitude =
1768
225
                (double)longitude_degrees +
1769
225
                (double)longitude_minutes / 60 +
1770
225
                (double)longitude_seconds / 3600;
1771
225
        } else {
1772
194
            zone.longitude =
1773
194
                (double)longitude_degrees -
1774
194
                (double)longitude_minutes / 60 -
1775
194
                (double)longitude_seconds / 3600;
1776
194
        }
1777
1778
419
        icalarray_append(builtin_timezones, &zone);
1779
1780
#if 0
1781
        printf("Found zone: %s %f %f\n", location, zone.latitude, zone.longitude);
1782
#endif
1783
419
    }
1784
1785
1
    fclose(fp);
1786
1
}
1787
1788
void icaltimezone_release_zone_tab(void)
1789
0
{
1790
0
    size_t i;
1791
0
    icalarray *mybuiltin_timezones = builtin_timezones;
1792
1793
0
    if (builtin_timezones == NULL)
1794
0
        return;
1795
1796
0
    builtin_timezones = NULL;
1797
0
    for (i = 0; i < mybuiltin_timezones->num_elements; i++) {
1798
0
        icalmemory_free_buffer(((icaltimezone *) icalarray_element_at(mybuiltin_timezones, i))->location);
1799
0
    }
1800
0
    icalarray_free(mybuiltin_timezones);
1801
0
}
1802
1803
/** @brief Loads the builtin VTIMEZONE data for the given timezone. */
1804
static void icaltimezone_load_builtin_timezone(icaltimezone *zone)
1805
572M
{
1806
572M
    icalcomponent *comp = 0, *subcomp;
1807
1808
    /* Prevent blocking on mutex lock caused by recursive calls */
1809
572M
    if (zone->component)
1810
572M
        return;
1811
1812
0
    icaltimezone_builtin_lock();
1813
1814
    /* Try again, maybe it had been set by other thread while waiting for the lock */
1815
0
    if (zone->component) {
1816
0
        icaltimezone_builtin_unlock();
1817
0
        return;
1818
0
    }
1819
1820
    /* If the location isn't set, it isn't a builtin timezone. */
1821
0
    if (!zone->location || !zone->location[0]) {
1822
0
        icaltimezone_builtin_unlock();
1823
0
        return;
1824
0
    }
1825
1826
0
    if (use_builtin_tzdata) {
1827
0
        char *filename;
1828
0
        size_t filename_len;
1829
0
        FILE *fp;
1830
0
        icalparser *parser;
1831
1832
0
        filename_len = strlen(get_zone_directory_builtin()) + strlen(zone->location) + 6;
1833
1834
0
        filename = (char *)icalmemory_new_buffer(filename_len);
1835
0
        if (!filename) {
1836
0
            icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1837
0
            goto out;
1838
0
        }
1839
1840
0
        snprintf(filename, filename_len, "%s/%s.ics", get_zone_directory_builtin(), zone->location);
1841
1842
0
        fp = fopen(filename, "r");
1843
0
        icalmemory_free_buffer(filename);
1844
0
        if (!fp) {
1845
0
            icalerror_set_errno(ICAL_FILE_ERROR);
1846
0
            goto out;
1847
0
        }
1848
1849
        /* ##### B.# Sun, 11 Nov 2001 04:04:29 +1100
1850
           this is where the MALFORMEDDATA error is being set, after the call to 'icalparser_parse'
1851
           icalerrprintf("** WARNING ** %s: %d %s\n",
1852
                   __FILE__, __LINE__, icalerror_strerror(icalerrno));
1853
         */
1854
1855
0
        parser = icalparser_new();
1856
0
        icalparser_set_gen_data(parser, fp);
1857
0
        comp = icalparser_parse(parser, icaltimezone_load_get_line_fn);
1858
0
        icalparser_free(parser);
1859
0
        fclose(fp);
1860
1861
        /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
1862
0
        subcomp = icalcomponent_get_first_component(comp, ICAL_VTIMEZONE_COMPONENT);
1863
1864
0
        if (subcomp) {
1865
0
            icalproperty *prop;
1866
1867
            /* Ensure expected TZID */
1868
0
            prop = icalcomponent_get_first_property(subcomp, ICAL_TZID_PROPERTY);
1869
0
            if(prop) {
1870
0
                char *new_tzid;
1871
0
                size_t new_tzid_len;
1872
0
                const char *tzid_prefix = icaltimezone_tzid_prefix();
1873
1874
0
                new_tzid_len = strlen(tzid_prefix) + strlen(zone->location) + 1;
1875
0
                new_tzid = (char *)icalmemory_new_buffer(sizeof(char) * (new_tzid_len + 1));
1876
0
                if(new_tzid) {
1877
0
                    snprintf(new_tzid, new_tzid_len, "%s%s", tzid_prefix, zone->location);
1878
0
                    icalproperty_set_tzid(prop, new_tzid);
1879
0
                    icalmemory_free_buffer(new_tzid);
1880
0
                } else {
1881
0
                    icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1882
0
                }
1883
0
            }
1884
1885
            /* Ensure expected Location - it's for cases where one VTIMEZONE is shared
1886
               between different locations (like Pacific/Midway is Pacific/Pago_Pago).
1887
               This updates the properties, thus when the component is converted to
1888
               the string and back to the component the Location will still match. */
1889
0
            prop = icalcomponent_get_first_property(subcomp, ICAL_LOCATION_PROPERTY);
1890
0
            if (prop)
1891
0
                icalproperty_set_location(prop, zone->location);
1892
1893
0
            for (prop = icalcomponent_get_first_property(subcomp, ICAL_X_PROPERTY);
1894
0
                 prop;
1895
0
                 prop = icalcomponent_get_next_property(subcomp, ICAL_X_PROPERTY)) {
1896
0
                const char *name;
1897
1898
0
                name = icalproperty_get_x_name(prop);
1899
0
                if (name && !strcasecmp(name, "X-LIC-LOCATION")) {
1900
0
                    icalproperty_set_x(prop, zone->location);
1901
0
                    break;
1902
0
                }
1903
0
            }
1904
0
        }
1905
0
    } else {
1906
0
        subcomp = icaltzutil_fetch_timezone(zone->location);
1907
0
    }
1908
1909
0
    if (!subcomp) {
1910
0
        icalerror_set_errno(ICAL_PARSE_ERROR);
1911
0
        goto out;
1912
0
    }
1913
1914
0
    icaltimezone_get_vtimezone_properties(zone, subcomp);
1915
1916
0
    if (use_builtin_tzdata) {
1917
0
        icalcomponent_remove_component(comp, subcomp);
1918
0
        icalcomponent_free(comp);
1919
0
    }
1920
1921
0
  out:
1922
0
    icaltimezone_builtin_unlock();
1923
0
    return;
1924
0
}
1925
1926
/** @brief Callback used from icalparser_parse() */
1927
static char *icaltimezone_load_get_line_fn(char *s, size_t size, void *data)
1928
0
{
1929
0
    return fgets(s, (int)size, (FILE *) data);
1930
0
}
1931
1932
/*
1933
 * DEBUGGING
1934
 */
1935
1936
int icaltimezone_dump_changes(icaltimezone *zone, int max_year, FILE *fp)
1937
0
{
1938
0
    static const char months[][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1939
0
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1940
0
    };
1941
0
    icaltimezonechange *zone_change;
1942
0
    size_t change_num;
1943
0
    char buffer[8];
1944
1945
    /* Make sure the changes array is expanded up to the given time. */
1946
0
    icaltimezone_ensure_coverage(zone, max_year);
1947
1948
#if 0
1949
    printf("Num changes: %i\n", zone->changes->num_elements);
1950
#endif
1951
1952
0
    icaltimezone_changes_lock();
1953
1954
0
    for (change_num = 0; change_num < zone->changes->num_elements; change_num++) {
1955
0
        zone_change = icalarray_element_at(zone->changes, change_num);
1956
1957
0
        if (zone_change->year > max_year)
1958
0
            break;
1959
1960
0
        fprintf(fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
1961
0
                zone->location,
1962
0
                zone_change->day, months[zone_change->month - 1],
1963
0
                zone_change->year, zone_change->hour, zone_change->minute, zone_change->second);
1964
1965
        /* Wall Clock Time offset from UTC. */
1966
0
        format_utc_offset(zone_change->utc_offset, buffer, sizeof(buffer));
1967
0
        fprintf(fp, "\t%s", buffer);
1968
1969
0
        fprintf(fp, "\n");
1970
0
    }
1971
1972
0
    icaltimezone_changes_unlock();
1973
1974
0
    return 1;
1975
0
}
1976
1977
/** @brief Formats a UTC offset as "+HHMM" or "+HHMMSS".
1978
 *
1979
 * @p buffer should have space for 8 characters. */
1980
static void format_utc_offset(int utc_offset, char *buffer, size_t buffer_size)
1981
0
{
1982
0
    const char *sign = "+";
1983
0
    int hours, minutes, seconds;
1984
1985
0
    if (utc_offset < 0) {
1986
0
        utc_offset = -utc_offset;
1987
0
        sign = "-";
1988
0
    }
1989
1990
0
    hours = utc_offset / 3600;
1991
0
    minutes = (utc_offset % 3600) / 60;
1992
0
    seconds = utc_offset % 60;
1993
1994
    /* Sanity check. Standard timezone offsets shouldn't be much more than 12
1995
       hours, and daylight saving shouldn't change it by more than a few hours.
1996
       (The maximum offset is 15 hours 56 minutes at present.) */
1997
0
    if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60 || seconds < 0 || seconds >= 60) {
1998
0
        icalerrprintf("Warning: Strange timezone offset: H:%i M:%i S:%i\n",
1999
0
                hours, minutes, seconds);
2000
0
    }
2001
2002
0
    if (seconds == 0) {
2003
0
        snprintf(buffer, buffer_size, "%s%02i%02i", sign, hours, minutes);
2004
0
    } else {
2005
0
        snprintf(buffer, buffer_size, "%s%02i%02i%02i", sign, hours, minutes, seconds);
2006
0
    }
2007
0
}
2008
2009
static const char *get_zone_directory_builtin(void)
2010
0
{
2011
0
#if !defined(_WIN32)
2012
0
    return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
2013
#else
2014
    wchar_t wbuffer[1000];
2015
2016
#if !defined(_WIN32_WCE)
2017
    char buffer[1000], zoneinfodir[1000], dirname[1000];
2018
#else
2019
    wchar_t zoneinfodir[1000], dirname[1000];
2020
#endif
2021
    int used_default;
2022
    static ICAL_GLOBAL_VAR char *cache = NULL;
2023
2024
#if !defined(_WIN32_WCE)
2025
    unsigned char *dirslash, *zislash, *zislashp1;
2026
#else
2027
    wchar_t *dirslash, *zislash;
2028
#endif
2029
    struct stat st;
2030
2031
    if (zone_files_directory)
2032
        return zone_files_directory;
2033
2034
    if (cache)
2035
        return cache;
2036
2037
    /* Get the filename of the application */
2038
    if (!GetModuleFileNameW(NULL, wbuffer, sizeof(wbuffer) / sizeof(wbuffer[0])))
2039
        return ZONEINFO_DIRECTORY;
2040
2041
/*wince supports only unicode*/
2042
#if !defined(_WIN32_WCE)
2043
    /* Convert to system codepage */
2044
    if (!WideCharToMultiByte(CP_ACP, 0, wbuffer, -1, buffer, sizeof(buffer),
2045
                             NULL, &used_default) || used_default) {
2046
        /* Failed, try 8.3 format */
2047
        if (!GetShortPathNameW(wbuffer, wbuffer,
2048
                               sizeof(wbuffer) / sizeof(wbuffer[0])) ||
2049
            !WideCharToMultiByte(CP_ACP, 0, wbuffer, -1, buffer, sizeof(buffer),
2050
                                 NULL, &used_default) || used_default) {
2051
            return ZONEINFO_DIRECTORY;
2052
        }
2053
    }
2054
#endif
2055
    /* Look for the zoneinfo directory somewhere in the path where
2056
     * the app is installed. If the path to the app is
2057
     *
2058
     *      C:\opt\evo-2.6\bin\evolution-2.6.exe
2059
     *
2060
     * and the compile-time ZONEINFO_DIRECTORY is
2061
     *
2062
     *      C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo,
2063
     *
2064
     * we check the pathnames:
2065
     *
2066
     *      C:\opt\evo-2.6/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2067
     *      C:\opt\evo-2.6/target/evo/share/evolution-data-server-1.6/zoneinfo
2068
     *      C:\opt\evo-2.6/evo/share/evolution-data-server-1.6/zoneinfo
2069
     *      C:\opt\evo-2.6/share/evolution-data-server-1.6/zoneinfo         <===
2070
     *      C:\opt\evo-2.6/evolution-data-server-1.6/zoneinfo
2071
     *      C:\opt\evo-2.6/zoneinfo
2072
     *      C:\opt/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2073
     *      C:\opt/target/evo/share/evolution-data-server-1.6/zoneinfo
2074
     *      C:\opt/evo/share/evolution-data-server-1.6/zoneinfo
2075
     *      C:\opt/share/evolution-data-server-1.6/zoneinfo
2076
     *      C:\opt/evolution-data-server-1.6/zoneinfo
2077
     *      C:\opt/zoneinfo
2078
     *      C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2079
     *      C:/target/evo/share/evolution-data-server-1.6/zoneinfo
2080
     *      C:/evo/share/evolution-data-server-1.6/zoneinfo
2081
     *      C:/share/evolution-data-server-1.6/zoneinfo
2082
     *      C:/evolution-data-server-1.6/zoneinfo
2083
     *      C:/zoneinfo
2084
     *
2085
     * In Evolution's case, we would get a match already at the
2086
     * fourth pathname check.
2087
     */
2088
2089
    /* Strip away basename of app .exe first */
2090
#if !defined(_WIN32_WCE)
2091
    dirslash = _mbsrchr((unsigned char *)buffer, '\\');
2092
#else
2093
    dirslash = wcsrchr(wbuffer, L'\\');
2094
#endif
2095
    if (dirslash) {
2096
#if !defined(_WIN32_WCE)
2097
        *dirslash = '\0';
2098
#else
2099
        *dirslash = L'\0';
2100
#endif
2101
    }
2102
2103
#if defined(_WIN32_WCE)
2104
    while ((dirslash = wcsrchr(wbuffer, '\\'))) {
2105
        /* Strip one more directory from app .exe location */
2106
        *dirslash = L'\0';
2107
2108
        MultiByteToWideChar(CP_ACP, 0, ZONEINFO_DIRECTORY, -1, zoneinfodir, 1000);
2109
2110
        while ((zislash = wcschr(zoneinfodir, L'/'))) {
2111
            *zislash = L'.';
2112
            wcscpy(dirname, wbuffer);
2113
            wcscat(dirname, "/");
2114
            wcscat(dirname, zislash + 1);
2115
            if (stat(dirname, &st) == 0 && S_ISDIR(st.st_mode)) {
2116
                cache = wce_wctomb(dirname);
2117
                return cache;
2118
            }
2119
        }
2120
    }
2121
#else
2122
    while ((dirslash = _mbsrchr((unsigned char *)buffer, '\\'))) {
2123
        /* Strip one more directory from app .exe location */
2124
        *dirslash = '\0';
2125
2126
        strcpy(zoneinfodir, ZONEINFO_DIRECTORY);
2127
        while ((zislash = _mbschr((unsigned char *)zoneinfodir, '/'))) {
2128
            *zislash = '.';
2129
            strcpy(dirname, buffer);
2130
            strcat(dirname, "/");
2131
            zislashp1 = zislash + 1;
2132
            strcat(dirname, (char *)zislashp1);
2133
            if (stat(dirname, &st) == 0 && S_ISDIR(st.st_mode)) {
2134
                cache = icalmemory_strdup(dirname);
2135
                return cache;
2136
            }
2137
        }
2138
    }
2139
#endif
2140
    return ZONEINFO_DIRECTORY;
2141
#endif
2142
0
}
2143
2144
const char *get_zone_directory(void)
2145
0
{
2146
0
    if (use_builtin_tzdata) {
2147
0
        return get_zone_directory_builtin();
2148
0
    } else {
2149
0
        return icaltzutil_get_zone_directory();
2150
0
    }
2151
0
}
2152
2153
void set_zone_directory(const char *path)
2154
0
{
2155
0
    if (zone_files_directory)
2156
0
        free_zone_directory();
2157
2158
0
    zone_files_directory = icalmemory_new_buffer(strlen(path) + 1);
2159
2160
0
    if (zone_files_directory != NULL)
2161
0
        strcpy(zone_files_directory, path);
2162
0
}
2163
2164
void free_zone_directory(void)
2165
0
{
2166
0
    if (zone_files_directory != NULL) {
2167
0
        icalmemory_free_buffer(zone_files_directory);
2168
0
        zone_files_directory = NULL;
2169
0
    }
2170
0
}
2171
2172
void icaltimezone_set_tzid_prefix(const char *new_prefix)
2173
0
{
2174
0
    if (new_prefix) {
2175
0
        strncpy(s_ical_tzid_prefix, new_prefix, BUILTIN_TZID_PREFIX_LEN-1);
2176
0
    }
2177
0
}
2178
2179
void icaltimezone_set_builtin_tzdata(int set)
2180
0
{
2181
0
    use_builtin_tzdata = set;
2182
0
}
2183
2184
int icaltimezone_get_builtin_tzdata(void)
2185
0
{
2186
0
    return use_builtin_tzdata;
2187
0
}
2188
2189
struct observance {
2190
    const char *name;
2191
    icaltimetype onset;
2192
    int offset_from;
2193
    int offset_to;
2194
};
2195
2196
static void check_tombstone(struct observance *tombstone,
2197
                            struct observance *obs)
2198
0
{
2199
0
    if (icaltime_compare(obs->onset, tombstone->onset) > 0) {
2200
        /* onset is closer to cutoff than existing tombstone */
2201
0
        tombstone->name = icalmemory_tmp_copy(obs->name);
2202
0
        tombstone->offset_from = tombstone->offset_to = obs->offset_to;
2203
0
        tombstone->onset = obs->onset;
2204
0
    }
2205
0
}
2206
2207
struct rdate {
2208
    icalproperty *prop;
2209
    struct icaldatetimeperiodtype date;
2210
};
2211
2212
static int rdate_compare(const void *rdate1, const void *rdate2)
2213
0
{
2214
0
    return icaltime_compare(((struct rdate *) rdate1)->date.time,
2215
0
                            ((struct rdate *) rdate2)->date.time);
2216
0
}
2217
2218
void icaltimezone_truncate_vtimezone(icalcomponent *vtz,
2219
                                     icaltimetype start, icaltimetype end,
2220
                                     int ms_compatible)
2221
0
{
2222
0
    icalcomponent *comp, *nextc, *tomb_std = NULL, *tomb_day = NULL;
2223
0
    icalproperty *prop, *proleptic_prop = NULL;
2224
0
    struct observance tombstone;
2225
0
    unsigned need_tomb = (unsigned)!icaltime_is_null_time(start);
2226
0
    unsigned need_tzuntil = (unsigned)!icaltime_is_null_time(end);
2227
2228
0
    if (!need_tomb && !need_tzuntil) {
2229
        /* Nothing to do */
2230
0
        return;
2231
0
    }
2232
2233
    /* See if we have a proleptic tzname in VTIMEZONE */
2234
0
    for (prop = icalcomponent_get_first_property(vtz, ICAL_X_PROPERTY);
2235
0
         prop;
2236
0
         prop = icalcomponent_get_next_property(vtz, ICAL_X_PROPERTY)) {
2237
0
        if (!strcmp("X-PROLEPTIC-TZNAME", icalproperty_get_x_name(prop))) {
2238
0
            proleptic_prop = prop;
2239
0
            break;
2240
0
        }
2241
0
    }
2242
2243
0
    memset(&tombstone, 0, sizeof(struct observance));
2244
0
    tombstone.name = icalmemory_tmp_copy(proleptic_prop ?
2245
0
                                         icalproperty_get_x(proleptic_prop) :
2246
0
                                         "LMT");
2247
0
    if (!proleptic_prop ||
2248
0
        !icalproperty_get_parameter_as_string(proleptic_prop, "X-NO-BIG-BANG")) {
2249
0
      tombstone.onset.year = -1;
2250
0
    }
2251
2252
    /* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */
2253
0
    for (comp = icalcomponent_get_first_component(vtz, ICAL_ANY_COMPONENT);
2254
0
         comp; comp = nextc) {
2255
0
        icalproperty *dtstart_prop = NULL, *rrule_prop = NULL;
2256
0
        icalarray *rdates = icalarray_new(sizeof(struct rdate), 10);
2257
0
        icaltimetype dtstart;
2258
0
        struct observance obs;
2259
0
        size_t n;
2260
0
        unsigned trunc_dtstart = 0;
2261
0
        int r;
2262
2263
0
        nextc = icalcomponent_get_next_component(vtz, ICAL_ANY_COMPONENT);
2264
2265
0
        memset(&obs, 0, sizeof(struct observance));
2266
0
        obs.offset_from = obs.offset_to = INT_MAX;
2267
0
        obs.onset.is_daylight =
2268
0
            (icalcomponent_isa(comp) == ICAL_XDAYLIGHT_COMPONENT);
2269
2270
        /* Grab the properties that we require to expand recurrences */
2271
0
        for (prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
2272
0
             prop;
2273
0
             prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY)) {
2274
2275
0
            switch (icalproperty_isa(prop)) {
2276
0
            case ICAL_TZNAME_PROPERTY:
2277
0
                obs.name = icalproperty_get_tzname(prop);
2278
0
                break;
2279
2280
0
            case ICAL_DTSTART_PROPERTY:
2281
0
                dtstart_prop = prop;
2282
0
                obs.onset = dtstart = icalproperty_get_dtstart(prop);
2283
0
                break;
2284
2285
0
            case ICAL_TZOFFSETFROM_PROPERTY:
2286
0
                obs.offset_from = icalproperty_get_tzoffsetfrom(prop);
2287
0
                break;
2288
2289
0
            case ICAL_TZOFFSETTO_PROPERTY:
2290
0
                obs.offset_to = icalproperty_get_tzoffsetto(prop);
2291
0
                break;
2292
2293
0
            case ICAL_RRULE_PROPERTY:
2294
0
                rrule_prop = prop;
2295
0
                break;
2296
2297
0
            case ICAL_RDATE_PROPERTY: {
2298
0
                struct icaldatetimeperiodtype dtp = icalproperty_get_rdate(prop);
2299
0
                struct rdate rdate;
2300
2301
0
                rdate.prop = prop;
2302
0
                rdate.date.time = dtp.time;
2303
0
                rdate.date.period = dtp.period;
2304
2305
0
                icalarray_append(rdates, &rdate);
2306
0
                break;
2307
0
            }
2308
2309
0
            default:
2310
                /* ignore all other properties */
2311
0
                break;
2312
0
            }
2313
0
        }
2314
2315
        /* We MUST have DTSTART, TZNAME, TZOFFSETFROM, and TZOFFSETTO */
2316
0
        if (!dtstart_prop || !obs.name ||
2317
0
            obs.offset_from == INT_MAX || obs.offset_to == INT_MAX) {
2318
0
            icalarray_free(rdates);
2319
0
            continue;
2320
0
        }
2321
2322
        /* Adjust DTSTART observance to UTC */
2323
0
        icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
2324
0
        (void)icaltime_set_timezone(&obs.onset, icaltimezone_get_utc_timezone());
2325
2326
        /* Check DTSTART vs window close */
2327
0
        if (need_tzuntil && icaltime_compare(obs.onset, end) >= 0) {
2328
            /* All observances occur on/after window close - remove component */
2329
0
            icalcomponent_remove_component(vtz, comp);
2330
0
            icalcomponent_free(comp);
2331
2332
            /* Nothing else to do */
2333
0
            icalarray_free(rdates);
2334
0
            continue;
2335
0
        }
2336
2337
        /* Check DTSTART vs window open */
2338
0
        r = icaltime_compare(obs.onset, start);
2339
0
        if (r < 0) {
2340
            /* DTSTART is prior to our window open - check it vs tombstone */
2341
0
            if (need_tomb) {
2342
0
                check_tombstone(&tombstone, &obs);
2343
0
            }
2344
2345
            /* Adjust it */
2346
0
            trunc_dtstart = 1;
2347
0
        } else if (r == 0) {
2348
            /* DTSTART is on/after our window open */
2349
0
            need_tomb = 0;
2350
0
        }
2351
2352
0
        if (rrule_prop) {
2353
0
            struct icalrecurrencetype rrule = icalproperty_get_rrule(rrule_prop);
2354
0
            unsigned eternal = (unsigned)icaltime_is_null_time(rrule.until);
2355
0
            icalrecur_iterator *ritr = NULL;
2356
0
            unsigned trunc_until = 0;
2357
2358
            /* Check RRULE duration */
2359
0
            if (!eternal && icaltime_compare(rrule.until, start) < 0) {
2360
                /* RRULE ends prior to our window open -
2361
                   check UNTIL vs tombstone */
2362
0
                obs.onset = rrule.until;
2363
0
                if (need_tomb) {
2364
0
                    check_tombstone(&tombstone, &obs);
2365
0
                }
2366
2367
                /* Remove RRULE */
2368
0
                icalcomponent_remove_property(comp, rrule_prop);
2369
0
                icalproperty_free(rrule_prop);
2370
0
            } else {
2371
                /* RRULE ends on/after our window open */
2372
0
                if (need_tzuntil &&
2373
0
                    (eternal || icaltime_compare(rrule.until, end) >= 0)) {
2374
                    /* RRULE ends after our window close - need to adjust it */
2375
0
                    trunc_until = 1;
2376
0
                }
2377
2378
0
                if (!eternal) {
2379
                    /* Adjust UNTIL to local time (for iterator) */
2380
0
                    icaltime_adjust(&rrule.until, 0, 0, 0, obs.offset_from);
2381
0
                    (void)icaltime_set_timezone(&rrule.until, NULL);
2382
0
                }
2383
2384
0
                ritr = icalrecur_iterator_new(rrule, dtstart);
2385
2386
0
                if (trunc_dtstart) {
2387
                    /* Bump RRULE start to 1 year prior to our window open */
2388
0
                    icaltimetype newstart = dtstart;
2389
0
                    newstart.year  = start.year - 1;
2390
0
                    newstart.month = start.month;
2391
0
                    newstart.day   = start.day;
2392
0
                    (void)icaltime_normalize(newstart);
2393
0
                    icalrecur_iterator_set_start(ritr, newstart);
2394
0
                }
2395
0
            }
2396
2397
            /* Process any RRULE observances within our window */
2398
0
            if (ritr) {
2399
0
                icaltimetype recur, prev_onset;
2400
2401
0
                while (!icaltime_is_null_time(recur = icalrecur_iterator_next(ritr))) {
2402
0
                    unsigned ydiff;
2403
2404
0
                    obs.onset = recur;
2405
2406
                    /* Adjust observance to UTC */
2407
0
                    icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
2408
0
                    (void)icaltime_set_timezone(&obs.onset,
2409
0
                                                icaltimezone_get_utc_timezone());
2410
2411
0
                    if (trunc_until && icaltime_compare(obs.onset, end) >= 0) {
2412
                        /* Observance is on/after window close */
2413
2414
                        /* Check if DSTART is within 1yr of prev onset */
2415
0
                        ydiff = (unsigned)(prev_onset.year - dtstart.year);
2416
0
                        if (ydiff <= 1) {
2417
                            /* Remove RRULE */
2418
0
                            icalcomponent_remove_property(comp, rrule_prop);
2419
0
                            icalproperty_free(rrule_prop);
2420
2421
0
                            if (ydiff) {
2422
                                /* Add previous onset as RDATE */
2423
0
                                struct icaldatetimeperiodtype rdate;
2424
0
                                rdate.time = prev_onset;
2425
0
                                rdate.period = icalperiodtype_null_period();
2426
2427
0
                                prop = icalproperty_new_rdate(rdate);
2428
0
                                icalcomponent_add_property(comp, prop);
2429
0
                            }
2430
0
                        } else {
2431
                            /* Set UNTIL to previous onset */
2432
0
                            rrule.until = prev_onset;
2433
0
                            icalproperty_set_rrule(rrule_prop, rrule);
2434
0
                        }
2435
2436
                        /* We're done */
2437
0
                        break;
2438
0
                    }
2439
2440
                    /* Check observance vs our window open */
2441
0
                    r = icaltime_compare(obs.onset, start);
2442
0
                    if (r < 0) {
2443
                        /* Observance is prior to our window open -
2444
                           check it vs tombstone */
2445
0
                        if (ms_compatible) {
2446
                            /* XXX  We don't want to move DTSTART of the RRULE
2447
                               as Outlook/Exchange doesn't appear to like
2448
                               truncating the frontend of RRULEs */
2449
0
                            need_tomb = 0;
2450
0
                            trunc_dtstart = 0;
2451
0
                            if (proleptic_prop) {
2452
0
                                icalcomponent_remove_property(vtz,
2453
0
                                                              proleptic_prop);
2454
0
                                icalproperty_free(proleptic_prop);
2455
0
                                proleptic_prop = NULL;
2456
0
                            }
2457
0
                        }
2458
0
                        if (need_tomb) {
2459
0
                            check_tombstone(&tombstone, &obs);
2460
0
                        }
2461
0
                    } else {
2462
                        /* Observance is on/after our window open */
2463
0
                        if (r == 0) need_tomb = 0;
2464
2465
0
                        if (trunc_dtstart) {
2466
                            /* Make this observance the new DTSTART */
2467
0
                            icalproperty_set_dtstart(dtstart_prop, recur);
2468
0
                            dtstart = obs.onset;
2469
0
                            trunc_dtstart = 0;
2470
2471
                            /* Check if new DSTART is within 1yr of UNTIL */
2472
0
                            ydiff = (unsigned)(rrule.until.year - recur.year);
2473
0
                            if (!trunc_until && ydiff <= 1) {
2474
                                /* Remove RRULE */
2475
0
                                icalcomponent_remove_property(comp, rrule_prop);
2476
0
                                icalproperty_free(rrule_prop);
2477
2478
0
                                if (ydiff) {
2479
                                    /* Add UNTIL as RDATE */
2480
0
                                    struct icaldatetimeperiodtype rdate;
2481
0
                                    rdate.time = rrule.until;
2482
0
                                    rdate.period = icalperiodtype_null_period();
2483
2484
0
                                    prop = icalproperty_new_rdate(rdate);
2485
0
                                    icalcomponent_add_property(comp, prop);
2486
0
                                }
2487
0
                            }
2488
0
                        }
2489
2490
0
                        if (!trunc_until) {
2491
                            /* We're done */
2492
0
                            break;
2493
0
                        }
2494
2495
                        /* Check if observance is outside 1yr of window close */
2496
0
                        ydiff = (unsigned)(end.year - recur.year);
2497
0
                        if (ydiff > 1) {
2498
                            /* Bump RRULE to restart at 1 year prior to our window close */
2499
0
                            icaltimetype newstart = recur;
2500
0
                            newstart.year  = end.year - 1;
2501
0
                            newstart.month = end.month;
2502
0
                            newstart.day   = end.day;
2503
0
                            (void)icaltime_normalize(newstart);
2504
0
                            icalrecur_iterator_set_start(ritr, newstart);
2505
0
                        }
2506
0
                    }
2507
0
                    prev_onset = obs.onset;
2508
0
                }
2509
0
                icalrecur_iterator_free(ritr);
2510
0
            }
2511
0
        }
2512
2513
        /* Sort the RDATEs by onset */
2514
0
        icalarray_sort(rdates, &rdate_compare);
2515
2516
        /* Check RDATEs */
2517
0
        for (n = 0; n < rdates->num_elements; n++) {
2518
0
            struct rdate *rdate = icalarray_element_at(rdates, n);
2519
2520
            /* RDATEs with a DATE value inherit the time from the DTSTART. */
2521
0
            if (icaltime_is_date(rdate->date.time)) {
2522
0
                rdate->date.time.hour   = dtstart.hour;
2523
0
                rdate->date.time.minute = dtstart.minute;
2524
0
                rdate->date.time.second = dtstart.second;
2525
0
            }
2526
2527
0
            if (n == 0 && icaltime_compare(rdate->date.time, dtstart) == 0) {
2528
                /* RDATE is same as DTSTART - remove it */
2529
0
                icalcomponent_remove_property(comp, rdate->prop);
2530
0
                icalproperty_free(rdate->prop);
2531
0
                continue;
2532
0
            }
2533
2534
0
            obs.onset = rdate->date.time;
2535
2536
            /* Adjust observance to UTC */
2537
0
            icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
2538
0
            (void)icaltime_set_timezone(&obs.onset, icaltimezone_get_utc_timezone());
2539
2540
0
            if (need_tzuntil && icaltime_compare(obs.onset, end) >= 0) {
2541
                /* RDATE is after our window close - remove it */
2542
0
                icalcomponent_remove_property(comp, rdate->prop);
2543
0
                icalproperty_free(rdate->prop);
2544
2545
0
                continue;
2546
0
            }
2547
2548
0
            r = icaltime_compare(obs.onset, start);
2549
0
            if (r < 0) {
2550
                /* RDATE is prior to window open - check it vs tombstone */
2551
0
                if (need_tomb) {
2552
0
                    check_tombstone(&tombstone, &obs);
2553
0
                }
2554
2555
                /* Remove it */
2556
0
                icalcomponent_remove_property(comp, rdate->prop);
2557
0
                icalproperty_free(rdate->prop);
2558
0
            } else {
2559
                /* RDATE is on/after our window open */
2560
0
                if (r == 0) need_tomb = 0;
2561
2562
0
                if (trunc_dtstart) {
2563
                    /* Make this RDATE the new DTSTART */
2564
0
                    icalproperty_set_dtstart(dtstart_prop,
2565
0
                                             rdate->date.time);
2566
0
                    trunc_dtstart = 0;
2567
2568
0
                    icalcomponent_remove_property(comp, rdate->prop);
2569
0
                    icalproperty_free(rdate->prop);
2570
0
                }
2571
0
            }
2572
0
        }
2573
0
        icalarray_free(rdates);
2574
2575
        /* Final check */
2576
0
        if (trunc_dtstart) {
2577
            /* All observances in comp occur prior to window open, remove it
2578
               unless we haven't saved a tombstone comp of this type yet */
2579
0
            if (icalcomponent_isa(comp) == ICAL_XDAYLIGHT_COMPONENT) {
2580
0
                if (!tomb_day) {
2581
0
                    tomb_day = comp;
2582
0
                    comp = NULL;
2583
0
                }
2584
0
            }
2585
0
            else if (!tomb_std) {
2586
0
                tomb_std = comp;
2587
0
                comp = NULL;
2588
0
            }
2589
2590
0
            if (comp) {
2591
0
                icalcomponent_remove_component(vtz, comp);
2592
0
                icalcomponent_free(comp);
2593
0
            }
2594
0
        }
2595
0
    }
2596
2597
0
    if (need_tomb && !icaltime_is_null_time(tombstone.onset)) {
2598
        /* Need to add tombstone component/observance starting at window open
2599
           as long as its not prior to start of TZ data */
2600
0
        icalcomponent *tomb;
2601
0
        icalproperty *prop, *nextp;
2602
2603
        /* Determine which tombstone component we need */
2604
0
        if (tombstone.onset.is_daylight) {
2605
0
            tomb = tomb_day;
2606
0
            tomb_day = NULL;
2607
0
        } else {
2608
0
            tomb = tomb_std;
2609
0
            tomb_std = NULL;
2610
0
        }
2611
2612
        /* Set property values on our tombstone */
2613
0
        for (prop = icalcomponent_get_first_property(tomb, ICAL_ANY_PROPERTY);
2614
0
             prop; prop = nextp) {
2615
2616
0
            nextp = icalcomponent_get_next_property(tomb, ICAL_ANY_PROPERTY);
2617
2618
0
            switch (icalproperty_isa(prop)) {
2619
0
            case ICAL_TZNAME_PROPERTY:
2620
0
                icalproperty_set_tzname(prop, tombstone.name);
2621
0
                break;
2622
0
            case ICAL_TZOFFSETFROM_PROPERTY:
2623
0
                icalproperty_set_tzoffsetfrom(prop, tombstone.offset_from);
2624
0
                break;
2625
0
            case ICAL_TZOFFSETTO_PROPERTY:
2626
0
                icalproperty_set_tzoffsetto(prop, tombstone.offset_to);
2627
0
                break;
2628
0
            case ICAL_DTSTART_PROPERTY:
2629
                /* Adjust window open to local time */
2630
0
                icaltime_adjust(&start, 0, 0, 0, tombstone.offset_from);
2631
0
                (void)icaltime_set_timezone(&start, NULL);
2632
2633
0
                icalproperty_set_dtstart(prop, start);
2634
0
                break;
2635
0
            default:
2636
0
                icalcomponent_remove_property(tomb, prop);
2637
0
                icalproperty_free(prop);
2638
0
                break;
2639
0
            }
2640
0
        }
2641
2642
        /* Remove X-PROLEPTIC-TZNAME as it no longer applies */
2643
0
        if (proleptic_prop) {
2644
0
            icalcomponent_remove_property(vtz, proleptic_prop);
2645
0
            icalproperty_free(proleptic_prop);
2646
0
        }
2647
0
    }
2648
2649
    /* Remove any unused tombstone components */
2650
0
    if (tomb_std) {
2651
0
        icalcomponent_remove_component(vtz, tomb_std);
2652
0
        icalcomponent_free(tomb_std);
2653
0
    }
2654
0
    if (tomb_day) {
2655
0
        icalcomponent_remove_component(vtz, tomb_day);
2656
0
        icalcomponent_free(tomb_day);
2657
0
    }
2658
2659
0
    if (need_tzuntil) {
2660
        /* Add TZUNTIL to VTIMEZONE */
2661
0
        prop = icalcomponent_get_first_property(vtz, ICAL_TZUNTIL_PROPERTY);
2662
2663
0
        if (prop) {
2664
0
            icalproperty_set_tzuntil(prop, end);
2665
0
        } else {
2666
0
            icalcomponent_add_property(vtz, icalproperty_new_tzuntil(end));
2667
0
        }
2668
0
    }
2669
0
}