Coverage Report

Created: 2024-09-08 06:20

/src/FreeRDP/winpr/libwinpr/timezone/timezone.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * Time Zone
4
 *
5
 * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 * Copyright 2024 Armin Novak <anovak@thincast.com>
7
 * Copyright 2024 Thincast Technologies GmbH
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *     http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
22
#include <winpr/config.h>
23
24
#include <winpr/environment.h>
25
#include <winpr/wtypes.h>
26
#include <winpr/timezone.h>
27
#include <winpr/crt.h>
28
#include <winpr/assert.h>
29
#include <winpr/file.h>
30
#include "../log.h"
31
32
79.6k
#define TAG WINPR_TAG("timezone")
33
34
#ifndef MIN
35
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
36
#endif
37
38
#include "TimeZoneNameMap.h"
39
#include "TimeZoneIanaAbbrevMap.h"
40
41
#ifndef _WIN32
42
43
#include <time.h>
44
#include <unistd.h>
45
46
#endif
47
48
#if !defined(_WIN32)
49
static char* winpr_read_unix_timezone_identifier_from_file(FILE* fp)
50
79.6k
{
51
79.6k
  const INT CHUNK_SIZE = 32;
52
79.6k
  INT64 rc = 0;
53
79.6k
  INT64 read = 0;
54
79.6k
  INT64 length = CHUNK_SIZE;
55
79.6k
  char* tzid = NULL;
56
57
79.6k
  tzid = (char*)malloc((size_t)length);
58
79.6k
  if (!tzid)
59
0
    return NULL;
60
61
79.6k
  do
62
79.6k
  {
63
79.6k
    rc = fread(tzid + read, 1, length - read - 1, fp);
64
79.6k
    if (rc > 0)
65
79.6k
      read += rc;
66
67
79.6k
    if (read < (length - 1))
68
79.6k
      break;
69
70
0
    length += CHUNK_SIZE;
71
0
    char* tmp = (char*)realloc(tzid, length);
72
0
    if (!tmp)
73
0
    {
74
0
      free(tzid);
75
0
      return NULL;
76
0
    }
77
78
0
    tzid = tmp;
79
0
  } while (rc > 0);
80
81
79.6k
  if (ferror(fp))
82
0
  {
83
0
    free(tzid);
84
0
    return NULL;
85
0
  }
86
87
79.6k
  tzid[read] = '\0';
88
79.6k
  if (read > 0)
89
79.6k
  {
90
79.6k
    if (tzid[read - 1] == '\n')
91
79.6k
      tzid[read - 1] = '\0';
92
79.6k
  }
93
94
79.6k
  return tzid;
95
79.6k
}
96
97
static char* winpr_get_timezone_from_link(const char* links[], size_t count)
98
79.6k
{
99
79.6k
  const char* _links[] = { "/etc/localtime", "/etc/TZ" };
100
101
79.6k
  if (links == NULL)
102
0
  {
103
0
    links = _links;
104
0
    count = ARRAYSIZE(_links);
105
0
  }
106
107
  /*
108
   * On linux distros such as Redhat or Archlinux, a symlink at /etc/localtime
109
   * will point to /usr/share/zoneinfo/region/place where region/place could be
110
   * America/Montreal for example.
111
   * Some distributions do have to symlink at /etc/TZ.
112
   */
113
114
79.6k
  for (size_t x = 0; x < count; x++)
115
79.6k
  {
116
79.6k
    char* tzid = NULL;
117
79.6k
    const char* link = links[x];
118
79.6k
    char* buf = realpath(link, NULL);
119
120
79.6k
    if (buf)
121
79.6k
    {
122
79.6k
      size_t sep = 0;
123
79.6k
      size_t alloc = 0;
124
79.6k
      size_t pos = 0;
125
79.6k
      size_t len = pos = strlen(buf);
126
127
      /* find the position of the 2nd to last "/" */
128
636k
      for (size_t i = 1; i <= len; i++)
129
636k
      {
130
636k
        const size_t curpos = len - i;
131
636k
        const char cur = buf[curpos];
132
133
636k
        if (cur == '/')
134
159k
          sep++;
135
636k
        if (sep >= 2)
136
79.6k
        {
137
79.6k
          alloc = i;
138
79.6k
          pos = len - i + 1;
139
79.6k
          break;
140
79.6k
        }
141
636k
      }
142
143
79.6k
      if ((len == 0) || (sep != 2))
144
0
        goto end;
145
146
79.6k
      tzid = (char*)calloc(alloc + 1, sizeof(char));
147
148
79.6k
      if (!tzid)
149
0
        goto end;
150
151
79.6k
      strncpy(tzid, &buf[pos], alloc);
152
79.6k
      WLog_DBG(TAG, "tzid: %s", tzid);
153
79.6k
      goto end;
154
79.6k
    }
155
156
79.6k
  end:
157
79.6k
    free(buf);
158
79.6k
    if (tzid)
159
79.6k
      return tzid;
160
79.6k
  }
161
162
0
  return NULL;
163
79.6k
}
164
165
#if defined(ANDROID)
166
#include "../utils/android.h"
167
168
static char* winpr_get_android_timezone_identifier(void)
169
{
170
  char* tzid = NULL;
171
  JNIEnv* jniEnv;
172
173
  /* Preferred: Try to get identifier from java TimeZone class */
174
  if (jniVm && ((*jniVm)->GetEnv(jniVm, (void**)&jniEnv, JNI_VERSION_1_6) == JNI_OK))
175
  {
176
    const char* raw;
177
    jclass jObjClass;
178
    jobject jObj;
179
    jmethodID jDefaultTimezone;
180
    jmethodID jTimezoneIdentifier;
181
    jstring tzJId;
182
    jboolean attached = (*jniVm)->AttachCurrentThread(jniVm, &jniEnv, NULL);
183
    jObjClass = (*jniEnv)->FindClass(jniEnv, "java/util/TimeZone");
184
185
    if (!jObjClass)
186
      goto fail;
187
188
    jDefaultTimezone =
189
        (*jniEnv)->GetStaticMethodID(jniEnv, jObjClass, "getDefault", "()Ljava/util/TimeZone;");
190
191
    if (!jDefaultTimezone)
192
      goto fail;
193
194
    jObj = (*jniEnv)->CallStaticObjectMethod(jniEnv, jObjClass, jDefaultTimezone);
195
196
    if (!jObj)
197
      goto fail;
198
199
    jTimezoneIdentifier =
200
        (*jniEnv)->GetMethodID(jniEnv, jObjClass, "getID", "()Ljava/lang/String;");
201
202
    if (!jTimezoneIdentifier)
203
      goto fail;
204
205
    tzJId = (*jniEnv)->CallObjectMethod(jniEnv, jObj, jTimezoneIdentifier);
206
207
    if (!tzJId)
208
      goto fail;
209
210
    raw = (*jniEnv)->GetStringUTFChars(jniEnv, tzJId, 0);
211
212
    if (raw)
213
      tzid = _strdup(raw);
214
215
    (*jniEnv)->ReleaseStringUTFChars(jniEnv, tzJId, raw);
216
  fail:
217
218
    if (attached)
219
      (*jniVm)->DetachCurrentThread(jniVm);
220
  }
221
222
  /* Fall back to property, might not be available. */
223
  if (!tzid)
224
  {
225
    FILE* fp = popen("getprop persist.sys.timezone", "r");
226
227
    if (fp)
228
    {
229
      tzid = winpr_read_unix_timezone_identifier_from_file(fp);
230
      pclose(fp);
231
    }
232
  }
233
234
  return tzid;
235
}
236
#endif
237
238
static char* winpr_get_unix_timezone_identifier_from_file(void)
239
79.6k
{
240
#if defined(ANDROID)
241
  return winpr_get_android_timezone_identifier();
242
#else
243
79.6k
  FILE* fp = NULL;
244
79.6k
  char* tzid = NULL;
245
#if !defined(WINPR_TIMEZONE_FILE)
246
#error \
247
    "Please define WINPR_TIMEZONE_FILE with the path to your timezone file (e.g. /etc/timezone or similar)"
248
#else
249
79.6k
  fp = winpr_fopen(WINPR_TIMEZONE_FILE, "r");
250
79.6k
#endif
251
252
79.6k
  if (NULL == fp)
253
0
    return NULL;
254
255
79.6k
  tzid = winpr_read_unix_timezone_identifier_from_file(fp);
256
79.6k
  (void)fclose(fp);
257
79.6k
  if (tzid != NULL)
258
79.6k
    WLog_DBG(TAG, "tzid: %s", tzid);
259
79.6k
  return tzid;
260
79.6k
#endif
261
79.6k
}
262
263
static char* winpr_time_zone_from_env(void)
264
79.6k
{
265
79.6k
  LPCSTR tz = "TZ";
266
79.6k
  char* tzid = NULL;
267
268
79.6k
  DWORD nSize = GetEnvironmentVariableA(tz, NULL, 0);
269
79.6k
  if (nSize > 0)
270
0
  {
271
0
    tzid = (char*)calloc(nSize, sizeof(char));
272
0
    if (!tzid)
273
0
      goto fail;
274
0
    if (!GetEnvironmentVariableA(tz, tzid, nSize))
275
0
      goto fail;
276
0
    else if (tzid[0] == ':')
277
0
    {
278
      /* Remove leading colon, see tzset(3) */
279
0
      memmove(tzid, tzid + 1, nSize - sizeof(char));
280
0
    }
281
0
  }
282
283
79.6k
  return tzid;
284
285
0
fail:
286
0
  free(tzid);
287
0
  return NULL;
288
79.6k
}
289
290
static char* winpr_translate_time_zone(const char* tzid)
291
79.6k
{
292
79.6k
  const char* zipath = "/usr/share/zoneinfo/";
293
79.6k
  char* buf = NULL;
294
79.6k
  const char* links[] = { buf };
295
296
79.6k
  if (!tzid)
297
0
    return NULL;
298
299
79.6k
  if (tzid[0] == '/')
300
0
  {
301
    /* Full path given in TZ */
302
0
    links[0] = tzid;
303
0
  }
304
79.6k
  else
305
79.6k
  {
306
79.6k
    size_t bsize = 0;
307
79.6k
    winpr_asprintf(&buf, &bsize, "%s%s", zipath, tzid);
308
79.6k
    links[0] = buf;
309
79.6k
  }
310
311
79.6k
  char* ntzid = winpr_get_timezone_from_link(links, 1);
312
79.6k
  free(buf);
313
79.6k
  return ntzid;
314
79.6k
}
315
316
static char* winpr_guess_time_zone(void)
317
79.6k
{
318
79.6k
  char* tzid = winpr_time_zone_from_env();
319
79.6k
  if (tzid)
320
0
    goto end;
321
79.6k
  tzid = winpr_get_unix_timezone_identifier_from_file();
322
79.6k
  if (tzid)
323
79.6k
    goto end;
324
0
  tzid = winpr_get_timezone_from_link(NULL, 0);
325
0
  if (tzid)
326
0
    goto end;
327
328
79.6k
end:
329
79.6k
{
330
79.6k
  char* ntzid = winpr_translate_time_zone(tzid);
331
79.6k
  if (ntzid)
332
79.6k
  {
333
79.6k
    free(tzid);
334
79.6k
    return ntzid;
335
79.6k
  }
336
0
  return tzid;
337
79.6k
}
338
79.6k
}
339
340
static SYSTEMTIME tm2systemtime(const struct tm* t)
341
159k
{
342
159k
  SYSTEMTIME st = { 0 };
343
344
159k
  if (t)
345
0
  {
346
0
    st.wYear = (WORD)1900 + t->tm_year;
347
0
    st.wMonth = (WORD)t->tm_mon + 1;
348
0
    st.wDay = (WORD)t->tm_mday;
349
0
    st.wDayOfWeek = (WORD)t->tm_wday;
350
0
    st.wHour = (WORD)t->tm_hour;
351
0
    st.wMinute = (WORD)t->tm_min;
352
0
    st.wSecond = (WORD)t->tm_sec;
353
0
    st.wMilliseconds = 0;
354
0
  }
355
159k
  return st;
356
159k
}
357
358
static struct tm systemtime2tm(const SYSTEMTIME* st)
359
0
{
360
0
  struct tm t = { 0 };
361
0
  if (st)
362
0
  {
363
0
    if (st->wYear >= 1900)
364
0
      t.tm_year = st->wYear - 1900;
365
0
    if (st->wMonth > 0)
366
0
      t.tm_mon = st->wMonth - 1;
367
0
    t.tm_mday = st->wDay;
368
0
    t.tm_wday = st->wDayOfWeek;
369
0
    t.tm_hour = st->wHour;
370
0
    t.tm_min = st->wMinute;
371
0
    t.tm_sec = st->wSecond;
372
0
  }
373
0
  return t;
374
0
}
375
376
static LONG get_gmtoff_min(const struct tm* t)
377
159k
{
378
159k
  WINPR_ASSERT(t);
379
159k
  return -(LONG)(t->tm_gmtoff / 60l);
380
159k
}
381
382
static struct tm next_day(const struct tm* start)
383
87.1M
{
384
87.1M
  struct tm cur = *start;
385
87.1M
  cur.tm_hour = 0;
386
87.1M
  cur.tm_min = 0;
387
87.1M
  cur.tm_sec = 0;
388
87.1M
  cur.tm_isdst = -1;
389
87.1M
  cur.tm_mday++;
390
87.1M
  const time_t t = mktime(&cur);
391
87.1M
  localtime_r(&t, &cur);
392
87.1M
  return cur;
393
87.1M
}
394
395
static struct tm adjust_time(const struct tm* start, int hour, int minute)
396
0
{
397
0
  struct tm cur = *start;
398
0
  cur.tm_hour = hour;
399
0
  cur.tm_min = minute;
400
0
  cur.tm_sec = 0;
401
0
  cur.tm_isdst = -1;
402
0
  const time_t t = mktime(&cur);
403
0
  localtime_r(&t, &cur);
404
0
  return cur;
405
0
}
406
407
/* [MS-RDPBCGR] 2.2.1.11.1.1.1.1.1  System Time (TS_SYSTEMTIME) */
408
static WORD get_transition_weekday_occurrence(const SYSTEMTIME* st)
409
0
{
410
0
  WORD count = 0;
411
0
  struct tm start = systemtime2tm(st);
412
413
0
  WORD last = 0;
414
0
  struct tm next = start;
415
0
  next.tm_mday = 1;
416
0
  next.tm_isdst = -1;
417
0
  do
418
0
  {
419
420
0
    const time_t t = mktime(&next);
421
0
    next.tm_mday++;
422
423
0
    struct tm cur = { 0 };
424
0
    localtime_r(&t, &cur);
425
426
0
    if (cur.tm_mon + 1 != st->wMonth)
427
0
      break;
428
429
0
    if (cur.tm_wday == st->wDayOfWeek)
430
0
    {
431
0
      if (cur.tm_mday <= st->wDay)
432
0
        count++;
433
0
      last++;
434
0
    }
435
436
0
  } while (TRUE);
437
438
0
  if (count == last)
439
0
    count = 5;
440
441
0
  return count;
442
0
}
443
444
static SYSTEMTIME tm2transitiontime(const struct tm* cur)
445
159k
{
446
159k
  SYSTEMTIME t = tm2systemtime(cur);
447
159k
  if (cur)
448
0
  {
449
0
    t.wDay = get_transition_weekday_occurrence(&t);
450
0
    t.wYear = 0;
451
0
  }
452
453
159k
  return t;
454
159k
}
455
456
static SYSTEMTIME get_transition_time(const struct tm* start, BOOL toDst)
457
0
{
458
0
  BOOL toggled = FALSE;
459
0
  struct tm first = adjust_time(start, 0, 0);
460
0
  for (int hour = 0; hour < 24; hour++)
461
0
  {
462
0
    struct tm cur = adjust_time(start, hour, 0);
463
0
    if (cur.tm_isdst != first.tm_isdst)
464
0
      toggled = TRUE;
465
466
0
    if (toggled)
467
0
    {
468
0
      if (toDst && (cur.tm_isdst > 0))
469
0
        return tm2transitiontime(&cur);
470
0
      else if (!toDst && (cur.tm_isdst == 0))
471
0
        return tm2transitiontime(&cur);
472
0
    }
473
0
  }
474
0
  return tm2transitiontime(start);
475
0
}
476
477
static BOOL get_transition_date(const struct tm* start, BOOL toDst, SYSTEMTIME* pdate)
478
159k
{
479
159k
  WINPR_ASSERT(start);
480
159k
  WINPR_ASSERT(pdate);
481
482
159k
  *pdate = tm2transitiontime(NULL);
483
484
159k
  if (start->tm_isdst < 0)
485
0
    return FALSE;
486
487
159k
  BOOL val = start->tm_isdst > 0; // the year starts with DST or not
488
159k
  BOOL toggled = FALSE;
489
159k
  struct tm cur = *start;
490
159k
  struct tm last = cur;
491
58.2M
  for (int day = 1; day <= 365; day++)
492
58.1M
  {
493
58.1M
    last = cur;
494
58.1M
    cur = next_day(&cur);
495
58.1M
    const BOOL curDst = (cur.tm_isdst > 0);
496
58.1M
    if ((val != curDst) && !toggled)
497
0
      toggled = TRUE;
498
499
58.1M
    if (toggled)
500
0
    {
501
0
      if (toDst && curDst)
502
0
      {
503
0
        *pdate = get_transition_time(&last, toDst);
504
0
        return TRUE;
505
0
      }
506
0
      if (!toDst && !curDst)
507
0
      {
508
0
        *pdate = get_transition_time(&last, toDst);
509
0
        return TRUE;
510
0
      }
511
0
    }
512
58.1M
  }
513
159k
  return FALSE;
514
159k
}
515
516
static LONG get_bias(const struct tm* start, BOOL dstBias)
517
238k
{
518
238k
  if ((start->tm_isdst > 0) && dstBias)
519
0
    return get_gmtoff_min(start);
520
238k
  if ((start->tm_isdst == 0) && !dstBias)
521
159k
    return get_gmtoff_min(start);
522
79.6k
  if (start->tm_isdst < 0)
523
0
    return get_gmtoff_min(start);
524
525
79.6k
  struct tm cur = *start;
526
29.1M
  for (int day = 1; day <= 365; day++)
527
29.0M
  {
528
29.0M
    cur = next_day(&cur);
529
29.0M
    if ((cur.tm_isdst > 0) && dstBias)
530
0
      return get_gmtoff_min(&cur);
531
29.0M
    else if ((cur.tm_isdst == 0) && !dstBias)
532
0
      return get_gmtoff_min(&cur);
533
29.0M
  }
534
79.6k
  return 0;
535
79.6k
}
536
537
static BOOL map_iana_id(const char* iana, LPDYNAMIC_TIME_ZONE_INFORMATION tz)
538
79.6k
{
539
79.6k
  const char* winId = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_ID);
