Coverage Report

Created: 2026-10-15 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/winpr/libwinpr/crt/unicode.c
Line
Count
Source
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * Unicode Conversion (CRT)
4
 *
5
 * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 * Copyright 2022 Armin Novak <anovak@thincast.com>
7
 * Copyright 2022 Thincast Technologies GmbH
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *     http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
22
#include <winpr/config.h>
23
#include <winpr/assert.h>
24
25
#include <errno.h>
26
#include <wctype.h>
27
28
#include <winpr/crt.h>
29
#include <winpr/error.h>
30
#include <winpr/print.h>
31
#include <winpr/endian.h>
32
33
#ifndef _WIN32
34
35
#include "unicode.h"
36
37
/**
38
 * Notes on cross-platform Unicode portability:
39
 *
40
 * Unicode has many possible Unicode Transformation Format (UTF) encodings,
41
 * where some of the most commonly used are UTF-8, UTF-16 and sometimes UTF-32.
42
 *
43
 * The number in the UTF encoding name (8, 16, 32) refers to the number of bits
44
 * per code unit. A code unit is the minimal bit combination that can represent
45
 * a unit of encoded text in the given encoding. For instance, UTF-8 encodes
46
 * the English alphabet using 8 bits (or one byte) each, just like in ASCII.
47
 *
48
 * However, the total number of code points (values in the Unicode codespace)
49
 * only fits completely within 32 bits. This means that for UTF-8 and UTF-16,
50
 * more than one code unit may be required to fully encode a specific value.
51
 * UTF-8 and UTF-16 are variable-width encodings, while UTF-32 is fixed-width.
52
 *
53
 * UTF-8 has the advantage of being backwards compatible with ASCII, and is
54
 * one of the most commonly used Unicode encoding.
55
 *
56
 * UTF-16 is used everywhere in the Windows API. The strategy employed by
57
 * Microsoft to provide backwards compatibility in their API was to create
58
 * an ANSI and a Unicode version of the same function, ending with A (ANSI)
59
 * and W (Wide character, or UTF-16 Unicode). In headers, the original
60
 * function name is replaced by a macro that defines to either the ANSI
61
 * or Unicode version based on the definition of the _UNICODE macro.
62
 *
63
 * UTF-32 has the advantage of being fixed width, but wastes a lot of space
64
 * for English text (4x more than UTF-8, 2x more than UTF-16).
65
 *
66
 * In C, wide character strings are often defined with the wchar_t type.
67
 * Many functions are provided to deal with those wide character strings,
68
 * such as wcslen (strlen equivalent) or wprintf (printf equivalent).
69
 *
70
 * This may lead to some confusion, since many of these functions exist
71
 * on both Windows and Linux, but they are *not* the same!
72
 *
73
 * This sample hello world is a good example:
74
 *
75
 * #include <wchar.h>
76
 *
77
 * wchar_t hello[] = L"Hello, World!\n";
78
 *
79
 * int main(int argc, char** argv)
80
 * {
81
 *  wprintf(hello);
82
 *  wprintf(L"sizeof(wchar_t): %d\n", sizeof(wchar_t));
83
 *  return 0;
84
 * }
85
 *
86
 * There is a reason why the sample prints the size of the wchar_t type:
87
 * On Windows, wchar_t is two bytes (UTF-16), while on most other systems
88
 * it is 4 bytes (UTF-32). This means that if you write code on Windows,
89
 * use L"" to define a string which is meant to be UTF-16 and not UTF-32,
90
 * you will have a little surprise when trying to port your code to Linux.
91
 *
92
 * Since the Windows API uses UTF-16, not UTF-32, WinPR defines the WCHAR
93
 * type to always be 2-bytes long and uses it instead of wchar_t. Do not
94
 * ever use wchar_t with WinPR unless you know what you are doing.
95
 *
96
 * As for L"", it is unfortunately unusable in a portable way, unless a
97
 * special option is passed to GCC to define wchar_t as being two bytes.
98
 * For string constants that must be UTF-16, it is a pain, but they can
99
 * be defined in a portable way like this:
100
 *
101
 * WCHAR hello[] = { 'H','e','l','l','o','\0' };
102
 *
103
 * Such strings cannot be passed to native functions like wcslen(), which
104
 * may expect a different wchar_t size. For this reason, WinPR provides
105
 * _wcslen, which expects UTF-16 WCHAR strings on all platforms.
106
 *
107
 */
