FreeRDP/winpr/libwinpr/timezone/timezone.c

590 lines
13 KiB
C
Raw Normal View History

/**
* WinPR: Windows Portable Runtime
* Time Zone
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
2022-02-16 12:08:00 +03:00
#include <winpr/config.h>
#include <winpr/environment.h>
#include <winpr/wtypes.h>
#include <winpr/timezone.h>
2016-02-23 20:08:18 +03:00
#include <winpr/crt.h>
#include <winpr/file.h>
2016-02-23 20:08:18 +03:00
#include "../log.h"
#define TAG WINPR_TAG("timezone")
#ifndef _WIN32
2016-02-23 20:08:18 +03:00
#include <time.h>
#include <unistd.h>
2019-12-06 11:58:04 +03:00
#include "TimeZones.h"
#include "WindowsZones.h"
2016-02-23 20:08:18 +03:00
static UINT64 winpr_windows_gmtime(void)
2016-02-23 20:08:18 +03:00
{
time_t unix_time;
UINT64 windows_time;
time(&unix_time);
if (unix_time < 0)
return 0;
windows_time = (UINT64)unix_time;
2016-02-23 20:08:18 +03:00
windows_time *= 10000000;
windows_time += 621355968000000000ULL;
return windows_time;
}
2016-02-24 22:16:19 +03:00
static char* winpr_read_unix_timezone_identifier_from_file(FILE* fp)
2016-02-23 20:08:18 +03:00
{
const INT CHUNK_SIZE = 32;
INT64 rc, read = 0, length = CHUNK_SIZE;
char *tmp, *tzid = NULL;
2016-02-23 20:08:18 +03:00
tzid = (char*)malloc(length);
if (!tzid)
return NULL;
2016-02-23 20:08:18 +03:00
do
{
rc = fread(tzid + read, 1, length - read - 1, fp);
read += rc;
if (read < (length - 1))
break;
length += CHUNK_SIZE;
tmp = (char*)realloc(tzid, length);
if (!tmp)
{
free(tzid);
return NULL;
}
2016-02-23 20:08:18 +03:00
tzid = tmp;
} while (rc > 0);
if (ferror(fp))
2016-02-23 20:08:18 +03:00
{
free(tzid);
return NULL;
}
2016-02-23 20:08:18 +03:00
tzid[read] = '\0';
if (tzid[read - 1] == '\n')
tzid[read - 1] = '\0';
2016-02-23 20:08:18 +03:00
return tzid;
}
2016-02-23 20:08:18 +03:00
static char* winpr_get_timezone_from_link(const char* links[], size_t count)
2016-02-24 11:42:12 +03:00
{
const char* _links[] = { "/etc/localtime", "/etc/TZ" };
2016-02-24 11:42:12 +03:00
size_t x;
if (links == NULL)
{
links = _links;
count = ARRAYSIZE(_links);
}
2016-02-24 11:42:12 +03:00
/*
* On linux distros such as Redhat or Archlinux, a symlink at /etc/localtime
* will point to /usr/share/zoneinfo/region/place where region/place could be
* America/Montreal for example.
* Some distributions do have to symlink at /etc/TZ.
*/
for (x = 0; x < count; x++)
2016-02-24 11:42:12 +03:00
{
char* tzid = NULL;
2016-02-24 11:42:12 +03:00
const char* link = links[x];
char* buf = realpath(link, NULL);
2016-02-24 11:42:12 +03:00
if (buf)
2016-02-24 11:42:12 +03:00
{
size_t i;
size_t sep = 0;
size_t alloc = 0;
size_t pos = 0;
size_t len = pos = strlen(buf);
2016-02-24 11:42:12 +03:00
/* find the position of the 2nd to last "/" */
for (i = 1; i <= len; i++)
2016-02-24 11:42:12 +03:00
{
const size_t curpos = len - i;
const char cur = buf[curpos];
if (cur == '/')
sep++;
if (sep >= 2)
{
alloc = i;
pos = len - i + 1;
2016-02-24 11:42:12 +03:00
break;
}
2016-02-24 11:42:12 +03:00
}
if ((len == 0) || (sep != 2))
goto end;
2016-02-24 11:42:12 +03:00
tzid = (char*)calloc(alloc + 1, sizeof(char));
if (!tzid)
goto end;
2016-02-24 11:42:12 +03:00
strncpy(tzid, &buf[pos], alloc);
WLog_DBG(TAG, "%s: tzid: %s", __FUNCTION__, tzid);
goto end;
2016-02-24 11:42:12 +03:00
}
end:
free(buf);
if (tzid)
return tzid;
2016-02-24 11:42:12 +03:00
}
return NULL;
}
#if defined(ANDROID)
#include <jni.h>
static JavaVM* jniVm = NULL;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
jniVm = vm;
return JNI_VERSION_1_6;
}
static char* winpr_get_android_timezone_identifier(void)
{
char* tzid = NULL;
JNIEnv* jniEnv;
/* Preferred: Try to get identifier from java TimeZone class */
if (jniVm && ((*jniVm)->GetEnv(jniVm, (void**)&jniEnv, JNI_VERSION_1_6) == JNI_OK))
{
const char* raw;
jclass jObjClass;
jobject jObj;
jmethodID jDefaultTimezone;
jmethodID jTimezoneIdentifier;
jstring tzJId;
jboolean attached = (*jniVm)->AttachCurrentThread(jniVm, &jniEnv, NULL);
jObjClass = (*jniEnv)->FindClass(jniEnv, "java/util/TimeZone");
if (!jObjClass)
goto fail;
2019-11-06 17:24:51 +03:00
jDefaultTimezone =
(*jniEnv)->GetStaticMethodID(jniEnv, jObjClass, "getDefault", "()Ljava/util/TimeZone;");
if (!jDefaultTimezone)
goto fail;
jObj = (*jniEnv)->CallStaticObjectMethod(jniEnv, jObjClass, jDefaultTimezone);
if (!jObj)
goto fail;
2019-11-06 17:24:51 +03:00
jTimezoneIdentifier =
(*jniEnv)->GetMethodID(jniEnv, jObjClass, "getID", "()Ljava/lang/String;");
if (!jTimezoneIdentifier)
goto fail;
tzJId = (*jniEnv)->CallObjectMethod(jniEnv, jObj, jTimezoneIdentifier);
if (!tzJId)
goto fail;
raw = (*jniEnv)->GetStringUTFChars(jniEnv, tzJId, 0);
if (raw)
tzid = _strdup(raw);
(*jniEnv)->ReleaseStringUTFChars(jniEnv, tzJId, raw);
fail:
if (attached)
(*jniVm)->DetachCurrentThread(jniVm);
}
/* Fall back to property, might not be available. */
if (!tzid)
{
FILE* fp = popen("getprop persist.sys.timezone", "r");
if (fp)
{
tzid = winpr_read_unix_timezone_identifier_from_file(fp);
pclose(fp);
}
}
return tzid;
}
#endif
2016-02-24 22:16:19 +03:00
static char* winpr_get_unix_timezone_identifier_from_file(void)
{
#if defined(ANDROID)
return winpr_get_android_timezone_identifier();
#else
FILE* fp;
char* tzid = NULL;
#if defined(__FreeBSD__) || defined(__OpenBSD__)
fp = winpr_fopen("/var/db/zoneinfo", "r");
#else
fp = winpr_fopen("/etc/timezone", "r");
#endif
2016-02-23 20:08:18 +03:00
if (NULL == fp)
return NULL;
2016-02-23 20:08:18 +03:00
2016-02-24 22:16:19 +03:00
tzid = winpr_read_unix_timezone_identifier_from_file(fp);
2019-11-06 17:24:51 +03:00
fclose(fp);
if (tzid != NULL)
WLog_DBG(TAG, "%s: tzid: %s", __FUNCTION__, tzid);
2016-02-23 20:08:18 +03:00
return tzid;
#endif
2016-02-23 20:08:18 +03:00
}
2016-02-24 22:16:19 +03:00
static BOOL winpr_match_unix_timezone_identifier_with_list(const char* tzid, const char* list)
2016-02-23 20:08:18 +03:00
{
char* p;
char* list_copy;
2020-05-18 11:35:52 +03:00
char* context = NULL;
2016-02-23 20:08:18 +03:00
list_copy = _strdup(list);
2016-02-23 20:08:18 +03:00
if (!list_copy)
return FALSE;
2020-05-18 11:35:52 +03:00
p = strtok_s(list_copy, " ", &context);
2016-02-23 20:08:18 +03:00
while (p != NULL)
{
if (strcmp(p, tzid) == 0)
{
free(list_copy);
return TRUE;
}
2020-05-18 11:35:52 +03:00
p = strtok_s(NULL, " ", &context);
2016-02-23 20:08:18 +03:00
}
free(list_copy);
return FALSE;
}
static TIME_ZONE_ENTRY* winpr_detect_windows_time_zone(void)
2016-02-23 20:08:18 +03:00
{
size_t i, j;
char *tzid = NULL, *ntzid = NULL;
LPCSTR tz = "TZ";
DWORD nSize = GetEnvironmentVariableA(tz, NULL, 0);
if (nSize)
{
tzid = (char*)malloc(nSize);
if (!GetEnvironmentVariableA(tz, tzid, nSize))
{
free(tzid);
tzid = NULL;
}
}
2019-12-06 11:58:04 +03:00
if (tzid == NULL)
tzid = winpr_get_unix_timezone_identifier_from_file();
2016-02-23 20:08:18 +03:00
2016-02-24 11:42:12 +03:00
if (tzid == NULL)
{
tzid = winpr_get_timezone_from_link(NULL, 0);
}
else
{
const char* zipath = "/usr/share/zoneinfo/";
char buf[1024] = { 0 };
const char* links[] = { buf };
snprintf(buf, ARRAYSIZE(buf), "%s%s", zipath, tzid);
ntzid = winpr_get_timezone_from_link(links, 1);
if (ntzid != NULL)
{
free(tzid);
tzid = ntzid;
}
}
2016-02-24 11:42:12 +03:00
2016-02-23 20:08:18 +03:00
if (tzid == NULL)
return NULL;
WLog_INFO(TAG, "tzid: %s", tzid);
2019-12-06 11:58:04 +03:00
for (i = 0; i < TimeZoneTableNrElements; i++)
2016-02-23 20:08:18 +03:00
{
2019-12-06 11:58:04 +03:00
const TIME_ZONE_ENTRY* tze = &TimeZoneTable[i];
for (j = 0; j < WindowsTimeZoneIdTableNrElements; j++)
2016-02-23 20:08:18 +03:00
{
2019-12-06 11:58:04 +03:00
const WINDOWS_TZID_ENTRY* wzid = &WindowsTimeZoneIdTable[j];
if (strcmp(tze->Id, wzid->windows) != 0)
2016-02-23 20:08:18 +03:00
continue;
2019-12-06 11:58:04 +03:00
if (winpr_match_unix_timezone_identifier_with_list(tzid, wzid->tzid))
2016-02-23 20:08:18 +03:00
{
2021-07-29 11:07:04 +03:00
TIME_ZONE_ENTRY* ctimezone = (TIME_ZONE_ENTRY*)malloc(sizeof(TIME_ZONE_ENTRY));
2016-02-23 20:08:18 +03:00
free(tzid);
2021-07-29 11:07:04 +03:00
if (!ctimezone)
2016-02-23 20:08:18 +03:00
return NULL;
2021-07-29 11:07:04 +03:00
*ctimezone = TimeZoneTable[i];
return ctimezone;
2016-02-23 20:08:18 +03:00
}
}
}
2019-11-06 17:24:51 +03:00
WLog_ERR(TAG, "Unable to find a match for unix timezone: %s", tzid);
2016-02-23 20:08:18 +03:00
free(tzid);
return NULL;
}
2019-11-06 17:24:51 +03:00
static const TIME_ZONE_RULE_ENTRY*
winpr_get_current_time_zone_rule(const TIME_ZONE_RULE_ENTRY* rules, UINT32 count)
2016-02-23 20:08:18 +03:00
{
UINT32 i;
2016-02-23 20:08:18 +03:00
UINT64 windows_time;
2016-02-24 22:16:19 +03:00
windows_time = winpr_windows_gmtime();
2016-02-23 20:08:18 +03:00
for (i = 0; i < count; i++)
2016-02-23 20:08:18 +03:00
{
if ((rules[i].TicksStart >= windows_time) && (windows_time >= rules[i].TicksEnd))
{
2019-11-06 17:24:51 +03:00
/*WLog_ERR(TAG, "Got rule %d from table at %p with count %"PRIu32"", i, (void*) rules,
* count);*/
2016-02-23 20:08:18 +03:00
return &rules[i];
}
}
2019-11-06 17:24:51 +03:00
WLog_ERR(TAG, "Unable to get current timezone rule");
2016-02-23 20:08:18 +03:00
return NULL;
}
2014-05-29 19:58:53 +04:00
DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation)
{
2016-02-23 20:08:18 +03:00
time_t t;
2020-05-25 14:56:24 +03:00
struct tm tres;
2016-02-23 20:08:18 +03:00
struct tm* local_time;
2020-06-23 12:53:45 +03:00
TIME_ZONE_ENTRY* dtz = NULL;
2016-02-23 20:08:18 +03:00
LPTIME_ZONE_INFORMATION tz = lpTimeZoneInformation;
lpTimeZoneInformation->StandardBias = 0;
time(&t);
2020-05-25 14:56:24 +03:00
local_time = localtime_r(&t, &tres);
if (!local_time)
goto out_error;
2016-02-23 20:08:18 +03:00
memset(tz, 0, sizeof(TIME_ZONE_INFORMATION));
#ifdef HAVE_TM_GMTOFF
{
long bias = -(local_time->tm_gmtoff / 60L);
if (bias > INT32_MAX)
bias = INT32_MAX;
tz->Bias = (LONG)bias;
}
2016-02-23 20:08:18 +03:00
#else
tz->Bias = 0;
#endif
dtz = winpr_detect_windows_time_zone();
2016-02-23 20:08:18 +03:00
if (dtz != NULL)
2016-02-23 20:08:18 +03:00
{
int status;
2019-11-06 17:24:51 +03:00
WLog_DBG(TAG, "tz: Bias=%" PRId32 " sn='%s' dln='%s'", dtz->Bias, dtz->StandardName,
dtz->DaylightName);
2016-02-23 20:08:18 +03:00
tz->Bias = dtz->Bias;
tz->StandardBias = 0;
tz->DaylightBias = 0;
ZeroMemory(tz->StandardName, sizeof(tz->StandardName));
ZeroMemory(tz->DaylightName, sizeof(tz->DaylightName));
status = MultiByteToWideChar(CP_UTF8, 0, dtz->StandardName, -1, tz->StandardName,
sizeof(tz->StandardName) / sizeof(WCHAR) - 1);
if (status < 1)
{
WLog_ERR(TAG, "StandardName conversion failed - using default");
goto out_error;
}
status = MultiByteToWideChar(CP_UTF8, 0, dtz->DaylightName, -1, tz->DaylightName,
sizeof(tz->DaylightName) / sizeof(WCHAR) - 1);
if (status < 1)
{
WLog_ERR(TAG, "DaylightName conversion failed - using default");
goto out_error;
}
2016-02-23 20:08:18 +03:00
if ((dtz->SupportsDST) && (dtz->RuleTableCount > 0))
{
2019-11-06 17:24:51 +03:00
const TIME_ZONE_RULE_ENTRY* rule =
winpr_get_current_time_zone_rule(dtz->RuleTable, dtz->RuleTableCount);
2016-02-23 20:08:18 +03:00
if (rule != NULL)
{
tz->DaylightBias = -rule->DaylightDelta;
tz->StandardDate = rule->StandardDate;
tz->DaylightDate = rule->DaylightDate;
}
}
free(dtz);
2016-02-23 20:08:18 +03:00
/* 1 ... TIME_ZONE_ID_STANDARD
* 2 ... TIME_ZONE_ID_DAYLIGHT */
2016-02-24 11:43:53 +03:00
return local_time->tm_isdst ? 2 : 1;
2016-02-23 20:08:18 +03:00
}
2016-05-12 11:01:30 +03:00
/* could not detect timezone, use computed bias from tm_gmtoff */
2019-11-06 17:24:51 +03:00
WLog_DBG(TAG, "tz not found, using computed bias %" PRId32 ".", tz->Bias);
out_error:
2016-05-12 11:01:30 +03:00
free(dtz);
memcpy(tz->StandardName, L"Client Local Time", sizeof(tz->StandardName));
memcpy(tz->DaylightName, L"Client Local Time", sizeof(tz->DaylightName));
return 0; /* TIME_ZONE_ID_UNKNOWN */
2014-05-29 19:58:53 +04:00
}
BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation)
{
WINPR_UNUSED(lpTimeZoneInformation);
2014-05-29 19:58:53 +04:00
return FALSE;
}
BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime)
{
WINPR_UNUSED(lpSystemTime);
WINPR_UNUSED(lpFileTime);
2014-05-29 19:58:53 +04:00
return FALSE;
}
BOOL FileTimeToSystemTime(const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime)
2014-05-29 19:58:53 +04:00
{
WINPR_UNUSED(lpFileTime);
WINPR_UNUSED(lpSystemTime);
2014-05-29 19:58:53 +04:00
return FALSE;
}
BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone,
LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime)
2014-05-29 19:58:53 +04:00
{
WINPR_UNUSED(lpTimeZone);
WINPR_UNUSED(lpUniversalTime);
WINPR_UNUSED(lpLocalTime);
2014-05-29 19:58:53 +04:00
return FALSE;
}
BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime)
2014-05-29 19:58:53 +04:00
{
WINPR_UNUSED(lpTimeZoneInformation);
WINPR_UNUSED(lpLocalTime);
WINPR_UNUSED(lpUniversalTime);
2014-05-29 19:58:53 +04:00
return FALSE;
}
#endif
/*
* GetDynamicTimeZoneInformation is provided by the SDK if _WIN32_WINNT >= 0x0600 in SDKs above 7.1A
* and incorrectly if _WIN32_WINNT >= 0x0501 in older SDKs
*/
2019-11-06 17:24:51 +03:00
#if !defined(_WIN32) || \
(defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
!defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */
2014-05-29 19:58:53 +04:00
DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation)
{
WINPR_UNUSED(pTimeZoneInformation);
2014-05-29 19:58:53 +04:00
return 0;
}
BOOL SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation)
{
WINPR_UNUSED(lpTimeZoneInformation);
2014-05-29 19:58:53 +04:00
return FALSE;
}
BOOL GetTimeZoneInformationForYear(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi,
LPTIME_ZONE_INFORMATION ptzi)
2014-05-29 19:58:53 +04:00
{
WINPR_UNUSED(wYear);
WINPR_UNUSED(pdtzi);
WINPR_UNUSED(ptzi);
2014-05-29 19:58:53 +04:00
return FALSE;
}
#endif
#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601)) /* Windows 7 */
BOOL SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime)
2014-05-29 19:58:53 +04:00
{
WINPR_UNUSED(lpTimeZoneInformation);
WINPR_UNUSED(lpUniversalTime);
WINPR_UNUSED(lpLocalTime);
2014-05-29 19:58:53 +04:00
return FALSE;
}
BOOL TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime)
2014-05-29 19:58:53 +04:00
{
WINPR_UNUSED(lpTimeZoneInformation);
WINPR_UNUSED(lpLocalTime);
WINPR_UNUSED(lpUniversalTime);
2014-05-29 19:58:53 +04:00
return FALSE;
}
#endif
#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0602)) /* Windows 8 */
DWORD EnumDynamicTimeZoneInformation(const DWORD dwIndex,
PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation)
2014-05-29 19:58:53 +04:00
{
WINPR_UNUSED(dwIndex);
WINPR_UNUSED(lpTimeZoneInformation);
2014-05-29 19:58:53 +04:00
return 0;
}
2019-11-06 17:24:51 +03:00
DWORD GetDynamicTimeZoneInformationEffectiveYears(
const PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear)
{
WINPR_UNUSED(lpTimeZoneInformation);
WINPR_UNUSED(FirstYear);
WINPR_UNUSED(LastYear);
2014-05-29 19:58:53 +04:00
return 0;
}
#endif