Coverage Report

Created: 2023-09-25 06:56

/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
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#include <winpr/config.h>
21
22
#include <winpr/environment.h>
23
#include <winpr/wtypes.h>
24
#include <winpr/timezone.h>
25
#include <winpr/crt.h>
26
#include <winpr/file.h>
27
#include "../log.h"
28
29
#define TAG WINPR_TAG("timezone")
30
31
#ifndef _WIN32
32
33
#include <time.h>
34
#include <unistd.h>
35
36
#include "TimeZones.h"
37
#include "WindowsZones.h"
38
39
static UINT64 winpr_windows_gmtime(void)
40
0
{
41
0
  time_t unix_time;
42
0
  UINT64 windows_time;
43
0
  time(&unix_time);
44
45
0
  if (unix_time < 0)
46
0
    return 0;
47
48
0
  windows_time = (UINT64)unix_time;
49
0
  windows_time *= 10000000;
50
0
  windows_time += 621355968000000000ULL;
51
0
  return windows_time;
52
0
}
53
54
static char* winpr_read_unix_timezone_identifier_from_file(FILE* fp)
55
0
{
56
0
  const INT CHUNK_SIZE = 32;
57
0
  INT64 rc, read = 0, length = CHUNK_SIZE;
58
0
  char* tzid = NULL;
59
60
0
  tzid = (char*)malloc(length);
61
0
  if (!tzid)
62
0
    return NULL;
63
64
0
  do
65
0
  {
66
0
    rc = fread(tzid + read, 1, length - read - 1, fp);
67
0
    if (rc > 0)
68
0
      read += rc;
69
70
0
    if (read < (length - 1))
71
0
      break;
72
73
0
    length += CHUNK_SIZE;
74
0
    char* tmp = (char*)realloc(tzid, length);
75
0
    if (!tmp)
76
0
    {
77
0
      free(tzid);
78
0
      return NULL;
79
0
    }
80
81
0
    tzid = tmp;
82
0
  } while (rc > 0);
83
84
0
  if (ferror(fp))
85
0
  {
86
0
    free(tzid);
87
0
    return NULL;
88
0
  }
89
90
0
  tzid[read] = '\0';
91
0
  if (read > 0)
92
0
  {
93
0
    if (tzid[read - 1] == '\n')
94
0
      tzid[read - 1] = '\0';
95
0
  }
96
97
0
  return tzid;
98
0
}
99
100
static char* winpr_get_timezone_from_link(const char* links[], size_t count)
101
0
{
102
0
  const char* _links[] = { "/etc/localtime", "/etc/TZ" };
103
0
  size_t x;
104
105
0
  if (links == NULL)
106
0
  {
107
0
    links = _links;
108
0
    count = ARRAYSIZE(_links);
109
0
  }
110
111
  /*
112
   * On linux distros such as Redhat or Archlinux, a symlink at /etc/localtime
113
   * will point to /usr/share/zoneinfo/region/place where region/place could be
114
   * America/Montreal for example.
115
   * Some distributions do have to symlink at /etc/TZ.
116
   */
117
118
0
  for (x = 0; x < count; x++)
119
0
  {
120
0
    char* tzid = NULL;
121
0
    const char* link = links[x];
122
0
    char* buf = realpath(link, NULL);
123
124
0
    if (buf)
125
0
    {
126
0
      size_t i;
127
0
      size_t sep = 0;
128
0
      size_t alloc = 0;
129
0
      size_t pos = 0;
130
0
      size_t len = pos = strlen(buf);
131
132
      /* find the position of the 2nd to last "/" */
133
0
      for (i = 1; i <= len; i++)
134
0
      {
135
0
        const size_t curpos = len - i;
136
0
        const char cur = buf[curpos];
137
138
0
        if (cur == '/')
139
0
          sep++;
140
0
        if (sep >= 2)
141
0
        {
142
0
          alloc = i;
143
0
          pos = len - i + 1;
144
0
          break;
145
0
        }
146
0
      }
147
148
0
      if ((len == 0) || (sep != 2))
149
0
        goto end;
150
151
0
      tzid = (char*)calloc(alloc + 1, sizeof(char));
152
153
0
      if (!tzid)
154
0
        goto end;
155
156
0
      strncpy(tzid, &buf[pos], alloc);
157
0
      WLog_DBG(TAG, "tzid: %s", tzid);
158
0
      goto end;
159
0
    }
160
161
0
  end:
162
0
    free(buf);
163
0
    if (tzid)
164
0
      return tzid;
165
0
  }
166
167
0
  return NULL;
168
0
}
169
170
#if defined(ANDROID)
171
#include "../utils/android.h"
172
173
static char* winpr_get_android_timezone_identifier(void)
174
{
175
  char* tzid = NULL;
176
  JNIEnv* jniEnv;
177
178
  /* Preferred: Try to get identifier from java TimeZone class */
179
  if (jniVm && ((*jniVm)->GetEnv(jniVm, (void**)&jniEnv, JNI_VERSION_1_6) == JNI_OK))
180
  {
181
    const char* raw;
182
    jclass jObjClass;
183
    jobject jObj;
184
    jmethodID jDefaultTimezone;
185
    jmethodID jTimezoneIdentifier;
186
    jstring tzJId;
187
    jboolean attached = (*jniVm)->AttachCurrentThread(jniVm, &jniEnv, NULL);
188
    jObjClass = (*jniEnv)->FindClass(jniEnv, "java/util/TimeZone");
189
190
    if (!jObjClass)
191
      goto fail;
192
193
    jDefaultTimezone =
194
        (*jniEnv)->GetStaticMethodID(jniEnv, jObjClass, "getDefault", "()Ljava/util/TimeZone;");
195
196
    if (!jDefaultTimezone)
197
      goto fail;
198
199
    jObj = (*jniEnv)->CallStaticObjectMethod(jniEnv, jObjClass, jDefaultTimezone);
200
201
    if (!jObj)
202
      goto fail;
203
204
    jTimezoneIdentifier =
205
        (*jniEnv)->GetMethodID(jniEnv, jObjClass, "getID", "()Ljava/lang/String;");
206
207
    if (!jTimezoneIdentifier)
208
      goto fail;
209
210
    tzJId = (*jniEnv)->CallObjectMethod(jniEnv, jObj, jTimezoneIdentifier);
211
212
    if (!tzJId)
213
      goto fail;
214
215
    raw = (*jniEnv)->GetStringUTFChars(jniEnv, tzJId, 0);
216
217
    if (raw)
218
      tzid = _strdup(raw);
219
220
    (*jniEnv)->ReleaseStringUTFChars(jniEnv, tzJId, raw);
221
  fail:
222
223
    if (attached)
224
      (*jniVm)->DetachCurrentThread(jniVm);
225
  }
226
227
  /* Fall back to property, might not be available. */
228
  if (!tzid)
229
  {
230
    FILE* fp = popen("getprop persist.sys.timezone", "r");
231
232
    if (fp)
233
    {
234
      tzid = winpr_read_unix_timezone_identifier_from_file(fp);
235
      pclose(fp);
236
    }
237
  }
238
239
  return tzid;
240
}
241
#endif
242
243
static char* winpr_get_unix_timezone_identifier_from_file(void)
244
0
{
245
#if defined(ANDROID)
246
  return winpr_get_android_timezone_identifier();
247
#else
248
0
  FILE* fp;
249
0
  char* tzid = NULL;
250
#if defined(__FreeBSD__) || defined(__OpenBSD__)
251
  fp = winpr_fopen("/var/db/zoneinfo", "r");
252
#else
253
0
  fp = winpr_fopen("/etc/timezone", "r");
254
0
#endif
255
256
0
  if (NULL == fp)
257
0
    return NULL;
258
259
0
  tzid = winpr_read_unix_timezone_identifier_from_file(fp);
260
0
  fclose(fp);
261
0
  if (tzid != NULL)
262
0
    WLog_DBG(TAG, "tzid: %s", tzid);
263
0
  return tzid;
264
0
#endif
265
0
}
266
267
static BOOL winpr_match_unix_timezone_identifier_with_list(const char* tzid, const char* list)
268
0
{
269
0
  char* p;
270
0
  char* list_copy;
271
0
  char* context = NULL;
272
273
0
  list_copy = _strdup(list);
274
275
0
  if (!list_copy)
276
0
    return FALSE;
277
278
0
  p = strtok_s(list_copy, " ", &context);
279
280
0
  while (p != NULL)
281
0
  {
282
0
    if (strcmp(p, tzid) == 0)
283
0
    {
284
0
      free(list_copy);
285
0
      return TRUE;
286
0
    }
287
288
0
    p = strtok_s(NULL, " ", &context);
289
0
  }
290
291
0
  free(list_copy);
292
0
  return FALSE;
293
0
}
294
295
static TIME_ZONE_ENTRY* winpr_detect_windows_time_zone(void)
296
0
{
297
0
  size_t i, j;
298
0
  char *tzid = NULL, *ntzid = NULL;
299
0
  LPCSTR tz = "TZ";
300
301
0
  DWORD nSize = GetEnvironmentVariableA(tz, NULL, 0);
302
0
  if (nSize)
303
0
  {
304
0
    tzid = (char*)malloc(nSize);
305
0
    if (!GetEnvironmentVariableA(tz, tzid, nSize))
306
0
    {
307
0
      free(tzid);
308
0
      tzid = NULL;
309
0
    }
310
0
  }
311
312
0
  if (tzid == NULL)
313
0
    tzid = winpr_get_unix_timezone_identifier_from_file();
314
315
0
  if (tzid == NULL)
316
0
  {
317
0
    tzid = winpr_get_timezone_from_link(NULL, 0);
318
0
  }
319
0
  else
320
0
  {
321
0
    const char* zipath = "/usr/share/zoneinfo/";
322
0
    char buf[1024] = { 0 };
323
0
    const char* links[] = { buf };
324
325
0
    snprintf(buf, ARRAYSIZE(buf), "%s%s", zipath, tzid);
326
0
    ntzid = winpr_get_timezone_from_link(links, 1);
327
0
    if (ntzid != NULL)
328
0
    {
329
0
      free(tzid);
330
0
      tzid = ntzid;
331
0
    }
332
0
  }
333
334
0
  if (tzid == NULL)
335
0
    return NULL;
336
337
0
  WLog_INFO(TAG, "tzid: %s", tzid);
338
339
0
  for (i = 0; i < TimeZoneTableNrElements; i++)
340
0
  {
341
0
    const TIME_ZONE_ENTRY* tze = &TimeZoneTable[i];
342
343
0
    for (j = 0; j < WindowsTimeZoneIdTableNrElements; j++)
344
0
    {
345
0
      const WINDOWS_TZID_ENTRY* wzid = &WindowsTimeZoneIdTable[j];
346
347
0
      if (strcmp(tze->Id, wzid->windows) != 0)
348
0
        continue;
349
350
0
      if (winpr_match_unix_timezone_identifier_with_list(tzid, wzid->tzid))
351
0
      {
352
0
        TIME_ZONE_ENTRY* ctimezone = (TIME_ZONE_ENTRY*)malloc(sizeof(TIME_ZONE_ENTRY));
353
0
        free(tzid);
354
355
0
        if (!ctimezone)
356
0
          return NULL;
357
358
0
        *ctimezone = TimeZoneTable[i];
359
0
        return ctimezone;
360
0
      }
361
0
    }
362
0
  }
363
364
0
  WLog_ERR(TAG, "Unable to find a match for unix timezone: %s", tzid);
365
0
  free(tzid);
366
0
  return NULL;
367
0
}
368
369
static const TIME_ZONE_RULE_ENTRY*
370
winpr_get_current_time_zone_rule(const TIME_ZONE_RULE_ENTRY* rules, UINT32 count)
371
0
{
372
0
  UINT32 i;
373
0
  UINT64 windows_time;
374
0
  windows_time = winpr_windows_gmtime();
375
376
0
  for (i = 0; i < count; i++)
377
0
  {
378
0
    if ((rules[i].TicksStart >= windows_time) && (windows_time >= rules[i].TicksEnd))
379
0
    {
380
      /*WLog_ERR(TAG,  "Got rule %" PRIu32 " from table at %p with count %"PRIu32"", i,
381
       * (void*) rules, count);*/
382
0
      return &rules[i];
383
0
    }
384
0
  }
385
386
0
  WLog_ERR(TAG, "Unable to get current timezone rule");
387
0
  return NULL;
388
0
}
389
390
DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation)
391
0
{
392
0
  time_t t;
393
0
  struct tm tres;
394
0
  struct tm* local_time;
395
0
  TIME_ZONE_ENTRY* dtz = NULL;
396
0
  LPTIME_ZONE_INFORMATION tz = lpTimeZoneInformation;
397
0
  lpTimeZoneInformation->StandardBias = 0;
398
0
  time(&t);
399
0
  local_time = localtime_r(&t, &tres);
400
0
  if (!local_time)
401
0
    goto out_error;
402
403
0
  memset(tz, 0, sizeof(TIME_ZONE_INFORMATION));
404
0
#ifdef WINPR_HAVE_TM_GMTOFF
405
0
  {
406
0
    long bias = -(local_time->tm_gmtoff / 60L);
407
408
0
    if (bias > INT32_MAX)
409
0
      bias = INT32_MAX;
410
411
0
    tz->Bias = (LONG)bias;
412
0
  }
413
#else
414
  tz->Bias = 0;
415
#endif
416
0
  dtz = winpr_detect_windows_time_zone();
417
418
0
  if (dtz != NULL)
419
0
  {
420
0
    const TIME_ZONE_INFORMATION empty = { 0 };
421
422
0
    WLog_DBG(TAG, "tz: Bias=%" PRId32 " sn='%s' dln='%s'", dtz->Bias, dtz->StandardName,
423
0
             dtz->DaylightName);
424
425
0
    *tz = empty;
426
0
    tz->Bias = dtz->Bias;
427
428
0
    if (ConvertUtf8ToWChar(dtz->StandardName, tz->StandardName, ARRAYSIZE(tz->StandardName)) <
429
0
        0)
430
0
    {
431
0
      WLog_ERR(TAG, "StandardName conversion failed - using default");
432
0
      goto out_error;
433
0
    }
434
435
0
    if (ConvertUtf8ToWChar(dtz->DaylightName, tz->DaylightName, ARRAYSIZE(tz->DaylightName)) <
436
0
        0)
437
0
    {
438
0
      WLog_ERR(TAG, "DaylightName conversion failed - using default");
439
0
      goto out_error;
440
0
    }
441
442
0
    if ((dtz->SupportsDST) && (dtz->RuleTableCount > 0))
443
0
    {
444
0
      const TIME_ZONE_RULE_ENTRY* rule =
445
0
          winpr_get_current_time_zone_rule(dtz->RuleTable, dtz->RuleTableCount);
446
447
0
      if (rule != NULL)
448
0
      {
449
0
        tz->DaylightBias = -rule->DaylightDelta;
450
0
        tz->StandardDate = rule->StandardDate;
451
0
        tz->DaylightDate = rule->DaylightDate;
452
0
      }
453
0
    }
454
455
0
    free(dtz);
456
    /* 1 ... TIME_ZONE_ID_STANDARD
457
     * 2 ... TIME_ZONE_ID_DAYLIGHT */
458
0
    return local_time->tm_isdst ? 2 : 1;
459
0
  }
460
461
  /* could not detect timezone, use computed bias from tm_gmtoff */
462
0
  WLog_DBG(TAG, "tz not found, using computed bias %" PRId32 ".", tz->Bias);
463
0
out_error:
464
0
  free(dtz);
465
0
  memcpy(tz->StandardName, L"Client Local Time", sizeof(tz->StandardName));
466
0
  memcpy(tz->DaylightName, L"Client Local Time", sizeof(tz->DaylightName));
467
0
  return 0; /* TIME_ZONE_ID_UNKNOWN */
468
0
}
469
470
BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation)
471
0
{
472
0
  WINPR_UNUSED(lpTimeZoneInformation);
473
0
  return FALSE;
474
0
}
475
476
BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime)
477
0
{
478
0
  WINPR_UNUSED(lpSystemTime);
479
0
  WINPR_UNUSED(lpFileTime);
480
0
  return FALSE;
481
0
}
482
483
BOOL FileTimeToSystemTime(const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime)
484
0
{
485
0
  WINPR_UNUSED(lpFileTime);
486
0
  WINPR_UNUSED(lpSystemTime);
487
0
  return FALSE;
488
0
}
489
490
BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone,
491
                                     LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime)