540
79.6k
  const char* winStd = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_STANDARD);
541
79.6k
  const char* winDst = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_DAYLIGHT);
542
543
79.6k
  if (winStd)
544
79.6k
    ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName));
545
79.6k
  if (winDst)
546
79.6k
    ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName));
547
79.6k
  if (winId)
548
79.6k
    ConvertUtf8ToWChar(winId, tz->TimeZoneKeyName, ARRAYSIZE(tz->TimeZoneKeyName));
549
550
79.6k
  return winId != NULL;
551
79.6k
}
552
553
static const char* weekday2str(WORD wDayOfWeek)
554
0
{
555
0
  switch (wDayOfWeek)
556
0
  {
557
0
    case 0:
558
0
      return "SUNDAY";
559
0
    case 1:
560
0
      return "MONDAY";
561
0
    case 2:
562
0
      return "TUESDAY";
563
0
    case 3:
564
0
      return "WEDNESDAY";
565
0
    case 4:
566
0
      return "THURSDAY";
567
0
    case 5:
568
0
      return "FRIDAY";
569
0
    case 6:
570
0
      return "SATURDAY";
571
0
    default:
572
0
      return "DAY-OF-MAGIC";
573
0
  }
574
0
}
575
576
static char* systemtime2str(const SYSTEMTIME* t, char* buffer, size_t len)
577
159k
{
578
159k
  const SYSTEMTIME empty = { 0 };
579
580
159k
  if (memcmp(t, &empty, sizeof(SYSTEMTIME)) == 0)
581
159k
    (void)_snprintf(buffer, len, "{ not set }");
582
0
  else
583
0
  {
584
0
    (void)_snprintf(buffer, len,
585
0
                    "{ %" PRIu16 "-%" PRIu16 "-%" PRIu16 " [%s] %" PRIu16 ":%" PRIu16
586
0
                    ":%" PRIu16 ".%" PRIu16 "}",
587
0
                    t->wYear, t->wMonth, t->wDay, weekday2str(t->wDayOfWeek), t->wHour,
588
0
                    t->wMinute, t->wSecond, t->wMilliseconds);
589
0
  }
590
159k
  return buffer;
591
159k
}
592
593
static void log_print(wLog* log, DWORD level, const char* file, const char* fkt, size_t line, ...)
594
955k
{
595
955k
  if (!WLog_IsLevelActive(log, level))
596
955k
    return;
597
598
0
  va_list ap;
599
0
  va_start(ap, line);
600
0
  WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, ap);
