FreeRDP
Loading...
Searching...
No Matches
TimeZoneNameMapUtils.c
1
20#include <winpr/config.h>
21#include <winpr/assert.h>
22#include <winpr/string.h>
23#include <winpr/synch.h>
24#include <winpr/json.h>
25#include <winpr/path.h>
26#include <winpr/file.h>
27
28#include "../log.h"
29
30#include <string.h>
31
32#define TAG WINPR_TAG("timezone.utils")
33
34#if defined(WITH_TIMEZONE_ICU)
35#include <unicode/ucal.h>
36#else
37#include "WindowsZones.h"
38#endif
39
40#include "TimeZoneNameMap.h"
41
42#if defined(WITH_TIMEZONE_COMPILED)
43#include "TimeZoneNameMap_static.h"
44#endif
45
46typedef struct
47{
48 size_t count;
49 TimeZoneNameMapEntry* entries;
50} TimeZoneNameMapContext;
51
52static TimeZoneNameMapContext tz_context = { 0 };
53
54static void tz_entry_free(TimeZoneNameMapEntry* entry)
55{
56 if (!entry)
57 return;
58 free(entry->DaylightName);
59 free(entry->DisplayName);
60 free(entry->Iana);
61 free(entry->Id);
62 free(entry->StandardName);
63
64 const TimeZoneNameMapEntry empty = { 0 };
65 *entry = empty;
66}
67
68static TimeZoneNameMapEntry tz_entry_clone(const TimeZoneNameMapEntry* entry)
69{
70 TimeZoneNameMapEntry clone = { 0 };
71 if (!entry)
72 return clone;
73
74 if (entry->DaylightName)
75 clone.DaylightName = _strdup(entry->DaylightName);
76 if (entry->DisplayName)
77 clone.DisplayName = _strdup(entry->DisplayName);
78 if (entry->Iana)
79 clone.Iana = _strdup(entry->Iana);
80 if (entry->Id)
81 clone.Id = _strdup(entry->Id);
82 if (entry->StandardName)
83 clone.StandardName = _strdup(entry->StandardName);
84 return clone;
85}
86
87static void tz_context_free(void)
88{
89 for (size_t x = 0; x < tz_context.count; x++)
90 tz_entry_free(&tz_context.entries[x]);
91 free(tz_context.entries);
92 tz_context.count = 0;
93 tz_context.entries = NULL;
94}
95
96#if defined(WITH_TIMEZONE_FROM_FILE) && defined(WITH_WINPR_JSON)
97static char* tz_get_object_str(WINPR_JSON* json, size_t pos, const char* name)
98{
99 WINPR_ASSERT(json);
100 if (!WINPR_JSON_IsObject(json) || !WINPR_JSON_HasObjectItem(json, name))
101 {
102 WLog_WARN(TAG, "Invalid JSON entry at entry %" PRIuz ", missing an Object named '%s'", pos,
103 name);
104 return NULL;
105 }
106 WINPR_JSON* obj = WINPR_JSON_GetObjectItem(json, name);
107 WINPR_ASSERT(obj);
108 if (!WINPR_JSON_IsString(obj))
109 {
110 WLog_WARN(TAG,
111 "Invalid JSON entry at entry %" PRIuz ", Object named '%s': Not of type string",
112 pos, name);
113 return NULL;
114 }
115
116 const char* str = WINPR_JSON_GetStringValue(obj);
117 if (!str)
118 {
119 WLog_WARN(TAG, "Invalid JSON entry at entry %" PRIuz ", Object named '%s': NULL string",
120 pos, name);
121 return NULL;
122 }
123
124 return _strdup(str);
125}
126
127static BOOL tz_parse_json_entry(WINPR_JSON* json, size_t pos, TimeZoneNameMapEntry* entry)
128{
129 WINPR_ASSERT(entry);
130 if (!json || !WINPR_JSON_IsObject(json))
131 {
132 WLog_WARN(TAG, "Invalid JSON entry at entry %" PRIuz ", expected an array", pos);
133 return FALSE;
134 }
135
136 entry->Id = tz_get_object_str(json, pos, "Id");
137 entry->StandardName = tz_get_object_str(json, pos, "StandardName");
138 entry->DisplayName = tz_get_object_str(json, pos, "DisplayName");
139 entry->DaylightName = tz_get_object_str(json, pos, "DaylightName");
140 entry->Iana = tz_get_object_str(json, pos, "Iana");
141 if (!entry->Id || !entry->StandardName || !entry->DisplayName || !entry->DaylightName ||
142 !entry->Iana)
143 {
144 tz_entry_free(entry);
145 return FALSE;
146 }
147 return TRUE;
148}
149
150static WINPR_JSON* load_timezones_from_file(const char* filename)
151{
152 INT64 jstrlen = 0;
153 char* jstr = NULL;
154 WINPR_JSON* json = NULL;
155 FILE* fp = winpr_fopen(filename, "r");
156 if (!fp)
157 {
158 WLog_WARN(TAG, "Timezone resource file '%s' does not exist or is not readable", filename);
159 return NULL;
160 }
161
162 if (_fseeki64(fp, 0, SEEK_END) < 0)
163 {
164 WLog_WARN(TAG, "Timezone resource file '%s' seek failed", filename);
165 goto end;
166 }
167 jstrlen = _ftelli64(fp);
168 if (jstrlen < 0)
169 {
170 WLog_WARN(TAG, "Timezone resource file '%s' invalid length %" PRId64, filename, jstrlen);
171 goto end;
172 }
173 if (_fseeki64(fp, 0, SEEK_SET) < 0)
174 {
175 WLog_WARN(TAG, "Timezone resource file '%s' seek failed", filename);
176 goto end;
177 }
178
179 jstr = calloc(WINPR_ASSERTING_INT_CAST(size_t, jstrlen + 1), sizeof(char));
180 if (!jstr)
181 {
182 WLog_WARN(TAG, "Timezone resource file '%s' failed to allocate buffer of size %" PRId64,
183 filename, jstrlen);
184 goto end;
185 }
186
187 if (fread(jstr, WINPR_ASSERTING_INT_CAST(size_t, jstrlen), sizeof(char), fp) != 1)
188 {
189 WLog_WARN(TAG, "Timezone resource file '%s' failed to read buffer of size %" PRId64,
190 filename, jstrlen);
191 goto end;
192 }
193
194 json = WINPR_JSON_ParseWithLength(jstr, WINPR_ASSERTING_INT_CAST(size_t, jstrlen));
195 if (!json)
196 WLog_WARN(TAG, "Timezone resource file '%s' is not a valid JSON file", filename);
197end:
198 fclose(fp);
199 free(jstr);
200 return json;
201}
202#endif
203
204static BOOL reallocate_context(TimeZoneNameMapContext* context, size_t size_to_add)
205{
206 {
207 TimeZoneNameMapEntry* tmp = realloc(context->entries, (context->count + size_to_add) *
208 sizeof(TimeZoneNameMapEntry));
209 if (!tmp)
210 {
211 WLog_WARN(TAG,
212 "Failed to reallocate TimeZoneNameMapEntry::entries to %" PRIuz " elements",
213 context->count + size_to_add);
214 return FALSE;
215 }
216 const size_t offset = context->count;
217 context->entries = tmp;
218 context->count += size_to_add;
219
220 memset(&context->entries[offset], 0, size_to_add * sizeof(TimeZoneNameMapEntry));
221 }
222 return TRUE;
223}
224
225static BOOL CALLBACK load_timezones(PINIT_ONCE once, PVOID param, PVOID* pvcontext)
226{
227 TimeZoneNameMapContext* context = param;
228 WINPR_ASSERT(context);
229 WINPR_UNUSED(pvcontext);
230 WINPR_UNUSED(once);
231
232 const TimeZoneNameMapContext empty = { 0 };
233 *context = empty;
234
235#if defined(WITH_TIMEZONE_FROM_FILE) && defined(WITH_WINPR_JSON)
236 {
237 WINPR_JSON* json = NULL;
238 char* filename = GetCombinedPath(WINPR_RESOURCE_ROOT, "TimeZoneNameMap.json");
239 if (!filename)
240 {
241 WLog_WARN(TAG, "Could not create WinPR timezone resource filename");
242 goto end;
243 }
244
245 json = load_timezones_from_file(filename);
246 if (!json)
247 goto end;
248
249 if (!WINPR_JSON_IsObject(json))
250 {
251 WLog_WARN(TAG, "Invalid top level JSON type in file %s, expected an array", filename);
252 goto end;
253 }
254
255 WINPR_JSON* obj = WINPR_JSON_GetObjectItem(json, "TimeZoneNameMap");
256 if (!WINPR_JSON_IsArray(obj))
257 {
258 WLog_WARN(TAG, "Invalid top level JSON type in file %s, expected an array", filename);
259 goto end;
260 }
261 const size_t count = WINPR_JSON_GetArraySize(obj);
262 const size_t offset = context->count;
263 if (!reallocate_context(context, count))
264 goto end;
265 for (size_t x = 0; x < count; x++)
266 {
267 WINPR_JSON* entry = WINPR_JSON_GetArrayItem(obj, x);
268 if (!tz_parse_json_entry(entry, x, &context->entries[offset + x]))
269 goto end;
270 }
271
272 end:
273 free(filename);
274 WINPR_JSON_Delete(json);
275 }
276#endif
277
278#if defined(WITH_TIMEZONE_COMPILED)
279 {
280 const size_t offset = context->count;
281 if (!reallocate_context(context, TimeZoneNameMapSize))
282 return FALSE;
283 for (size_t x = 0; x < TimeZoneNameMapSize; x++)
284 context->entries[offset + x] = tz_entry_clone(&TimeZoneNameMap[x]);
285 }
286#endif
287
288 (void)atexit(tz_context_free);
289 return TRUE;
290}
291
292const TimeZoneNameMapEntry* TimeZoneGetAt(size_t index)
293{
294 static INIT_ONCE init_guard = INIT_ONCE_STATIC_INIT;
295
296 InitOnceExecuteOnce(&init_guard, load_timezones, &tz_context, NULL);
297 if (index >= tz_context.count)
298 return NULL;
299 return &tz_context.entries[index];
300}
301
302static const char* return_type(const TimeZoneNameMapEntry* entry, TimeZoneNameType type)
303{
304 WINPR_ASSERT(entry);
305 switch (type)
306 {
307 case TIME_ZONE_NAME_IANA:
308 return entry->Iana;
309 case TIME_ZONE_NAME_ID:
310 return entry->Id;
311 case TIME_ZONE_NAME_STANDARD:
312 return entry->StandardName;
313 case TIME_ZONE_NAME_DISPLAY:
314 return entry->DisplayName;
315 case TIME_ZONE_NAME_DAYLIGHT:
316 return entry->DaylightName;
317 default:
318 return NULL;
319 }
320}
321
322static BOOL iana_cmp(const TimeZoneNameMapEntry* entry, const char* iana)
323{
324 if (!entry || !iana || !entry->Iana)
325 return FALSE;
326 return strcmp(iana, entry->Iana) == 0;
327}
328
329static BOOL id_cmp(const TimeZoneNameMapEntry* entry, const char* id)
330{
331 if (!entry || !id || !entry->Id)
332 return FALSE;
333 return strcmp(id, entry->Id) == 0;
334}
335
336static const char* get_for_type(const char* val, TimeZoneNameType type,
337 BOOL (*cmp)(const TimeZoneNameMapEntry*, const char*))
338{
339 WINPR_ASSERT(val);
340 WINPR_ASSERT(cmp);
341
342 size_t index = 0;
343 while (TRUE)
344 {
345 const TimeZoneNameMapEntry* entry = TimeZoneGetAt(index++);
346 if (!entry)
347 return NULL;
348 if (cmp(entry, val))
349 return return_type(entry, type);
350 }
351}
352
353#if defined(WITH_TIMEZONE_ICU)
354static char* get_wzid_icu(const UChar* utzid, size_t utzid_len)
355{
356 char* res = NULL;
357 UErrorCode error = U_ZERO_ERROR;
358
359 int32_t rc = ucal_getWindowsTimeZoneID(utzid, WINPR_ASSERTING_INT_CAST(int32_t, utzid_len),
360 NULL, 0, &error);
361 if ((error == U_BUFFER_OVERFLOW_ERROR) && (rc > 0))
362 {
363 rc++; // make space for '\0'
364 UChar* wzid = calloc((size_t)rc + 1, sizeof(UChar));
365 if (wzid)
366 {
367 UErrorCode error2 = U_ZERO_ERROR;
368 int32_t rc2 = ucal_getWindowsTimeZoneID(
369 utzid, WINPR_ASSERTING_INT_CAST(int32_t, utzid_len), wzid, rc, &error2);
370 if (U_SUCCESS(error2) && (rc2 > 0))
371 res = ConvertWCharNToUtf8Alloc(wzid, (size_t)rc, NULL);
372 free(wzid);
373 }
374 }
375 return res;
376}
377
378static char* get(const char* iana)
379{
380 size_t utzid_len = 0;
381 UChar* utzid = ConvertUtf8ToWCharAlloc(iana, &utzid_len);
382 if (!utzid)
383 return NULL;
384
385 char* wzid = get_wzid_icu(utzid, utzid_len);
386 free(utzid);
387 return wzid;
388}
389
390static const char* map_fallback(const char* iana, TimeZoneNameType type)
391{
392 char* wzid = get(iana);
393 if (!wzid)
394 return NULL;
395
396 const char* res = get_for_type(wzid, type, id_cmp);
397 free(wzid);
398 return res;
399}
400#else
401static const char* map_fallback(const char* iana, WINPR_ATTR_UNUSED TimeZoneNameType type)
402{
403 if (!iana)
404 return NULL;
405
406 for (size_t x = 0; x < WindowsZonesNrElements; x++)
407 {
408 const WINDOWS_TZID_ENTRY* const entry = &WindowsZones[x];
409 if (strchr(entry->tzid, ' '))
410 {
411 const char* res = NULL;
412 char* tzid = _strdup(entry->tzid);
413 char* ctzid = tzid;
414 while (tzid)
415 {
416 char* space = strchr(tzid, ' ');
417 if (space)
418 *space++ = '\0';
419 if (strcmp(tzid, iana) == 0)
420 {
421 res = entry->windows;
422 break;
423 }
424 tzid = space;
425 }
426 free(ctzid);
427 if (res)
428 return res;
429 }
430 else if (strcmp(entry->tzid, iana) == 0)
431 return entry->windows;
432 }
433
434 return NULL;
435}
436#endif
437
438const char* TimeZoneIanaToWindows(const char* iana, TimeZoneNameType type)
439{
440 if (!iana)
441 return NULL;
442
443 const char* val = get_for_type(iana, type, iana_cmp);
444 if (val)
445 return val;
446
447 const char* wzid = map_fallback(iana, type);
448 if (!wzid)
449 return NULL;
450
451 return get_for_type(wzid, type, id_cmp);
452}
WINPR_API BOOL WINPR_JSON_HasObjectItem(const WINPR_JSON *object, const char *string)
Check if JSON has an object matching the name.
Definition json.c:209
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItem(const WINPR_JSON *object, const char *string)
Return a pointer to an JSON object item.
Definition json.c:183
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
Definition json.c:348
WINPR_API WINPR_JSON * WINPR_JSON_GetArrayItem(const WINPR_JSON *array, size_t index)
Return a pointer to an item in the array.
Definition json.c:154
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
Definition json.c:124
WINPR_API BOOL WINPR_JSON_IsObject(const WINPR_JSON *item)
Check if JSON item is of type Object.
Definition json.c:372
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition json.c:233
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition json.c:143
WINPR_API size_t WINPR_JSON_GetArraySize(const WINPR_JSON *array)
Get the number of arrayitems from an array.
Definition json.c:168
WINPR_API BOOL WINPR_JSON_IsArray(const WINPR_JSON *item)
Check if JSON item is of type Array.
Definition json.c:360