492
0
{
493
0
  WINPR_UNUSED(lpTimeZone);
494
0
  WINPR_UNUSED(lpUniversalTime);
495
0
  WINPR_UNUSED(lpLocalTime);
496
0
  return FALSE;
497
0
}
498
499
BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
500
                                     LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime)
501
0
{
502
0
  WINPR_UNUSED(lpTimeZoneInformation);
503
0
  WINPR_UNUSED(lpLocalTime);
504
0
  WINPR_UNUSED(lpUniversalTime);
505
0
  return FALSE;
506
0
}
507
508
#endif
509
510
/*
511
 * GetDynamicTimeZoneInformation is provided by the SDK if _WIN32_WINNT >= 0x0600 in SDKs above 7.1A
512
 * and incorrectly if _WIN32_WINNT >= 0x0501 in older SDKs
513
 */
514
#if !defined(_WIN32) ||                                                  \
515
    (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
516
                         !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */
517
518
DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation)
519
0
{
520
0
  WINPR_UNUSED(pTimeZoneInformation);
521
0
  return 0;
522
0
}
523
524
BOOL SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation)
525
0
{
526
0
  WINPR_UNUSED(lpTimeZoneInformation);
527
0
  return FALSE;
528
0
}
529
530
BOOL GetTimeZoneInformationForYear(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi,
531
                                   LPTIME_ZONE_INFORMATION ptzi)