601
0
  va_end(ap);
602
0
}
603
604
79.6k
#define log_timezone(tzif, result) log_timezone_((tzif), (result), __FILE__, __func__, __LINE__)
605
static void log_timezone_(const DYNAMIC_TIME_ZONE_INFORMATION* tzif, DWORD result, const char* file,
606
                          const char* fkt, size_t line)
607
79.6k
{
608
79.6k
  WINPR_ASSERT(tzif);
609
610
79.6k
  char buffer[130] = { 0 };
611
79.6k
  DWORD level = WLOG_TRACE;
612
79.6k
  wLog* log = WLog_Get(TAG);
613
79.6k
  log_print(log, level, file, fkt, line, "DYNAMIC_TIME_ZONE_INFORMATION {");
614
615
79.6k
  log_print(log, level, file, fkt, line, "  Bias=%" PRIu32, tzif->Bias);
616
79.6k
  ConvertWCharNToUtf8(tzif->StandardName, ARRAYSIZE(tzif->StandardName), buffer,
617
79.6k
                      ARRAYSIZE(buffer));
618
79.6k
  log_print(log, level, file, fkt, line, "  StandardName=%s", buffer);
619
79.6k
  log_print(log, level, file, fkt, line, "  StandardDate=%s",
620
79.6k
            systemtime2str(&tzif->StandardDate, buffer, sizeof(buffer)));
621
79.6k
  log_print(log, level, file, fkt, line, "  StandardBias=%" PRIu32, tzif->StandardBias);
622
623
79.6k
  ConvertWCharNToUtf8(tzif->DaylightName, ARRAYSIZE(tzif->DaylightName), buffer,
624
79.6k
                      ARRAYSIZE(buffer));
625
79.6k
  log_print(log, level, file, fkt, line, "  DaylightName=%s", buffer);
626
79.6k
  log_print(log, level, file, fkt, line, "  DaylightDate=%s",
627
79.6k
            systemtime2str(&tzif->DaylightDate, buffer, sizeof(buffer)));
628
79.6k
  log_print(log, level, file, fkt, line, "  DaylightBias=%" PRIu32, tzif->DaylightBias);
629
79.6k
  ConvertWCharNToUtf8(tzif->TimeZoneKeyName, ARRAYSIZE(tzif->TimeZoneKeyName), buffer,
630
79.6k
                      ARRAYSIZE(buffer));
631
79.6k
  log_print(log, level, file, fkt, line, "  TimeZoneKeyName=%s", buffer);
632
79.6k
  log_print(log, level, file, fkt, line, "  DynamicDaylightTimeDisabled=DST-%s",
633
79.6k
            tzif->DynamicDaylightTimeDisabled ? "disabled" : "enabled");
634
79.6k
  switch (result)
635
79.6k
  {
636
0
    case TIME_ZONE_ID_DAYLIGHT:
637
0
      log_print(log, level, file, fkt, line, "  DaylightDate in use");
638
0
      break;
639
79.6k
    case TIME_ZONE_ID_STANDARD:
640
79.6k
      log_print(log, level, file, fkt, line, "  StandardDate in use");
641
79.6k
      break;
642
0
    default:
643
0
      log_print(log, level, file, fkt, line, "  UnknownDate in use");
644
0
      break;
645
79.6k
  }
646
79.6k
  log_print(log, level, file, fkt, line, "}");
647
79.6k
}
648
649
DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation)
650
39.8k
{
651
39.8k
  DYNAMIC_TIME_ZONE_INFORMATION dyn = { 0 };
652
39.8k
  DWORD rc = GetDynamicTimeZoneInformation(&dyn);
653
39.8k
  lpTimeZoneInformation->Bias = dyn.Bias;
654
39.8k
  lpTimeZoneInformation->DaylightBias = dyn.DaylightBias;
655
39.8k
  lpTimeZoneInformation->DaylightDate = dyn.DaylightDate;
656
39.8k
  lpTimeZoneInformation->StandardBias = dyn.StandardBias;
657
39.8k
  lpTimeZoneInformation->StandardDate = dyn.StandardDate;
658
39.8k
  memcpy(lpTimeZoneInformation->StandardName, dyn.StandardName,
659
39.8k
         sizeof(lpTimeZoneInformation->StandardName));
660
39.8k
  memcpy(lpTimeZoneInformation->DaylightName, dyn.DaylightName,
661
39.8k
         sizeof(lpTimeZoneInformation->DaylightName));
662
39.8k
  return rc;
663
39.8k
}
664
665
BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation)
666
0
{
667
0
  WINPR_UNUSED(lpTimeZoneInformation);
668
0
  return FALSE;
669
0
}
670
671
BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime)
672
0
{
673
0
  WINPR_UNUSED(lpSystemTime);
674
0
  WINPR_UNUSED(lpFileTime);
675
0
  return FALSE;
676
0
}
677
678
BOOL FileTimeToSystemTime(const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime)
679
0
{
680
0
  WINPR_UNUSED(lpFileTime);
681
0
  WINPR_UNUSED(lpSystemTime);
682
0
  return FALSE;
683
0
}
684
685
BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone,
686
                                     LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime)