108
109
/** \deprecated We no longer export this function, see ConvertUtf8ToWChar family of functions for a
110
 * replacement
111
 *
112
 * Conversion to Unicode (UTF-16)
113
 * MultiByteToWideChar: http://msdn.microsoft.com/en-us/library/windows/desktop/dd319072/
114
 *
115
 * cbMultiByte is an input size in bytes (BYTE)
116
 * cchWideChar is an output size in wide characters (WCHAR)
117
 *
118
 * Null-terminated UTF-8 strings:
119
 *
120
 * cchWideChar *cannot* be assumed to be cbMultiByte since UTF-8 is variable-width!
121
 *
122
 * Instead, obtain the required cchWideChar output size like this:
123
 * cchWideChar = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR) lpMultiByteStr, -1, nullptr, 0);
124
 *
125
 * A value of -1 for cbMultiByte indicates that the input string is null-terminated,
126
 * and the null terminator *will* be processed. The size returned by MultiByteToWideChar
127
 * will therefore include the null terminator. Equivalent behavior can be obtained by
128
 * computing the length in bytes of the input buffer, including the null terminator:
129
 *
130
 * cbMultiByte = strlen((char*) lpMultiByteStr) + 1;
131
 *
132
 * An output buffer of the proper size can then be allocated:
133
 *
134
 * lpWideCharStr = (LPWSTR) malloc(cchWideChar * sizeof(WCHAR));
135
 *
136
 * Since cchWideChar is an output size in wide characters, the actual buffer size is:
137
 * (cchWideChar * sizeof(WCHAR)) or (cchWideChar * 2)
138
 *
139
 * Finally, perform the conversion:
140
 *
141
 * cchWideChar = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR) lpMultiByteStr, -1, lpWideCharStr,
142
 * cchWideChar);
143
 *
144
 * The value returned by MultiByteToWideChar corresponds to the number of wide characters written
145
 * to the output buffer, and should match the value obtained on the first call to
146
 * MultiByteToWideChar.
147
 *
148
 */
149
150
#if !defined(WITH_WINPR_DEPRECATED)
151
static
152
#endif
153
    int
154
    MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
155
                        LPWSTR lpWideCharStr, int cchWideChar)
156
3.37M
{
157
3.37M
  return int_MultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cbMultiByte, lpWideCharStr,
158
3.37M
                                 cchWideChar);
159
3.37M
}
160
161
/** \deprecated We no longer export this function, see ConvertWCharToUtf8 family of functions for a
162
 * replacement
163
 *
164
 * Conversion from Unicode (UTF-16)
165
 * WideCharToMultiByte: http://msdn.microsoft.com/en-us/library/windows/desktop/dd374130/
166
 *
167
 * cchWideChar is an input size in wide characters (WCHAR)
168
 * cbMultiByte is an output size in bytes (BYTE)
169
 *
170
 * Null-terminated UTF-16 strings:
171
 *
172
 * cbMultiByte *cannot* be assumed to be cchWideChar since UTF-8 is variable-width!
173
 *
174
 * Instead, obtain the required cbMultiByte output size like this:
175
 * cbMultiByte = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) lpWideCharStr, -1, nullptr, 0, nullptr,
176
 * nullptr);
177
 *
178
 * A value of -1 for cbMultiByte indicates that the input string is null-terminated,
179
 * and the null terminator *will* be processed. The size returned by WideCharToMultiByte
180
 * will therefore include the null terminator. Equivalent behavior can be obtained by
181
 * computing the length in bytes of the input buffer, including the null terminator:
182
 *
183
 * cchWideChar = _wcslen((WCHAR*) lpWideCharStr) + 1;
184
 *
185
 * An output buffer of the proper size can then be allocated:
186
 * lpMultiByteStr = (LPSTR) malloc(cbMultiByte);
187
 *
188
 * Since cbMultiByte is an output size in bytes, it is the same as the buffer size
189
 *
190
 * Finally, perform the conversion:
191
 *
192
 * cbMultiByte = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) lpWideCharStr, -1, lpMultiByteStr,
193
 * cbMultiByte, nullptr, nullptr);
194
 *
195
 * The value returned by WideCharToMultiByte corresponds to the number of bytes written
196
 * to the output buffer, and should match the value obtained on the first call to
197
 * WideCharToMultiByte.
198
 *
199
 */
