master
c 408 lines 11.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "../libnetdata.h"
4
5 #if defined(OS_WINDOWS)
6 /*
7 * Convert any CodePage to UTF16
8 * Goals:
9 * 1. Destination is always NULL terminated
10 * 2. If the destination buffer is not enough, return as much as possible data (truncate)
11 * 3. Always return the number of wide characters written, including the null terminator
12 */
13
14 size_t any_to_utf16(uint32_t CodePage, wchar_t *dst, size_t dst_size, const char *src, int src_len, bool *truncated) {
15 if(!src || src_len == 0) {
16 // invalid input
17 if(truncated)
18 *truncated = true;
19
20 if(dst && dst_size)
21 *dst = L'\0';
22 return 0;
23 }
24
25 if(!dst || !dst_size) {
26 // the caller wants to know the buffer to allocate for the conversion
27
28 if(truncated)
29 *truncated = true;
30
31 int required = MultiByteToWideChar(CodePage, 0, src, src_len, NULL, 0);
32 if(required <= 0) return 0; // error in the conversion
33
34 // Add 1 for null terminator only if src_len is not -1
35 // so that the caller can call us again to get the entire string (not truncated)
36 return (size_t)required + ((src_len != -1) ? 1 : 0);
37 }
38
39 // do the conversion directly to the destination buffer
40 int rc = MultiByteToWideChar(CodePage, 0, src, src_len, dst, (int)dst_size);
41 if(rc <= 0) {
42 if(truncated)
43 *truncated = true;
44
45 // conversion failed, let's see why...
46 DWORD status = GetLastError();
47 if(status == ERROR_INSUFFICIENT_BUFFER) {
48 // it cannot fit entirely, let's allocate a new buffer to convert it
49 // and then truncate it to the destination buffer
50
51 // clear errno and LastError to clear the error of the
52 // MultiByteToWideChar() that failed
53 errno_clear();
54
55 // get the required size
56 int required_size = MultiByteToWideChar(CodePage, 0, src, src_len, NULL, 0);
57
58 // mallocz() never fails (exits the program on NULL)
59 wchar_t *tmp = mallocz(required_size * sizeof(wchar_t));
60
61 // convert it, now it should fit
62 rc = MultiByteToWideChar(CodePage, 0, src, src_len, tmp, required_size);
63 if (rc <= 0) {
64 // it failed!
65 *dst = L'\0';
66 freez(tmp);
67 return 0;
68 }
69
70 size_t len = rc;
71
72 // copy as much as we can
73 memcpy(dst, tmp, MIN(len, (dst_size - 1)) * sizeof(wchar_t));
74
75 // null terminate it
76 dst[MIN(len, (dst_size - 1))] = L'\0';
77
78 // free the temporary buffer
79 freez(tmp);
80
81 // return the actual bytes written
82 return MIN(len, dst_size);
83 }
84
85 // empty the destination
86 *dst = L'\0';
87 return 0;
88 }
89
90 size_t len = rc;
91
92 if(truncated)
93 *truncated = false;
94
95 if(len >= dst_size) {
96 if(dst[dst_size - 1] != L'\0') {
97 if (truncated)
98 *truncated = true;
99
100 // Truncate it to fit the null terminator
101 dst[dst_size - 1] = L'\0';
102 }
103 return dst_size;
104 }
105
106 if(dst[len - 1] != L'\0') {
107 // the result is not null terminated
108 // append the null
109 dst[len] = L'\0';
110 return len + 1;
111 }
112
113 // the result is already null terminated
114 return len;
115 }
116
117 /*
118 * Convert UTF16 (wide-character string) to UTF8
119 * Goals:
120 * 1. Destination is always NULL terminated
121 * 2. If the destination buffer is not enough, return as much as possible data (truncate)
122 * 3. Always return the number of bytes written, including the null terminator
123 */
124
125 size_t utf16_to_utf8(char *dst, size_t dst_size, const wchar_t *src, int src_len, bool *truncated) {
126 if (!src || src_len == 0) {
127 // invalid input
128 if(truncated)
129 *truncated = true;
130
131 if(dst && dst_size)
132 *dst = '\0';
133
134 return 0;
135 }
136
137 if (!dst || dst_size == 0) {
138 // The caller wants to know the buffer size required for the conversion
139
140 if(truncated)
141 *truncated = true;
142
143 int required = WideCharToMultiByte(CP_UTF8, 0, src, src_len, NULL, 0, NULL, NULL);
144 if (required <= 0) return 0; // error in the conversion
145
146 // Add 1 for null terminator only if src_len is not -1
147 return (size_t)required + ((src_len != -1) ? 1 : 0);
148 }
149
150 // Perform the conversion directly into the destination buffer
151 int rc = WideCharToMultiByte(CP_UTF8, 0, src, src_len, dst, (int)dst_size, NULL, NULL);
152 if (rc <= 0) {
153 if(truncated)
154 *truncated = true;
155
156 // Conversion failed, let's see why...
157 DWORD status = GetLastError();
158 if (status == ERROR_INSUFFICIENT_BUFFER) {
159 // It cannot fit entirely, let's allocate a new buffer to convert it
160 // and then truncate it to the destination buffer
161
162 // Clear errno and LastError to clear the error of the
163 // WideCharToMultiByte() that failed
164 errno_clear();
165
166 // Get the required size
167 int required_size = WideCharToMultiByte(CP_UTF8, 0, src, src_len, NULL, 0, NULL, NULL);
168
169 // mallocz() never fails (exits the program on NULL)
170 char *tmp = mallocz(required_size * sizeof(char));
171
172 // Convert it, now it should fit
173 rc = WideCharToMultiByte(CP_UTF8, 0, src, src_len, tmp, required_size, NULL, NULL);
174 if (rc <= 0) {
175 // Conversion failed
176 *dst = '\0';
177 freez(tmp);
178 return 0;
179 }
180
181 size_t len = rc;
182
183 // Copy as much as we can
184 memcpy(dst, tmp, MIN(len, (dst_size - 1)) * sizeof(char));
185
186 // Null-terminate it
187 dst[MIN(len, (dst_size - 1))] = '\0';
188
189 // Free the temporary buffer
190 freez(tmp);
191
192 // Return the actual bytes written
193 return MIN(len, dst_size);
194 }
195
196 // Empty the destination
197 *dst = '\0';
198 return 0;
199 }
200
201 size_t len = rc;
202
203 if(truncated)
204 *truncated = false;
205
206 if (len >= dst_size) {
207 if(dst[dst_size - 1] != '\0') {
208 if (truncated)
209 *truncated = true;
210
211 // Truncate it to fit the null terminator
212 dst[dst_size - 1] = '\0';
213 }
214 return dst_size;
215 }
216
217 if (dst[len - 1] != '\0') {
218 // The result is not null-terminated
219 // Append the null terminator
220 dst[len] = '\0';
221 return len + 1;
222 }
223
224 // The result is already null-terminated
225 return len;
226 }
227
228 // --------------------------------------------------------------------------------------------------------------------
229
230 size_t txt_compute_new_size(size_t old_size, size_t required_size) {
231 size_t size = (required_size % 2048 == 0) ? required_size : required_size + 2048;
232 size = (size / 2048) * 2048;
233
234 if(size < old_size * 2)
235 size = old_size * 2;
236
237 return size;
238 }
239
240 // --------------------------------------------------------------------------------------------------------------------
241 // TXT_UTF8
242
243 void txt_utf8_cleanup(TXT_UTF8 *dst) {
244 freez(dst->data);
245 dst->data = NULL;
246 dst->used = 0;
247 }
248
249 void txt_utf8_resize(TXT_UTF8 *dst, size_t required_size, bool keep) {
250 if(required_size <= dst->size)
251 return;
252
253 size_t new_size = txt_compute_new_size(dst->size, required_size);
254
255 if(keep && dst->data)
256 dst->data = reallocz(dst->data, new_size);
257 else {
258 txt_utf8_cleanup(dst);
259 dst->data = mallocz(new_size);
260 dst->used = 0;
261 }
262
263 dst->size = new_size;
264 }
265
266 void txt_utf8_empty(TXT_UTF8 *dst) {
267 txt_utf8_resize(dst, 1, false);
268 dst->data[0] = '\0';
269 dst->used = 1;
270 }
271
272 void txt_utf8_set(TXT_UTF8 *dst, const char *txt, size_t txt_len) {
273 txt_utf8_resize(dst, txt_len + 1, false);
274 memcpy(dst->data, txt, txt_len);
275 dst->used = txt_len + 1;
276 dst->data[dst->used - 1] = '\0';
277 }
278
279 void txt_utf8_append(TXT_UTF8 *dst, const char *txt, size_t txt_len) {
280 if(dst->used <= 1) {
281 // the destination is empty
282 txt_utf8_set(dst, txt, txt_len);
283 }
284 else {
285 // there is something already in the buffer
286 txt_utf8_resize(dst, dst->used + txt_len, true);
287 memcpy(&dst->data[dst->used - 1], txt, txt_len);
288 dst->used += txt_len; // the null was already counted
289 dst->data[dst->used - 1] = '\0';
290 }
291 }
292
293 // --------------------------------------------------------------------------------------------------------------------
294 // TXT_UTF16
295
296 void txt_utf16_cleanup(TXT_UTF16 *dst) {
297 freez(dst->data);
298 }
299
300 void txt_utf16_resize(TXT_UTF16 *dst, size_t required_size, bool keep) {
301 if(required_size <= dst->size)
302 return;
303
304 size_t new_size = txt_compute_new_size(dst->size, required_size);
305
306 if (keep && dst->data) {
307 dst->data = reallocz(dst->data, new_size * sizeof(wchar_t));
308 } else {
309 txt_utf16_cleanup(dst);
310 dst->data = mallocz(new_size * sizeof(wchar_t));
311 dst->used = 0;
312 }
313
314 dst->size = new_size;
315 }
316
317 void txt_utf16_set(TXT_UTF16 *dst, const wchar_t *txt, size_t txt_len) {
318 txt_utf16_resize(dst, dst->used + txt_len + 1, true);
319 memcpy(dst->data, txt, txt_len * sizeof(wchar_t));
320 dst->used = txt_len + 1;
321 dst->data[dst->used - 1] = '\0';
322 }
323
324 void txt_utf16_append(TXT_UTF16 *dst, const wchar_t *txt, size_t txt_len) {
325 if(dst->used <= 1) {
326 // the destination is empty
327 txt_utf16_set(dst, txt, txt_len);
328 }
329 else {
330 // there is something already in the buffer
331 txt_utf16_resize(dst, dst->used + txt_len, true);
332 memcpy(&dst->data[dst->used - 1], txt, txt_len * sizeof(wchar_t));
333 dst->used += txt_len; // the null was already counted
334 dst->data[dst->used - 1] = '\0';
335 }
336 }
337
338 // --------------------------------------------------------------------------------------------------------------------
339
340 bool wchar_to_txt_utf8(TXT_UTF8 *dst, const wchar_t *src, int src_len) {
341 if(!src || !src_len) {
342 txt_utf8_empty(dst);
343 return false;
344 }
345
346 if(!dst->data && !dst->size) {
347 size_t size = utf16_to_utf8(NULL, 0, src, src_len, NULL);
348 if(!size) {
349 txt_utf8_empty(dst);
350 return false;
351 }
352
353 // we +1 here to avoid entering the next condition below
354 txt_utf8_resize(dst, size, false);
355 }
356
357 bool truncated = false;
358 dst->used = utf16_to_utf8(dst->data, dst->size, src, src_len, &truncated);
359 if(truncated) {
360 // we need to resize
361 size_t needed = utf16_to_utf8(NULL, 0, src, src_len, NULL); // find the size needed
362 if(!needed) {
363 txt_utf8_empty(dst);
364 return false;
365 }
366
367 txt_utf8_resize(dst, needed, false);
368 dst->used = utf16_to_utf8(dst->data, dst->size, src, src_len, NULL);
369 }
370
371 // Make sure it is not zero padded at the end
372 while(dst->used >= 2 && dst->data[dst->used - 2] == 0)
373 dst->used--;
374
375 internal_fatal(strlen(dst->data) + 1 != dst->used,
376 "Wrong UTF8 string length");
377
378 return true;
379 }
380
381 bool txt_utf16_to_utf8(TXT_UTF8 *utf8, TXT_UTF16 *utf16) {
382 fatal_assert(utf8 && ((utf8->data && utf8->size) || (!utf8->data && !utf8->size)));
383 fatal_assert(utf16 && ((utf16->data && utf16->size) || (!utf16->data && !utf16->size)));
384
385 // pass the entire utf16 size, including the null terminator
386 // so that the resulting utf8 message will be null terminated too.
387 return wchar_to_txt_utf8(utf8, utf16->data, (int)utf16->used - 1);
388 }
389
390 char *utf16_to_utf8_strdupz(const wchar_t *src, size_t *dst_len) {
391 size_t size = utf16_to_utf8(NULL, 0, src, -1, NULL);
392 if (size) {
393 char *dst = mallocz(size);
394
395 size = utf16_to_utf8(dst, size, src, -1, NULL);
396 if(dst_len)
397 *dst_len = size - 1;
398
399 return dst;
400 }
401
402 if(dst_len)
403 *dst_len = 0;
404
405 return NULL;
406 }
407
408 #endif