687
0
{
688
0
  WINPR_UNUSED(lpTimeZone);
689
0
  WINPR_UNUSED(lpUniversalTime);
690
0
  WINPR_UNUSED(lpLocalTime);
691
0
  return FALSE;
692
0
}
693
694
BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
695
                                     LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime)
696
0
{
697
0
  WINPR_UNUSED(lpTimeZoneInformation);
698
0
  WINPR_UNUSED(lpLocalTime);
699
0
  WINPR_UNUSED(lpUniversalTime);
700
0
  return FALSE;
701
0
}
702
703
#endif
704
705
/*
706
 * GetDynamicTimeZoneInformation is provided by the SDK if _WIN32_WINNT >= 0x0600 in SDKs above 7.1A
707
 * and incorrectly if _WIN32_WINNT >= 0x0501 in older SDKs
708
 */
709
#if !defined(_WIN32) ||                                                  \
710
    (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
711
                         !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */
712
713
typedef enum
714
{
715
  HAVE_TRANSITION_DATES = 0,
716
  HAVE_NO_STANDARD_TRANSITION_DATE = 1,
717
  HAVE_NO_DAYLIGHT_TRANSITION_DATE = 2
718
} dyn_transition_result;
719
720
static int dynamic_time_zone_from_localtime(const struct tm* local_time,
721
                                            PDYNAMIC_TIME_ZONE_INFORMATION tz)
722
79.6k
{
723
79.6k
  WINPR_ASSERT(local_time);
724
79.6k
  WINPR_ASSERT(tz);
725
79.6k
  int rc = HAVE_TRANSITION_DATES;
726
727
79.6k
  tz->Bias = get_bias(local_time, FALSE);
728
79.6k
  if (local_time->tm_isdst >= 0)
729
79.6k
  {
730
    /* DST bias is the difference between standard time and DST in minutes */
731
79.6k
    const LONG d = get_bias(local_time, TRUE);
732
79.6k
    tz->DaylightBias = -1 * labs(tz->Bias - d);
733
79.6k
    if (!get_transition_date(local_time, FALSE, &tz->StandardDate))
734
79.6k
      rc |= HAVE_NO_STANDARD_TRANSITION_DATE;
735
79.6k
    if (!get_transition_date(local_time, TRUE, &tz->DaylightDate))
736
79.6k
      rc |= HAVE_NO_DAYLIGHT_TRANSITION_DATE;
737
79.6k
  }
738
79.6k
  return rc;
739
79.6k
}
740
741
DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION tz)
742
79.6k
{
743
79.6k
  BOOL doesNotHaveStandardDate = FALSE;
744
79.6k
  BOOL doesNotHaveDaylightDate = FALSE;
745
79.6k
  const char** list = NULL;
746
79.6k
  char* tzid = NULL;
747
79.6k
  const char* defaultName = "Client Local Time";
748
79.6k
  DWORD res = TIME_ZONE_ID_UNKNOWN;
749
79.6k
  const DYNAMIC_TIME_ZONE_INFORMATION empty = { 0 };
750
751
79.6k
  WINPR_ASSERT(tz);
752
753
79.6k
  *tz = empty;
754
79.6k
  ConvertUtf8ToWChar(defaultName, tz->StandardName, ARRAYSIZE(tz->StandardName));
755
756
79.6k
  const time_t t = time(NULL);
757
79.6k
  struct tm tres = { 0 };
758
79.6k
  struct tm* local_time = localtime_r(&t, &tres);
759
79.6k
  if (!local_time)
760
0
    goto out_error;
761
762
79.6k
  tz->Bias = get_bias(local_time, FALSE);
763
79.6k
  if (local_time->tm_isdst >= 0)
764
79.6k
  {
765
79.6k
    const int rc = dynamic_time_zone_from_localtime(local_time, tz);
766
79.6k
    if (rc & HAVE_NO_STANDARD_TRANSITION_DATE)
767
79.6k
      doesNotHaveStandardDate = TRUE;
768
79.6k
    if (rc & HAVE_NO_DAYLIGHT_TRANSITION_DATE)
769
79.6k
      doesNotHaveDaylightDate = TRUE;
770
79.6k
  }
771
772
79.6k
  tzid = winpr_guess_time_zone();
773
79.6k
  if (!map_iana_id(tzid, tz))
774
0
  {
775
0
    const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone, NULL, 0);
776
0
    list = calloc(len, sizeof(char*));
777
0
    if (!list)
778
0
      goto out_error;
779
0
    const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len);
