diff --git a/winpr/libwinpr/timezone/CMakeLists.txt b/winpr/libwinpr/timezone/CMakeLists.txt index c877e1890..d54e3b727 100644 --- a/winpr/libwinpr/timezone/CMakeLists.txt +++ b/winpr/libwinpr/timezone/CMakeLists.txt @@ -15,12 +15,30 @@ # See the License for the specific language governing permissions and # limitations under the License. +option(WITH_TIMEZONE_COMPILED "Use compiled in timezone definitions" ON) +if (WITH_TIMEZONE_COMPILED) + winpr_definition_add(-DWITH_TIMEZONE_COMPILED) +endif() + +include(CMakeDependentOption) +cmake_dependent_option(WITH_TIMEZONE_FROM_FILE "Use timezone definitions from JSON file" OFF WITH_WINPR_JSON OFF) +if (WITH_TIMEZONE_FROM_FILE) + set(WINPR_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/WinPR) + winpr_definition_add(-DWINPR_RESOURCE_ROOT="${WINPR_RESOURCE_ROOT}") + winpr_definition_add(-DWITH_TIMEZONE_FROM_FILE) +endif() + set(SRCS - TimeZoneNameMap.c - TimeZoneNameMap.h TimeZoneNameMapUtils.c + TimeZoneNameMap.h timezone.c ) +if (WITH_TIMEZONE_COMPILED) + list(APPEND SRCS + TimeZoneNameMap.c + ) +endif() + if (NOT WIN32) list(APPEND SRCS TimeZoneIanaAbbrevMap.c diff --git a/winpr/libwinpr/timezone/TimeZoneNameMap.h b/winpr/libwinpr/timezone/TimeZoneNameMap.h index bd5f8fe87..60841ad90 100644 --- a/winpr/libwinpr/timezone/TimeZoneNameMap.h +++ b/winpr/libwinpr/timezone/TimeZoneNameMap.h @@ -34,11 +34,11 @@ typedef enum typedef struct { - const char* Id; - const char* StandardName; - const char* DisplayName; - const char* DaylightName; - const char* Iana; + char* Id; + char* StandardName; + char* DisplayName; + char* DaylightName; + char* Iana; } TimeZoneNameMapEntry; const TimeZoneNameMapEntry* TimeZoneGetAt(size_t index); diff --git a/winpr/libwinpr/timezone/TimeZoneNameMapUtils.c b/winpr/libwinpr/timezone/TimeZoneNameMapUtils.c index 70660db2a..1fd58792e 100644 --- a/winpr/libwinpr/timezone/TimeZoneNameMapUtils.c +++ b/winpr/libwinpr/timezone/TimeZoneNameMapUtils.c @@ -21,9 +21,16 @@ #include #include #include +#include +#include +#include + +#include "../log.h" #include +#define TAG WINPR_TAG("timezone.utils") + #if defined(WITH_TIMEZONE_ICU) #include #else @@ -38,28 +45,272 @@ typedef struct TimeZoneNameMapEntry* entries; } TimeZoneNameMapContext; +static TimeZoneNameMapContext tz_context = { 0 }; + +static void tz_entry_free(TimeZoneNameMapEntry* entry) +{ + if (!entry) + return; + free(entry->DaylightName); + free(entry->DisplayName); + free(entry->Iana); + free(entry->Id); + free(entry->StandardName); + + const TimeZoneNameMapEntry empty = { 0 }; + *entry = empty; +} + +static TimeZoneNameMapEntry tz_entry_clone(const TimeZoneNameMapEntry* entry) +{ + TimeZoneNameMapEntry clone = { 0 }; + if (!entry) + return clone; + + if (entry->DaylightName) + clone.DaylightName = _strdup(entry->DaylightName); + if (entry->DisplayName) + clone.DisplayName = _strdup(entry->DisplayName); + if (entry->Iana) + clone.Iana = _strdup(entry->Iana); + if (entry->Id) + clone.Id = _strdup(entry->Id); + if (entry->StandardName) + clone.StandardName = _strdup(entry->StandardName); + return clone; +} + +static void tz_context_free(void) +{ + for (size_t x = 0; x < tz_context.count; x++) + tz_entry_free(&tz_context.entries[x]); + free(tz_context.entries); + tz_context.count = 0; + tz_context.entries = NULL; +} + +#if defined(WITH_TIMEZONE_FROM_FILE) && defined(WITH_WINPR_JSON) +static BOOL tz_parse_json_entry(WINPR_JSON* json, size_t pos, TimeZoneNameMapEntry* entry) +{ + WINPR_ASSERT(entry); + if (!json || !WINPR_JSON_IsArray(json)) + { + WLog_WARN(TAG, "Invalid JSON entry at entry %" PRIuz ", expected an array", pos); + return FALSE; + } + const size_t count = WINPR_JSON_GetArraySize(json); + if (count != 5) + { + WLog_WARN(TAG, + "Invalid JSON entry at entry %" PRIuz + ", expected an array of 5 elements, got %" PRIuz, + pos, count); + return FALSE; + } + + for (size_t x = 0; x < count; x++) + { + WINPR_JSON* jstr = WINPR_JSON_GetArrayItem(json, x); + if (!jstr || !WINPR_JSON_IsString(jstr)) + { + WLog_WARN( + TAG, "Invalid JSON entry at entry %" PRIuz ", element %" PRIuz " expected a string", + pos, x); + tz_entry_free(entry); + return FALSE; + } + + const char* str = WINPR_JSON_GetStringValue(jstr); + if (!str) + { + WLog_WARN(TAG, + "Invalid JSON entry at entry %" PRIuz ", element %" PRIuz + " expected a value, got a NULL string", + pos, x); + tz_entry_free(entry); + return FALSE; + } + + char* copy = _strdup(str); + if (!copy) + { + WLog_WARN(TAG, "Failed to copy string at entry %" PRIuz ", element %" PRIuz, pos, x); + tz_entry_free(entry); + return FALSE; + } + switch (x) + { + case 0: + entry->Id = copy; + break; + case 1: + entry->StandardName = copy; + break; + case 2: + entry->DisplayName = copy; + break; + case 3: + entry->DaylightName = copy; + break; + case 4: + entry->Iana = copy; + break; + default: + free(copy); + tz_entry_free(entry); + return FALSE; + } + } + return TRUE; +} + +static WINPR_JSON* load_timezones_from_file(const char* filename) +{ + INT64 jstrlen = 0; + char* jstr = NULL; + WINPR_JSON* json = NULL; + FILE* fp = winpr_fopen(filename, "r"); + if (!fp) + { + WLog_WARN(TAG, "Timezone resource file '%s' does not exist or is not readable", filename); + return NULL; + } + + if (_fseeki64(fp, 0, SEEK_END) < 0) + { + WLog_WARN(TAG, "Timezone resource file '%s' seek failed", filename); + goto end; + } + jstrlen = _ftelli64(fp); + if (jstrlen < 0) + { + WLog_WARN(TAG, "Timezone resource file '%s' invalid length %" PRId64, filename, jstrlen); + goto end; + } + if (_fseeki64(fp, 0, SEEK_SET) < 0) + { + WLog_WARN(TAG, "Timezone resource file '%s' seek failed", filename); + goto end; + } + + jstr = calloc(jstrlen + 1, sizeof(char)); + if (!jstr) + { + WLog_WARN(TAG, "Timezone resource file '%s' failed to allocate buffer of size %" PRId64, + filename, jstrlen); + goto end; + } + + if (fread(jstr, jstrlen, sizeof(char), fp) != 1) + { + WLog_WARN(TAG, "Timezone resource file '%s' failed to read buffer of size %" PRId64, + filename, jstrlen); + goto end; + } + + json = WINPR_JSON_ParseWithLength(jstr, jstrlen); + if (!json) + WLog_WARN(TAG, "Timezone resource file '%s' is not a valid JSON file", filename); +end: + fclose(fp); + free(jstr); + return json; +} +#endif + +static BOOL reallocate_context(TimeZoneNameMapContext* context, size_t size_to_add) +{ + { + TimeZoneNameMapEntry* tmp = realloc(context->entries, (context->count + size_to_add) * + sizeof(TimeZoneNameMapEntry)); + if (!tmp) + { + WLog_WARN(TAG, + "Failed to reallocate TimeZoneNameMapEntry::entries to %" PRIuz " elements", + context->count + size_to_add); + return FALSE; + } + const size_t offset = context->count; + context->entries = tmp; + context->count += size_to_add; + + memset(&context->entries[offset], 0, size_to_add * sizeof(TimeZoneNameMapEntry)); + } + return TRUE; +} + static BOOL CALLBACK load_timezones(PINIT_ONCE once, PVOID param, PVOID* pvcontext) { - // Do not expose these, only used internally. - extern const TimeZoneNameMapEntry TimeZoneNameMap[]; - extern const size_t TimeZoneNameMapSize; - - TimeZoneNameMapContext* context = pvcontext; + TimeZoneNameMapContext* context = param; WINPR_ASSERT(context); + WINPR_UNUSED(pvcontext); + WINPR_UNUSED(once); - context->count = TimeZoneNameMapSize; - context->entries = TimeZoneNameMap; + const TimeZoneNameMapContext empty = { 0 }; + *context = empty; + +#if defined(WITH_TIMEZONE_FROM_FILE) && defined(WITH_WINPR_JSON) + { + char* filename = GetCombinedPath(WINPR_RESOURCE_ROOT, "TimeZoneNameMap.json"); + if (!filename) + { + WLog_WARN(TAG, "Could not create WinPR timezone resource filename"); + goto end; + } + + WINPR_JSON* json = load_timezones_from_file(filename); + if (!json) + goto end; + + if (!WINPR_JSON_IsArray(json)) + { + WLog_WARN(TAG, "Invalid top level JSON type in file %s, expected an array", filename); + goto end; + } + + const size_t count = WINPR_JSON_GetArraySize(json); + const size_t offset = context->count; + if (!reallocate_context(context, count)) + goto end; + for (size_t x = 0; x < count; x++) + { + WINPR_JSON* entry = WINPR_JSON_GetArrayItem(json, x); + if (!tz_parse_json_entry(entry, x, &context->entries[offset + x])) + goto end; + } + + end: + free(filename); + WINPR_JSON_Delete(json); + } +#endif + +#if defined(WITH_TIMEZONE_COMPILED) + { + // Do not expose these, only used internally. + extern const TimeZoneNameMapEntry TimeZoneNameMap[]; + extern const size_t TimeZoneNameMapSize; + + const size_t offset = context->count; + if (!reallocate_context(context, TimeZoneNameMapSize)) + return FALSE; + for (size_t x = 0; x < TimeZoneNameMapSize; x++) + context->entries[offset + x] = tz_entry_clone(&TimeZoneNameMap[x]); + } +#endif + + (void)atexit(tz_context_free); + return TRUE; } const TimeZoneNameMapEntry* TimeZoneGetAt(size_t index) { static INIT_ONCE init_guard = INIT_ONCE_STATIC_INIT; - static TimeZoneNameMapContext context = { 0 }; - InitOnceExecuteOnce(&init_guard, load_timezones, NULL, &context); - if (index >= context.count) + InitOnceExecuteOnce(&init_guard, load_timezones, &tz_context, NULL); + if (index >= tz_context.count) return NULL; - return &context.entries[index]; + return &tz_context.entries[index]; } static const char* return_type(const TimeZoneNameMapEntry* entry, TimeZoneNameType type)