200
201
#if !defined(WITH_WINPR_DEPRECATED)
202
static
203
#endif
204
    int
205
    WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
206
                        LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
207
                        LPBOOL lpUsedDefaultChar)
208
1.80M
{
209
1.80M
  return int_WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr,
210
1.80M
                                 cbMultiByte, lpDefaultChar, lpUsedDefaultChar);
211
1.80M
}
212
213
#endif
214
215
/**
216
 * ConvertToUnicode is a convenience wrapper for MultiByteToWideChar:
217
 *
218
 * If the lpWideCharStr parameter for the converted string points to nullptr
219
 * or if the cchWideChar parameter is set to 0 this function will automatically
220
 * allocate the required memory which is guaranteed to be null-terminated
221
 * after the conversion, even if the source c string isn't.
222
 *
223
 * If the cbMultiByte parameter is set to -1 the passed lpMultiByteStr must
224
 * be null-terminated and the required length for the converted string will be
225
 * calculated accordingly.
226
 */
227
#if defined(WITH_WINPR_DEPRECATED)
228
int ConvertToUnicode(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
229
                     LPWSTR* lpWideCharStr, int cchWideChar)
230
{
231
  int status = 0;
232
  BOOL allocate = FALSE;
233
234
  if (!lpMultiByteStr)
235
    return 0;
236
237
  if (!lpWideCharStr)
238
    return 0;
239
240
  if (cbMultiByte == -1)
241
  {
242
    size_t len = strnlen(lpMultiByteStr, INT_MAX);
243
    if (len >= INT_MAX)
244
      return 0;
245
    cbMultiByte = (int)(len + 1);
246
  }
247
248
  if (cchWideChar == 0)
249
  {
250
    cchWideChar =
251
        MultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cbMultiByte, nullptr, 0);
252
    allocate = TRUE;
253
  }
254
  else if (!(*lpWideCharStr))
255
    allocate = TRUE;
256
257
  if (cchWideChar < 1)
258
    return 0;
259
260
  if (allocate)
261
  {
262
    *lpWideCharStr = (LPWSTR)calloc((size_t)cchWideChar + 1ull, sizeof(WCHAR));
263
264
    if (!(*lpWideCharStr))
265
    {
266
      // SetLastError(ERROR_INSUFFICIENT_BUFFER);
267
      return 0;
268
    }
269
  }
270
271
  status = MultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cbMultiByte, *lpWideCharStr,
272
                               cchWideChar);
273
274
  if (status != cchWideChar)
275
  {
276
    if (allocate)
277
    {
278
      free(*lpWideCharStr);
279
      *lpWideCharStr = nullptr;
280
      status = 0;
281
    }
282
  }
283
284
  return status;
285
}
286
#endif
287
288
/**
289
 * ConvertFromUnicode is a convenience wrapper for WideCharToMultiByte:
290
 *
291
 * If the lpMultiByteStr parameter for the converted string points to nullptr
292
 * or if the cbMultiByte parameter is set to 0 this function will automatically
293
 * allocate the required memory which is guaranteed to be null-terminated
294
 * after the conversion, even if the source unicode string isn't.
295
 *
296
 * If the cchWideChar parameter is set to -1 the passed lpWideCharStr must
297
 * be null-terminated and the required length for the converted string will be
298
 * calculated accordingly.
299
 */
300
#if defined(WITH_WINPR_DEPRECATED)
301
int ConvertFromUnicode(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
302
                       LPSTR* lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
303
                       LPBOOL lpUsedDefaultChar)
304
{
305
  int status = 0;
306
  BOOL allocate = FALSE;
307
308
  if (!lpWideCharStr)
309
    return 0;
310
311
  if (!lpMultiByteStr)
312
    return 0;
313
314
  if (cchWideChar == -1)
315
    cchWideChar = (int)(_wcslen(lpWideCharStr) + 1);
316
317
  if (cbMultiByte == 0)
318
  {
319
    cbMultiByte = WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, nullptr, 0,
320
                                      nullptr, nullptr);
321
    allocate = TRUE;
322
  }