780
0
    for (size_t x = 0; x < size; x++)
781
0
    {
782
0
      const char* id = list[x];
783
0
      if (map_iana_id(id, tz))
784
0
      {
785
0
        res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
786
0
        break;
787
0
      }
788
0
    }
789
0
  }
790
79.6k
  else
791
79.6k
    res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
792
793
79.6k
  if (doesNotHaveDaylightDate)
794
79.6k
    tz->DaylightBias = 0;
795
796
79.6k
  if (doesNotHaveStandardDate)
797
79.6k
    tz->StandardBias = 0;
798
799
79.6k
out_error:
800
79.6k
  free(tzid);
801
79.6k
  free(list);
802
803
79.6k
  log_timezone(tz, res);
804
79.6k
  return res;
805
79.6k
}
806
807
BOOL SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation)
808
0
{
809
0
  WINPR_UNUSED(lpTimeZoneInformation);
810
0
  return FALSE;
811
0
}
812
813
BOOL GetTimeZoneInformationForYear(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi,
814
                                   LPTIME_ZONE_INFORMATION ptzi)
815
0
{
816
0
  WINPR_UNUSED(wYear);
817
0
  WINPR_UNUSED(pdtzi);
818
0
  WINPR_UNUSED(ptzi);
819
0
  return FALSE;
820
0
}
821
822
#endif
823
824
#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601)) /* Windows 7 */
825
826
BOOL SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
827
                                       const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime)