532
0
{
533
0
  WINPR_UNUSED(wYear);
534
0
  WINPR_UNUSED(pdtzi);
535
0
  WINPR_UNUSED(ptzi);
536
0
  return FALSE;
537
0
}
538
539
#endif
540
541
#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601)) /* Windows 7 */
542
543
BOOL SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
544
                                       const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime)
545
0
{
546
0
  WINPR_UNUSED(lpTimeZoneInformation);
547
0
  WINPR_UNUSED(lpUniversalTime);
548
0
  WINPR_UNUSED(lpLocalTime);
549
0
  return FALSE;
550
0
}
551
552
BOOL TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
553
                                       const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime)
554
0
{
555
0
  WINPR_UNUSED(lpTimeZoneInformation);
556
0
  WINPR_UNUSED(lpLocalTime);
557
0
  WINPR_UNUSED(lpUniversalTime);
558
0
  return FALSE;
559
0
}
560
561
#endif
562
563
#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0602)) /* Windows 8 */
564
565
DWORD EnumDynamicTimeZoneInformation(const DWORD dwIndex,
566
                                     PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation)
567
0
{
568
0
  WINPR_UNUSED(dwIndex);
569
0
  WINPR_UNUSED(lpTimeZoneInformation);
570
0
  return 0;
571
0
}
572
573
DWORD GetDynamicTimeZoneInformationEffectiveYears(
574
    const PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear)
575
0
{
576
0
  WINPR_UNUSED(lpTimeZoneInformation);
577
0
  WINPR_UNUSED(FirstYear);
578
0
  WINPR_UNUSED(LastYear);
579
0
  return 0;
580
0
}
581
582
#endif