323
  else if (!(*lpMultiByteStr))
324
    allocate = TRUE;
325
326
  if (cbMultiByte < 1)
327
    return 0;
328
329
  if (allocate)
330
  {
331
    *lpMultiByteStr = (LPSTR)calloc(1, (size_t)cbMultiByte + 1ull);
332
333
    if (!(*lpMultiByteStr))
334
    {
335
      // SetLastError(ERROR_INSUFFICIENT_BUFFER);
336
      return 0;
337
    }
338
  }
339
340
  status = WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, *lpMultiByteStr,
341
                               cbMultiByte, lpDefaultChar, lpUsedDefaultChar);
342
343
  if ((status != cbMultiByte) && allocate)
344
  {
345
    status = 0;
346
  }
347
348
  if ((status <= 0) && allocate)
349
  {
350
    free(*lpMultiByteStr);
351
    *lpMultiByteStr = nullptr;
352
  }
353
354
  return status;
355
}
356
#endif
357
358
/**
359
 * Swap Unicode byte order (UTF16LE <-> UTF16BE)
360
 */
361
362
const WCHAR* ByteSwapUnicode(WCHAR* wstr, size_t length)
363
0
{
364
0
  WINPR_ASSERT(wstr || (length == 0));
365
366
0
  for (size_t x = 0; x < length; x++)
367
0
    wstr[x] = _byteswap_ushort(wstr[x]);
368
0
  return wstr;
369
0
}
370
371
SSIZE_T ConvertWCharToUtf8(const WCHAR* wstr, char* str, size_t len)
372
880k
{
373
880k
  if (!wstr)
374
0
  {
375
0
    if (str && len)
376
0
      str[0] = 0;
377
0
    return 0;
378
0
  }
379
380
880k
  const size_t wlen = _wcslen(wstr);
381
880k
  return ConvertWCharNToUtf8(wstr, wlen + 1, str, len);
382
880k
}
383
384
SSIZE_T ConvertWCharNToUtf8(const WCHAR* wstr, size_t wlen, char* str, size_t len)
385
1.80M
{
386
1.80M
  BOOL isNullTerminated = FALSE;
387
1.80M
  if (wlen == 0)
388
10
    return 0;
389
390
1.80M
  WINPR_ASSERT(wstr);
391
1.80M
  size_t iwlen = _wcsnlen(wstr, wlen);
392
393
1.80M
  if ((len > INT32_MAX) || (wlen > INT32_MAX))
394
0
  {
395
0
    SetLastError(ERROR_INVALID_PARAMETER);
396
0
    return -1;
397
0
  }
398
399
1.80M
  if (iwlen < wlen)
400
1.53M
  {
401
1.53M
    isNullTerminated = TRUE;
402
1.53M
    iwlen++;
403
1.53M
  }
404
1.80M
  WINPR_PRAGMA_DIAG_PUSH
405
1.80M
  WINPR_PRAGMA_DIAG_IGNORED_DEPRECATED_DECL
406
1.80M
  const int rc =
407
1.80M
      WideCharToMultiByte(CP_UTF8, 0, wstr, (int)iwlen, str, (int)len, nullptr, nullptr);
408
1.80M
  WINPR_PRAGMA_DIAG_POP
409
1.80M
  if ((rc <= 0) || ((len > 0) && ((size_t)rc > len)))
410
2.30k
    return -1;
411
1.80M
  else if (!isNullTerminated)
412
273k
  {
413
273k
    if (str && ((size_t)rc < len))
414
142k
      str[rc] = '\0';
415
273k
    return rc;
416
273k
  }
417
1.53M
  else if ((size_t)rc == len)
418
524k
  {
419
524k
    if (str && (str[rc - 1] != '\0'))
420
0
      return rc;
421
524k
  }
422
1.53M
  return rc - 1;
423
1.80M
}
424
425
SSIZE_T ConvertMszWCharNToUtf8(const WCHAR* wstr, size_t wlen, char* str, size_t len)
426
0
{
427
0
  if (wlen == 0)
428
0
    return 0;
429
430
0
  WINPR_ASSERT(wstr);
431
432
0
  if ((len > INT32_MAX) || (wlen > INT32_MAX))
433
0
  {
434
0
    SetLastError(ERROR_INVALID_PARAMETER);
435
0
    return -1;
436
0
  }
437
438
0
  const int iwlen = (int)len;
439
0
  WINPR_PRAGMA_DIAG_PUSH
440
0
  WINPR_PRAGMA_DIAG_IGNORED_DEPRECATED_DECL
441
0
  const int rc = WideCharToMultiByte(CP_UTF8, 0, wstr, (int)wlen, str, iwlen, nullptr, nullptr);
442
0
  WINPR_PRAGMA_DIAG_POP
443
0
  if ((rc <= 0) || ((len > 0) && (rc > iwlen)))
444
0
    return -1;
445
446
0
  return rc;
447
0
}
448
449
SSIZE_T ConvertUtf8ToWChar(const char* str, WCHAR* wstr, size_t wlen)
450
1.61M
{
451
1.61M
  if (!str)
452
108
  {
453
108
    if (wstr && wlen)
454
54
      wstr[0] = 0;
455
108
    return 0;
456
108
  }
457
458
1.61M
  const size_t len = strlen(str);
459
1.61M
  return ConvertUtf8NToWChar(str, len + 1, wstr, wlen);
460
1.61M
}
461
462
SSIZE_T ConvertUtf8NToWChar(const char* str, size_t len, WCHAR* wstr, size_t wlen)
463
3.37M
{
464
3.37M
  size_t ilen = strnlen(str, len);
465
3.37M
  BOOL isNullTerminated = FALSE;
466
3.37M
  if (len == 0)
467
14
    return 0;
468
469
3.37M
  WINPR_ASSERT(str);
470
471
3.37M
  if ((len > INT32_MAX) || (wlen > INT32_MAX))
472
0
  {
473
0
    SetLastError(ERROR_INVALID_PARAMETER);
474
0
    return -1;
475
0
  }
476
3.37M
  if (ilen < len)
477
3.35M
  {
478
3.35M
    isNullTerminated = TRUE;
479
3.35M
    ilen++;
480
3.35M
  }
481
482
3.37M
  const int iwlen = (int)wlen;
483
3.37M
  WINPR_PRAGMA_DIAG_PUSH
484
3.37M
  WINPR_PRAGMA_DIAG_IGNORED_DEPRECATED_DECL
485
3.37M
  const int rc = MultiByteToWideChar(CP_UTF8, 0, str, (int)ilen, wstr, iwlen);
486
3.37M
  WINPR_PRAGMA_DIAG_POP
487
3.37M
  if ((rc <= 0) || ((wlen > 0) && (rc > iwlen)))
488
802
    return -1;
489
3.37M
  if (!isNullTerminated)
490
20.3k
  {
491
20.3k
    if (wstr && (rc < iwlen))
492
10.1k
      wstr[rc] = '\0';
493
20.3k
    return rc;
494
20.3k
  }
495
3.35M
  else if (rc == iwlen)
496
15.9k
  {
497
15.9k
    if (wstr && (wstr[rc - 1] != '\0'))
498
0
      return rc;
499
15.9k
  }
500
3.35M
  return rc - 1;
501
3.37M
}
502
503
SSIZE_T ConvertMszUtf8NToWChar(const char* str, size_t len, WCHAR* wstr, size_t wlen)
504
0
{
505
0
  if (len == 0)
506
0
    return 0;
507
508
0
  WINPR_ASSERT(str);
509
510
0
  if ((len > INT32_MAX) || (wlen > INT32_MAX))
511
0
  {
512
0
    SetLastError(ERROR_INVALID_PARAMETER);
513
0
    return -1;
514
0
  }
515
516
0
  const int iwlen = (int)wlen;
517
0
  WINPR_PRAGMA_DIAG_PUSH
518
0
  WINPR_PRAGMA_DIAG_IGNORED_DEPRECATED_DECL
519
0
  const int rc = MultiByteToWideChar(CP_UTF8, 0, str, (int)len, wstr, iwlen);
520
0
  WINPR_PRAGMA_DIAG_POP
521
0
  if ((rc <= 0) || ((wlen > 0) && (rc > iwlen)))
522
0
    return -1;
523
524
0
  return rc;
525
0
}
526
527
char* ConvertWCharToUtf8Alloc(const WCHAR* wstr, size_t* pUtfCharLength)
528
439k
{
529
439k
  char* tmp = nullptr;
530
439k
  const SSIZE_T rc = ConvertWCharToUtf8(wstr, nullptr, 0);
531
439k
  if (pUtfCharLength)
532
0
    *pUtfCharLength = 0;
533
439k
  if (rc < 0)
534
0
    return nullptr;
535
439k
  tmp = calloc((size_t)rc + 1ull, sizeof(char));
536
439k
  if (!tmp)
537
0
    return nullptr;
538
439k
  const SSIZE_T rc2 = ConvertWCharToUtf8(wstr, tmp, (size_t)rc + 1ull);
539
439k
  if (rc2 < 0)
540
0
  {
541
0
    free(tmp);
542
0
    return nullptr;
543
0
  }
544
439k
  WINPR_ASSERT(rc == rc2);
545
439k
  if (pUtfCharLength)
546
0
    *pUtfCharLength = (size_t)rc2;
547
439k
  return tmp;
548
439k
}
549
550
char* ConvertWCharNToUtf8Alloc(const WCHAR* wstr, size_t wlen, size_t* pUtfCharLength)
551
216k
{
552
216k
  char* tmp = nullptr;
553
216k
  const SSIZE_T rc = ConvertWCharNToUtf8(wstr, wlen, nullptr, 0);
554
555
216k
  if (pUtfCharLength)
556
79.3k
    *pUtfCharLength = 0;
557
216k
  if (rc < 0)
558
576
    return nullptr;
559
215k
  tmp = calloc((size_t)rc + 1ull, sizeof(char));
560
215k
  if (!tmp)
561
0
    return nullptr;
562
215k
  const SSIZE_T rc2 = ConvertWCharNToUtf8(wstr, wlen, tmp, (size_t)rc + 1ull);
563
215k
  if (rc2 < 0)
564
0
  {
565
0
    free(tmp);
566
0
    return nullptr;
567
0
  }
568
215k
  WINPR_ASSERT(rc == rc2);
569
215k
  if (pUtfCharLength)
570
78.9k
    *pUtfCharLength = (size_t)rc2;
571
215k
  return tmp;
572
215k
}
573
574
char* ConvertMszWCharNToUtf8Alloc(const WCHAR* wstr, size_t wlen, size_t* pUtfCharLength)
575
0
{
576
0
  char* tmp = nullptr;
577
0
  const SSIZE_T rc = ConvertMszWCharNToUtf8(wstr, wlen, nullptr, 0);
578
579
0
  if (pUtfCharLength)
580
0
    *pUtfCharLength = 0;
581
0
  if (rc < 0)
582
0
    return nullptr;
583
0
  tmp = calloc((size_t)rc + 1ull, sizeof(char));
584
0
  if (!tmp)
585
0
    return nullptr;
586
0
  const SSIZE_T rc2 = ConvertMszWCharNToUtf8(wstr, wlen, tmp, (size_t)rc + 1ull);
587
0
  if (rc2 < 0)
588
0
  {
589
0
    free(tmp);
590
0
    return nullptr;
591
0
  }
592
0
  WINPR_ASSERT(rc == rc2);
593
0
  if (pUtfCharLength)
594
0
    *pUtfCharLength = (size_t)rc2;
595
0
  return tmp;
596
0
}
597
598
WCHAR* ConvertUtf8ToWCharAlloc(const char* str, size_t* pSize)
599
16.1k
{
600
16.1k
  WCHAR* tmp = nullptr;
601
16.1k
  const SSIZE_T rc = ConvertUtf8ToWChar(str, nullptr, 0);
602
16.1k
  if (pSize)
603
4.90k
    *pSize = 0;
604
16.1k
  if (rc < 0)
605
176
    return nullptr;
606
16.0k
  tmp = calloc((size_t)rc + 1ull, sizeof(WCHAR));
607
16.0k
  if (!tmp)
608
0
    return nullptr;
609
16.0k
  const SSIZE_T rc2 = ConvertUtf8ToWChar(str, tmp, (size_t)rc + 1ull);
610
16.0k
  if (rc2 < 0)
611
0
  {
612
0
    free(tmp);
613
0
    return nullptr;
614
0
  }
615
16.0k
  WINPR_ASSERT(rc == rc2);
616
16.0k
  if (pSize)
617
4.82k
    *pSize = (size_t)rc2;
618
16.0k
  return tmp;
619
16.0k
}
620
621
WCHAR* ConvertUtf8NToWCharAlloc(const char* str, size_t len, size_t* pSize)
622
10.8k
{
623
10.8k
  WCHAR* tmp = nullptr;
624
10.8k
  const SSIZE_T rc = ConvertUtf8NToWChar(str, len, nullptr, 0);
625
10.8k
  if (pSize)
626
10.8k
    *pSize = 0;
627
10.8k
  if (rc < 0)
628
626
    return nullptr;
629
10.2k
  tmp = calloc((size_t)rc + 1ull, sizeof(WCHAR));
630
10.2k
  if (!tmp)
631
0
    return nullptr;
632
10.2k
  const SSIZE_T rc2 = ConvertUtf8NToWChar(str, len, tmp, (size_t)rc + 1ull);
633
10.2k
  if (rc2 < 0)
634
0
  {
635
0
    free(tmp);
636
0
    return nullptr;
637
0
  }
638
10.2k
  WINPR_ASSERT(rc == rc2);
639
10.2k
  if (pSize)
640
10.2k
    *pSize = (size_t)rc2;
641
10.2k
  return tmp;
642
10.2k
}
643
644
WCHAR* ConvertMszUtf8NToWCharAlloc(const char* str, size_t len, size_t* pSize)
645
0
{
646
0
  WCHAR* tmp = nullptr;
647
0
  const SSIZE_T rc = ConvertMszUtf8NToWChar(str, len, nullptr, 0);
648
0
  if (pSize)
649
0
    *pSize = 0;
650
0
  if (rc < 0)
651
0
    return nullptr;
652
0
  tmp = calloc((size_t)rc + 1ull, sizeof(WCHAR));
653
0
  if (!tmp)
654
0
    return nullptr;
655
0
  const SSIZE_T rc2 = ConvertMszUtf8NToWChar(str, len, tmp, (size_t)rc + 1ull);
656
0
  if (rc2 < 0)
657
0
  {
658
0
    free(tmp);
659
0
    return nullptr;
660
0
  }
661
0
  WINPR_ASSERT(rc == rc2);
662
0
  if (pSize)
663
0
    *pSize = (size_t)rc2;
664
0
  return tmp;
665
0
}
666
667
WINPR_ATTR_NODISCARD
668
static INT8 nnibble(char c)
669
255k
{
670
255k
  if ((c >= '0') && (c <= '9'))
671
78.9k
    return (INT8)(c - '0');
672
176k
  if ((c >= 'a') && (c <= 'f'))
673
21.2k
    return (INT8)((c - 'a') + 0xa);
674
155k
  if ((c >= 'A') && (c <= 'F'))
675
113k
    return (INT8)((c - 'A') + 0xa);
676
41.6k
  return -1;
677
155k
}
678
679
WINPR_ATTR_NODISCARD
680
static INT32 escapeToNumber(char* str, char** next)
681
79.9k
{
682
79.9k
  if (strncmp(str, "\\u", 2) != 0)
683
145
    return -1;
684
685
79.8k
  size_t offset = 2;
686
79.8k
  INT32 code = 0;
687
293k
  for (size_t x = 0; x < 4; x++)
688
255k
  {
689
255k
    const INT8 val = nnibble(str[offset++]);
690
255k
    if (val < 0)
691
41.6k
      return -1;
692
693
213k
    code <<= 4;
694
213k
    code |= val;
695
213k
  }
696
38.1k
  *next = &str[offset];
697
38.1k
  return code;
698
79.8k
}
699
700
SSIZE_T winpr_utfEscapedStringToUtf8(char* str, size_t len)
701
2.53k
{
702
2.53k
  char* cur = str;
703
2.53k
  size_t clen = len;
704
71.2k
  while ((cur = winpr_strnstr(cur, "\\u", clen)))
705
69.2k
  {
706
69.2k
    char* next = nullptr;
707
69.2k
    const INT32 code = escapeToNumber(cur, &next);
708
69.2k
    if (code < 0)
709
41.4k
    {
710
      /* Not a \uXXXX escape (e.g. "C:\users"): keep it as literal text */
711
41.4k
      cur += 2;
712
41.4k
      clen = len - (size_t)(cur - str);
713
41.4k
      continue;
714
41.4k
    }
715
716
27.7k
    WCHAR wc[2] = WINPR_C_ARRAY_INIT;
717
27.7k
    winpr_Data_Write_UINT16(&wc[0], (WCHAR)code);
718
719
    // Check for high surrogate
720
27.7k
    if ((code & 0xd800) == 0xd800)
721
10.7k
    {
722
10.7k
      const INT32 code2 = escapeToNumber(next, &next);
723
10.7k
      if (code2 < 0)
724
380
        return -1;
725
726
      // low surrogate check. must match
727
10.3k
      if ((code2 & 0xdc00) != 0xdc00)
728
80
        return -1;
729
730
10.3k
      winpr_Data_Write_UINT16(&wc[1], (WCHAR)code2);
731
10.3k
    }
732
733
27.3k
    char utf8[8] = WINPR_C_ARRAY_INIT;
734
27.3k
    const SSIZE_T rc = ConvertWCharNToUtf8(wc, ARRAYSIZE(wc), utf8, ARRAYSIZE(utf8));
735
27.3k
    if (rc < 0)
736
30
      return -1;
737
738
27.3k
    strncpy(cur, utf8, (size_t)rc);
739
740
27.3k
    const size_t diff = (size_t)(next - cur);
741
27.3k
    if (diff > (size_t)rc)
742
27.3k
    {
743
27.3k
      const size_t rlen = strnlen(next, len);
744
27.3k
      memmove(&cur[(size_t)rc], next, rlen + 1);
745
27.3k
    }
746
747
    /* Continue after the decoded bytes, so they are never decoded again */
748
27.3k
    cur += rc;
749
27.3k
    clen = len - (size_t)(cur - str);
750
27.3k
  }
751
2.04k
  return (SSIZE_T)strnlen(str, len);
752
2.53k
}
753
754
char* winpr_utf8ToUtfEscapedString(char* str, size_t len, size_t* pDstLen)
755
1.57k
{
756
1.57k
  WINPR_ASSERT(str || (len == 0));
757
758
1.57k
  if (pDstLen)
759
1.57k
    *pDstLen = 0;
760
761
1.57k
  if (len >= SIZE_MAX / 12)
762
0
    return nullptr;
763
764
1.57k
  char* escaped = nullptr;
765
1.57k
  size_t offset = 0;
766
1.57k
  size_t wlen = 0;
767
1.57k
  WCHAR* wstr = ConvertUtf8NToWCharAlloc(str, len, &wlen);
768
1.57k
  if (!wstr || (wlen == 0))
769
423
    goto fail;
770
771
1.15k
  const size_t esclen = len * 12ull;
772
1.15k
  escaped = calloc(len + 1, 12);
773
1.15k
  if (!escaped)
774
0
    goto fail;
775
776
21.7M
  for (size_t x = 0; x < wlen; x++)
777
21.7M
  {
778
21.7M
    const WCHAR wc = winpr_Data_Get_UINT16(&wstr[x]);
779
21.7M
    if (wc < 0x80)
780
9.14M
      escaped[offset++] = (char)wc;
781
12.6M
    else
782
12.6M
    {
783
12.6M
      const int rc = snprintf(&escaped[offset], esclen - offset, "\\u%04x", wc);
784
12.6M
      if (rc < 0)
785
0
        break;
786
12.6M
      offset += (size_t)rc;
787
12.6M
    }
788
21.7M
  }
789
1.15k
  escaped[offset] = '\0';
790
791
1.57k
fail:
792
1.57k
  if (pDstLen)
793
1.57k
    *pDstLen = offset;
794
795
1.57k
  winpr_znfree(wstr, wlen * sizeof(WCHAR));
796
1.57k
  return escaped;
797
1.15k
}