828
0
{
829
0
  WINPR_UNUSED(lpTimeZoneInformation);
830
0
  WINPR_UNUSED(lpUniversalTime);
831
0
  WINPR_UNUSED(lpLocalTime);
832
0
  return FALSE;
833
0
}
834
835
BOOL TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
836
                                       const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime)
837
0
{
838
0
  WINPR_UNUSED(lpTimeZoneInformation);
839
0
  WINPR_UNUSED(lpLocalTime);
840
0
  WINPR_UNUSED(lpUniversalTime);
841
0
  return FALSE;
842
0
}
843
844
#endif
845
846
#if !defined(_WIN32)
847
848
DWORD EnumDynamicTimeZoneInformation(const DWORD dwIndex,
849
                                     PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation)
850
0
{
851
0
  if (!lpTimeZoneInformation)
852
0
    return ERROR_INVALID_PARAMETER;
853
0
  const DYNAMIC_TIME_ZONE_INFORMATION empty = { 0 };
854
0
  *lpTimeZoneInformation = empty;
855
856
0
  const TimeZoneNameMapEntry* entry = TimeZoneGetAt(dwIndex);
857
0
  if (!entry)
858
0
    return ERROR_NO_MORE_ITEMS;
859
860
0
  if (entry->DaylightName)
861
0
    ConvertUtf8ToWChar(entry->DaylightName, lpTimeZoneInformation->DaylightName,
862
0
                       ARRAYSIZE(lpTimeZoneInformation->DaylightName));
863
0
  if (entry->StandardName)
864
0
    ConvertUtf8ToWChar(entry->StandardName, lpTimeZoneInformation->StandardName,
865
0
                       ARRAYSIZE(lpTimeZoneInformation->StandardName));
866
0
  if (entry->Id)
867
0
    ConvertUtf8ToWChar(entry->Id, lpTimeZoneInformation->TimeZoneKeyName,
868
0
                       ARRAYSIZE(lpTimeZoneInformation->TimeZoneKeyName));
869
870
0
  const time_t t = time(NULL);
871
0
  struct tm tres = { 0 };
872
873
0
  const char* tz = getenv("TZ");
874
0
  char* tzcopy = NULL;
875
0
  if (tz)
876
0
  {
877
0
    size_t tzianalen = 0;
878
0
    winpr_asprintf(&tzcopy, &tzianalen, "TZ=%s", tz);
879
0
  }
880
881
0
  char* tziana = NULL;
882
0
  {
883
0
    size_t tzianalen = 0;
884
0
    winpr_asprintf(&tziana, &tzianalen, "TZ=%s", entry->Iana);
885
0
  }
886
0
  if (tziana)
887
0
    putenv(tziana);
888
889
0
  tzset();
890
0
  struct tm* local_time = localtime_r(&t, &tres);
891
0
  free(tziana);
892
0
  if (tzcopy)
893
0
    putenv(tzcopy);
894
0
  else
895
0
    unsetenv("TZ");
896
0
  free(tzcopy);
897
898
0
  if (local_time)
899
0
    dynamic_time_zone_from_localtime(local_time, lpTimeZoneInformation);
900
901
0
  return ERROR_SUCCESS;
902
0
}
903
904
DWORD GetDynamicTimeZoneInformationEffectiveYears(
905
    const PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear)
906
0
{
907
0
  WINPR_UNUSED(lpTimeZoneInformation);
908
0
  WINPR_UNUSED(FirstYear);
909
0
  WINPR_UNUSED(LastYear);
910
0
  return ERROR_FILE_NOT_FOUND;
911
0
}
912
913
#elif _WIN32_WINNT < 0x0602 /* Windows 8 */
914
DWORD EnumDynamicTimeZoneInformation(const DWORD dwIndex,
915
                                     PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation)
916
{
917
  WINPR_UNUSED(dwIndex);
918
  WINPR_UNUSED(lpTimeZoneInformation);
919
  return ERROR_NO_MORE_ITEMS;
920
}
921
922
DWORD GetDynamicTimeZoneInformationEffectiveYears(
923
    const PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear)
924
{
925
  WINPR_UNUSED(lpTimeZoneInformation);
926
  WINPR_UNUSED(FirstYear);
927
  WINPR_UNUSED(LastYear);
928
  return ERROR_FILE_NOT_FOUND;
929
}
930
#endif