Windows: more roken.h include corrections
[openafs.git] / src / WINNT / afsd / cm_nls.c
1 /*
2  * Copyright (c) 2008 Secure Endpoints Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person
5  * obtaining a copy of this software and associated documentation
6  * files (the "Software"), to deal in the Software without
7  * restriction, including without limitation the rights to use, copy,
8  * modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24
25 #include <afsconfig.h>
26 #include <afs/param.h>
27 #include <afs/stds.h>
28 #include <roken.h>
29
30 #include <windows.h>
31 #include <wchar.h>
32 #include <strsafe.h>
33
34 #include "cm_nls.h"
35
36 #ifdef DEBUG_UNICODE
37 #include <assert.h>
38 #endif
39
40 /* This is part of the Microsoft Internationalized Domain Name
41    Mitigation APIs. */
42 #include <normalization.h>
43
44 /* TODO: All the normalization and conversion code should NUL
45    terminate destination strings. */
46
47 int
48 (WINAPI *pNormalizeString)( __in NORM_FORM NormForm,
49                             __in_ecount(cwSrcLength) LPCWSTR lpSrcString,
50                             __in int cwSrcLength,
51                             __out_ecount(cwDstLength) LPWSTR lpDstString,
52                             __in int cwDstLength ) = NULL;
53
54 BOOL
55 (WINAPI *pIsNormalizedString)( __in NORM_FORM NormForm,
56                                __in_ecount(cwLength) LPCWSTR lpString,
57                                __in int cwLength ) = NULL;
58
59
60 #define NLSDLLNAME "Normaliz.dll"
61 #define NLSMAXCCH  1024
62 #define NLSERRCCH  8
63
64 #define AFS_NORM_FORM NormalizationC
65
66 static LCID nls_lcid = LOCALE_INVARIANT;
67
68 static int nls_init = 0;
69
70 static BOOL
71 is_windows_2000 (void)
72 {
73    static BOOL fChecked = FALSE;
74    static BOOL fIsWin2K = FALSE;
75
76    if (!fChecked)
77    {
78        OSVERSIONINFO Version;
79
80        memset (&Version, 0x00, sizeof(Version));
81        Version.dwOSVersionInfoSize = sizeof(Version);
82
83        if (GetVersionEx (&Version))
84        {
85            if (Version.dwPlatformId == VER_PLATFORM_WIN32_NT &&
86                 Version.dwMajorVersion >= 5)
87                fIsWin2K = TRUE;
88        }
89        fChecked = TRUE;
90    }
91
92    return fIsWin2K;
93 }
94
95 long cm_InitNormalization(void)
96 {
97     HMODULE h_Nls;
98
99     if (pNormalizeString != NULL)
100         return 0;
101
102     h_Nls = LoadLibrary(NLSDLLNAME);
103     if (h_Nls == INVALID_HANDLE_VALUE) {
104         return 1;
105     }
106
107     pNormalizeString =
108         (int (WINAPI *)( NORM_FORM, LPCWSTR,
109                          int, LPWSTR, int))
110         GetProcAddress(h_Nls, "NormalizeString");
111
112     pIsNormalizedString =
113         (BOOL
114          (WINAPI *)( NORM_FORM, LPCWSTR, int ))
115         GetProcAddress(h_Nls, "IsNormalizedString");
116
117     if (is_windows_2000())
118         nls_lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
119
120     nls_init = 1;
121
122     return (pNormalizeString && pIsNormalizedString);
123 }
124
125 /* \brief Normalize a UTF-16 string.
126
127    If the supplied destination buffer is insufficient or NULL, then a
128    new buffer will be allocated to hold the normalized string.
129
130    \param[in] src : Source UTF-16 string.  Length is specified in
131        cch_src.
132
133    \param[in] cch_src : The character count in cch_src is assumed to
134        be tight and include the terminating NULL character if there is
135        one.  If the NULL is absent, the resulting string will not be
136        NULL terminated.
137
138    \param[out] ext_dest : The destination buffer.  Can be NULL, in
139        which case *pcch_dest MUST be 0.
140
141    \param[in,out] pcch_dest : On entry *pcch_dest contains a count of
142        characters in the destination buffer.  On exit, it will contain
143        a count of characters that were copied to the destination
144        buffer.
145
146    Returns a pointer to the buffer containing the normalized string or
147    NULL if the call was unsuccessful.  If the returned destination
148    buffer is different from the supplied buffer and non-NULL, it
149    should be freed using free().
150 */
151 static wchar_t *
152 NormalizeUtf16String(const wchar_t * src, int cch_src, wchar_t * ext_dest, int *pcch_dest)
153 {
154     if (!nls_init)
155         cm_InitNormalization();
156
157 #ifdef DEBUG_UNICODE
158     assert (pNormalizeString != NULL && pIsNormalizedString != NULL);
159 #endif
160
161     if (cch_src == -1)
162         cch_src = (int)wcslen(src) + 1;
163
164     if ((pIsNormalizedString && (*pIsNormalizedString)(AFS_NORM_FORM, src, cch_src)) ||
165         (!pNormalizeString)) {
166
167         if (ext_dest == NULL || *pcch_dest < cch_src) {
168             ext_dest = malloc(cch_src * sizeof(wchar_t));
169             *pcch_dest = cch_src;
170         }
171
172         /* No need to or unable to normalize.  Just copy the string.
173            Note that the string is not NUL terminated if the source
174            string is not NUL terminated. */
175
176         if (ext_dest) {
177             memcpy(ext_dest, src, cch_src * sizeof(wchar_t));
178             *pcch_dest = cch_src;
179         } else {
180             *pcch_dest = 0;
181         }
182         return ext_dest;
183
184     } else {
185
186         int rv;
187         DWORD gle;
188         int tries = 10;
189         wchar_t * dest;
190         int cch_dest = *pcch_dest;
191
192         dest = ext_dest;
193
194         while (tries-- > 0) {
195
196             rv = (*pNormalizeString)(AFS_NORM_FORM, src, cch_src, dest, cch_dest);
197
198             if (rv <= 0 && (gle = GetLastError()) != ERROR_SUCCESS) {
199                 if (gle == ERROR_INSUFFICIENT_BUFFER) {
200
201                     /* The buffer wasn't big enough.  We are going to
202                        try allocating one. */
203
204                     cch_dest = (-rv) + NLSERRCCH;
205                     goto cont;
206
207                 } else {
208                     /* Something else is wrong */
209                     break;
210                 }
211
212             } else if (rv < 0) { /* rv < 0 && gle == ERROR_SUCCESS */
213
214                 /* Technically not one of the expected outcomes */
215                 break;
216
217             } else {            /* rv > 0 || (rv == 0 && gle == ERROR_SUCCESS) */
218
219                 /* Possibly succeeded */
220
221                 if (rv == 0) { /* Succeeded and the return string is empty */
222                     *pcch_dest = 0;
223                     return dest;
224                 }
225
226                 if (cch_dest == 0) {
227                     /* Nope.  We only calculated the required size of the buffer */
228
229                     cch_dest = rv + NLSERRCCH;
230                     goto cont;
231                 }
232
233                 *pcch_dest = rv;
234                 if (cch_dest > rv)
235                     dest[rv] = 0;
236                 else {
237                     /* Can't NUL terminate */
238                     cch_dest = max(rv,cch_dest) + NLSERRCCH;
239                     goto cont;
240                 }
241
242                 /* Success! */
243                 return dest;
244             }
245
246         cont:
247             if (dest != ext_dest && dest)
248                 free(dest);
249             dest = malloc(cch_dest * sizeof(wchar_t));
250         }
251
252         /* Failed */
253
254         if (dest != ext_dest && dest)
255             free(dest);
256
257         *pcch_dest = 0;
258         return NULL;
259     }
260 }
261
262 /*! \brief Normalize a Unicode string into a newly allocated buffer
263
264   The input string will be normalized using NFC.
265
266   \param[in] s UTF-16 string to be normalized.
267
268   \param[in] cch_src The number of characters in the input string.  If
269       this is -1, then the input string is assumed to be NUL
270       terminated.
271
272   \param[out] pcch_dest Receives the number of characters copied to
273       the output buffer.  Note that the character count is the number
274       of wchar_t characters copied, and not the count of Unicode code
275       points.  This includes the terminating NUL if cch_src was -1 or
276       included the terminating NUL.
277
278   \return A newly allocated buffer holding the normalized string or
279       NULL if the call failed.
280  */
281 cm_normchar_t * cm_NormalizeStringAlloc(const cm_unichar_t * s, int cch_src, int *pcch_dest)
282 {
283     int cch_dest = 0;
284     cm_normchar_t * r;
285
286     if (!nls_init)
287         cm_InitNormalization();
288
289     if (s == NULL || cch_src == 0 || *s == L'\0') {
290         if (pcch_dest)
291             *pcch_dest = ((cch_src != 0)? 1: 0);
292         return wcsdup(L"");
293     }
294
295     r = NormalizeUtf16String(s, cch_src, NULL, &cch_dest);
296
297     if (pcch_dest)
298         *pcch_dest = cch_dest;
299
300     return r;
301 }
302
303 int cm_NormalizeString(const cm_unichar_t * s, int cch_src,
304                        cm_normchar_t * dest, int cch_dest)
305 {
306     int tcch = cch_dest;
307     cm_normchar_t * r;
308
309     if (!nls_init)
310         cm_InitNormalization();
311
312     r = NormalizeUtf16String(s, cch_src, dest, &tcch);
313
314     if (r != dest) {
315         /* The supplied buffer was insufficient */
316         free(r);
317         return 0;
318     } else {
319         return tcch;
320     }
321 }
322
323 /*! \brief Convert a UTF-16 string to a UTF-8 string using a newly allocated buffer
324
325   \param[in] s UTF-16 source string
326
327   \param[in] cch_src Number of characters in \a s. This can be set to
328       -1 if \a s is NUL terminated.
329
330   \param[out] pcch_dest Receives a count of characters that were
331       copied to the target buffer.
332
333   \return A newly allocated buffer holding the UTF-8 string.
334
335  */
336 cm_utf8char_t * cm_Utf16ToUtf8Alloc(const cm_unichar_t * s, int cch_src, int *pcch_dest)
337 {
338     int cch_dest;
339     cm_utf8char_t * dest;
340
341     if (!nls_init)
342         cm_InitNormalization();
343
344     if (s == NULL || cch_src == 0 || *s == L'\0') {
345         if (pcch_dest)
346             *pcch_dest = ((cch_src != 0)?1:0);
347         return strdup("");
348     }
349
350     cch_dest = WideCharToMultiByte(CP_UTF8, 0, s, cch_src, NULL, 0, NULL, FALSE);
351
352     if (cch_dest == 0) {
353         if (pcch_dest)
354             *pcch_dest = cch_dest;
355         return NULL;
356     }
357
358     dest = malloc((cch_dest + 1) * sizeof(cm_utf8char_t));
359
360     WideCharToMultiByte(CP_UTF8, 0, s, cch_src, dest, cch_dest, NULL, FALSE);
361     dest[cch_dest] = 0;
362
363     if (pcch_dest)
364         *pcch_dest = cch_dest;
365
366     return dest;
367 }
368
369 int cm_Utf16ToUtf8(const cm_unichar_t * src, int cch_src,
370                    cm_utf8char_t * dest, int cch_dest)
371 {
372     if (!nls_init)
373         cm_InitNormalization();
374
375     return WideCharToMultiByte(CP_UTF8, 0, src, cch_src, dest, cch_dest, NULL, FALSE);
376 }
377
378 int cm_Utf16ToUtf16(const cm_unichar_t * src, int cch_src,
379                     cm_unichar_t * dest, int cch_dest)
380 {
381     if (!nls_init)
382         cm_InitNormalization();
383
384     if (cch_src == -1) {
385         StringCchCopyW(dest, cch_dest, src);
386         return (int)wcslen(dest) + 1;
387     } else {
388         int cch_conv = min(cch_src, cch_dest);
389         memcpy(dest, src, cch_conv * sizeof(cm_unichar_t));
390         return cch_conv;
391     }
392 }
393
394 /* \brief Normalize a UTF-16 string into a UTF-8 string.
395
396    \param[in] src : Source string.
397
398    \param[in] cch_src : Count of characters in src. If the count includes the
399        NULL terminator, then the resulting string will be NULL
400        terminated.  If it is -1, then src is assumed to be NULL
401        terminated.
402
403    \param[out] adest : Destination buffer.
404
405    \param[in] cch_adest : Number of characters in the destination buffer.
406
407    Returns the number of characters stored into cch_adest. This will
408    include the terminating NULL if cch_src included the terminating
409    NULL or was -1.  If this is 0, then the operation was unsuccessful.
410  */
411 long cm_NormalizeUtf16StringToUtf8(const wchar_t * src, int cch_src,
412                                    char * adest, int cch_adest)
413 {
414     if (!nls_init)
415         cm_InitNormalization();
416
417     if (cch_src < 0) {
418         size_t cch;
419
420         if (FAILED(StringCchLengthW(src, NLSMAXCCH, &cch)))
421             return E2BIG;
422
423         cch_src = (int)cch+1;
424     }
425
426     {
427         wchar_t nbuf[NLSMAXCCH];
428         wchar_t * normalized;
429         int cch_norm = NLSMAXCCH;
430
431         normalized = NormalizeUtf16String(src, cch_src, nbuf, &cch_norm);
432         if (normalized) {
433             cch_adest = WideCharToMultiByte(CP_UTF8, 0, normalized, cch_norm,
434                                             adest, cch_adest, NULL, 0);
435
436             if (normalized != nbuf && normalized)
437                 free(normalized);
438
439             return cch_adest;
440
441         } else {
442
443             return 0;
444
445         }
446     }
447 }
448
449 #define ESCVAL 0x1000
450 #define Esc(c) (ESCVAL + (short)(c))
451 #define IS_ESCAPED(c) (((c) & ESCVAL) == ESCVAL)
452
453 /* \brief Character sanitization map for CP-1252
454
455    The following map indicates which characters should be escaped in
456    the CP-1252 character map.  Characters that are documented as
457    illegal characters in a file name are marked as escaped.  Escaped
458    characters are marked using the ::Esc macro defined above.  The
459    following exceptions apply:
460
461    - Path delimeters '\\' and '/' are NOT escaped because the
462      sanitization map applies to paths.  While those characters are
463      illegal in filenames, they are legal in paths.
464
465    - Wildcard characters '*' and '?' ARE escaped.  The document
466      referred below does not specify these characters as invalid.
467      Since no other escape mechanism exists, names containing
468      wildcards are indistinguishable from actual wildcards used in SMB
469      requests.
470
471    - Reserved names are not and cannot be represented in this map.
472      Reserved names are :
473
474      CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7,
475      COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9,
476      CLOCK$
477
478    - Characters 0x80, 0x81, 0x8d, 0x8e, 0x8f, 0x90, 0x9d, 0x9e, 0x9f
479      are also escaped because they are unused in CP-1252 and hence
480      cannot be convered to a Unicode string.
481
482      Reserved names with extensions are also invalid. (i.e. NUL.txt)
483
484    \note The only bit we are actually interested in from the following
485      table is the ESCVAL bit.  However, the characters themselves are
486      included for ease of maintenance.
487
488    \see "Naming a File" topic in the Windows SDK.
489  */
490 static const short sanitized_escapes_1252[] = {
491     Esc(0x00),Esc(0x01),Esc(0x02),Esc(0x03),Esc(0x04),Esc(0x05),Esc(0x06),Esc(0x07),
492     Esc(0x08),Esc(0x09),Esc(0x0a),Esc(0x0b),Esc(0x0c),Esc(0x0d),Esc(0x0e),Esc(0x0f),
493     Esc(0x10),Esc(0x11),Esc(0x12),Esc(0x13),Esc(0x14),Esc(0x15),Esc(0x16),Esc(0x17),
494     Esc(0x18),Esc(0x19),Esc(0x1a),Esc(0x1b),Esc(0x1c),Esc(0x1d),Esc(0x1e),Esc(0x1f),
495     ' ','!',Esc('"'),'#','$','%','&','\'','(',')',Esc('*'),'+',',','-','.','/',
496     '0','1','2','3','4','5','6','7','8','9',Esc(':'),';',Esc('<'),'=',Esc('>'),Esc('?'),
497     '@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
498     'P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_',
499     '`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
500     'p','q','r','s','t','u','v','w','x','y','z','{',Esc('|'),'}','~',Esc(0x7f),
501     Esc(0x80),Esc(0x81),0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,Esc(0x8d),Esc(0x8e),Esc(0x8f),
502     Esc(0x90),0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,Esc(0x9d),Esc(0x9e),0x9f,
503     0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
504     0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
505     0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,
506     0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,
507     0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,
508     0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
509 };
510
511 static int sanitize_bytestring(const char * src, int cch_src,
512                                char * odest, int cch_dest)
513 {
514     char * dest = odest;
515
516     if (!nls_init)
517         cm_InitNormalization();
518
519     while (cch_src > 0 && *src && cch_dest > 0) {
520
521         unsigned short rc;
522
523         rc = sanitized_escapes_1252[*src];
524         if (IS_ESCAPED(rc)) {
525             static const char hex[] =
526                 {'0','1','2','3','4','5','6','7',
527                  '8','9','a','b','c','d','e','f'};
528
529             if (cch_dest < 3) {
530                 *dest++ = '\0';
531                 return 0;
532             }
533
534             *dest++ = '%';
535             *dest++ = hex[(((int)*src) >> 4) & 0x0f];
536             *dest++ = hex[(((int)*src) & 0x0f)];
537             cch_dest -= 3;
538
539         } else {
540             *dest++ = *src;
541             cch_dest--;
542         }
543
544         cch_src--;
545         src++;
546     }
547
548     if (cch_src > 0 && cch_dest > 0) {
549         *dest++ = '\0';
550     }
551
552     return (int)(dest - odest);
553 }
554
555 static int sanitize_utf16char(wchar_t c, wchar_t ** pdest, size_t * pcch)
556 {
557     if (*pcch >= 6) {
558         StringCchPrintfExW(*pdest, *pcch, pdest, pcch, 0, L"%%%04x", (int) c);
559         return 1;
560     } else {
561         return 0;
562     }
563 }
564
565 static int sanitize_utf16string(const wchar_t * src, size_t cch_src,
566                                 wchar_t * dest, size_t cch_dest)
567 {
568     int cch_dest_o = cch_dest;
569
570     if (dest == NULL) {
571         /* only estimating */
572         for (cch_dest = 0; cch_src > 0;) {
573             if (*src >= 0xd800 && *src < 0xdc00) {
574                 if (cch_src <= 1 || src[1] < 0xdc00 || src[1] > 0xdfff) {
575                     /* dangling surrogate */
576                     src++;
577                     cch_src --;
578                     cch_dest += 5;
579                 } else {
580                     /* surrogate pair */
581                     src += 2;
582                     cch_src -= 2;
583                     cch_dest += 2;
584                 }
585             } else if (*src >= 0xdc00 && *src <= 0xdfff) {
586                 /* dangling surrogate */
587                 src++;
588                 cch_src --;
589                 cch_dest += 5;
590             } else {
591                 /* normal char */
592                 src++; cch_src --;
593                 cch_dest++;
594             }
595         }
596
597         return cch_dest;
598     }
599
600     while (cch_src > 0 && cch_dest > 0) {
601         if (*src >= 0xd800 && *src < 0xdc00) {
602             if (cch_src <= 1 || src[1] < 0xdc00 || src[1] > 0xdfff) {
603                 if (!sanitize_utf16char(*src++, &dest, &cch_dest))
604                     return 0;
605                 cch_src--;
606             } else {
607                 /* found a surrogate pair */
608                 *dest++ = *src++;
609                 *dest++ = *src++;
610                 cch_dest -= 2; cch_src -= 2;
611             }
612         } else if (*src >= 0xdc00 && *src <= 0xdfff) {
613             if (!sanitize_utf16char(*src++, &dest, &cch_dest))
614                 return 0;
615             cch_src--;
616         } else {
617             *dest++ = *src++;
618             cch_dest--; cch_src--;
619         }
620     }
621
622     return (cch_src == 0) ? cch_dest_o - cch_dest : 0;
623 }
624
625 #undef Esc
626 #undef IS_ESCAPED
627 #undef ESCVAL
628
629 long cm_NormalizeUtf8StringToUtf16(const char * src, int cch_src,
630                                    wchar_t * dest, int cch_dest)
631 {
632     wchar_t wsrcbuf[NLSMAXCCH];
633     wchar_t *wnorm;
634     int cch;
635     int cch_norm;
636
637     if (!nls_init)
638         cm_InitNormalization();
639
640     /* Get some edge cases out first, so we don't have to worry about
641        cch_src being 0 etc. */
642     if (cch_src == 0) {
643         return 0;
644     } else if (*src == '\0') {
645         if (cch_dest >= 1)
646             *dest = L'\0';
647         return 1;
648     }
649
650     if (dest && cch_dest > 0) {
651         dest[0] = L'\0';
652     }
653
654     if (cch_src == -1) {
655         cch_src = (int)strlen(src) + 1;
656     }
657
658     cch = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src,
659                               cch_src * sizeof(char), wsrcbuf, NLSMAXCCH);
660
661     if (cch != 0 && !cm_is_valid_utf16(wsrcbuf, cch)) {
662         wchar_t wsanitized[NLSMAXCCH];
663
664         /* We successfully converted, but the resulting UTF-16 string
665            has dangling surrogates.  We should try and escape those
666            next.  */
667         cch = sanitize_utf16string(wsrcbuf, cch, wsanitized, NLSMAXCCH);
668         if (cch != 0) {
669             memcpy(wsrcbuf, wsanitized, cch * sizeof(wchar_t));
670         }
671     }
672
673     if (cch == 0) {
674         if (GetLastError() == ERROR_NO_UNICODE_TRANSLATION) {
675             char sanitized[NLSMAXCCH];
676             int cch_sanitized;
677
678             /* If src doesn't have a unicode translation, then it
679                wasn't valid UTF-8.  In this case, we assume that src
680                is CP-1252 and then try to convert again.  But before
681                that, we use a translation table to "sanitize" the
682                input. */
683
684             cch_sanitized = sanitize_bytestring(src, cch_src, sanitized,
685                                                 sizeof(sanitized)/sizeof(char));
686
687             if (cch_sanitized == 0) {
688 #ifdef DEBUG_UNICODE
689                 DebugBreak();
690 #endif
691                 return 0;
692             }
693
694             cch = MultiByteToWideChar(1252, 0, sanitized,
695                                       cch_sanitized * sizeof(char), wsrcbuf, NLSMAXCCH);
696             if (cch == 0) {
697                 /* Well, that didn't work either.  Something is very wrong. */
698 #ifdef DEBUG_UNICODE
699                 DebugBreak();
700 #endif
701                 return 0;
702             }
703         } else {
704             return 0;
705         }
706     }
707
708     cch_norm = cch_dest;
709     wnorm = NormalizeUtf16String(wsrcbuf, cch, dest, &cch_norm);
710     if (wnorm == NULL) {
711 #ifdef DEBUG_UNICODE
712         DebugBreak();
713 #endif
714         return 0;
715     }
716
717     if (wnorm != dest) {
718         /* The buffer was insufficient */
719         if (dest != NULL && cch_dest > 1) {
720             *dest = L'\0';
721             cch_norm = 0;
722         }
723
724         free(wnorm);
725     }
726
727     return cch_norm;
728 }
729
730 cm_normchar_t *cm_NormalizeUtf8StringToUtf16Alloc(const cm_utf8char_t * src, int cch_src,
731                                                   int *pcch_dest)
732 {
733     wchar_t wsrcbuf[NLSMAXCCH];
734     wchar_t *wnorm;
735     int cch;
736     int cch_norm;
737
738     if (!nls_init)
739         cm_InitNormalization();
740
741     /* Get some edge cases out first, so we don't have to worry about
742        cch_src being 0 etc. */
743     if (cch_src == 0 || src == NULL || *src == '\0') {
744         if (pcch_dest)
745             *pcch_dest = ((cch_src != 0)? 1 : 0);
746         return wcsdup(L"");
747     }
748
749     if (cch_src == -1) {
750         cch_src = (int)strlen(src) + 1;
751     }
752
753     cch = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src,
754                               cch_src * sizeof(char), wsrcbuf, NLSMAXCCH);
755
756     if (cch != 0 && !cm_is_valid_utf16(wsrcbuf, cch)) {
757         wchar_t wsanitized[NLSMAXCCH];
758
759         /* We successfully converted, but the resulting UTF-16 string
760            has dangling surrogates.  We should try and escape those
761            next.  */
762         cch = sanitize_utf16string(wsrcbuf, cch, wsanitized, NLSMAXCCH);
763         if (cch != 0) {
764             memcpy(wsrcbuf, wsanitized, cch * sizeof(wchar_t));
765         }
766     }
767
768     if (cch == 0) {
769         if (GetLastError() == ERROR_NO_UNICODE_TRANSLATION) {
770             char sanitized[NLSMAXCCH];
771             int cch_sanitized;
772
773             /* If src doesn't have a unicode translation, then it
774                wasn't valid UTF-8.  In this case, we assume that src
775                is CP-1252 and then try to convert again.  But before
776                that, we use a translation table to "sanitize" the
777                input. */
778
779             cch_sanitized = sanitize_bytestring(src, cch_src, sanitized,
780                                                 sizeof(sanitized)/sizeof(char));
781
782             if (cch_sanitized == 0) {
783 #ifdef DEBUG_UNICODE
784                 DebugBreak();
785 #endif
786                 return NULL;
787             }
788
789             cch = MultiByteToWideChar(1252, 0, sanitized,
790                                       cch_sanitized * sizeof(char), wsrcbuf, NLSMAXCCH);
791             if (cch == 0) {
792                 /* Well, that didn't work either.  Something is very wrong. */
793 #ifdef DEBUG_UNICODE
794                 DebugBreak();
795 #endif
796                 return NULL;
797             }
798         } else {
799             return NULL;
800         }
801     }
802
803     cch_norm = 0;
804     wnorm = NormalizeUtf16String(wsrcbuf, cch, NULL, &cch_norm);
805     if (wnorm == NULL) {
806 #ifdef DEBUG_UNICODE
807         DebugBreak();
808 #endif
809         return NULL;
810     }
811
812     if (pcch_dest)
813         *pcch_dest = cch_norm;
814
815     return wnorm;
816 }
817
818 int cm_Utf8ToUtf16(const cm_utf8char_t * src, int cch_src,
819                    cm_unichar_t * dest, int cch_dest)
820 {
821     int cch;
822
823     if (cch_dest >= 1 && dest != NULL) {
824         dest[0] = L'\0';
825     }
826
827     if (!nls_init)
828         cm_InitNormalization();
829
830     if (cch_src == -1) {
831         cch_src = (int)strlen(src) + 1;
832     }
833
834     cch = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src,
835                               cch_src * sizeof(char), dest, cch_dest);
836
837     if (cch != 0 && !cm_is_valid_utf16(dest, cch)) {
838         wchar_t wsanitized[NLSMAXCCH];
839
840         cch = sanitize_utf16string(dest, cch, wsanitized, NLSMAXCCH);
841         if (cch != 0) {
842             memcpy(dest, wsanitized, cch * sizeof(wchar_t));
843         }
844     }
845
846     if (cch == 0) {
847         if (GetLastError() == ERROR_NO_UNICODE_TRANSLATION) {
848             char sanitized[NLSMAXCCH];
849             int cch_sanitized;
850
851             /* If src doesn't have a unicode translation, then it
852                wasn't valid UTF-8.  In this case, we assume that src
853                is CP-1252 and then try to convert again.  But before
854                that, we use a translation table to "sanitize" the
855                input. */
856
857             cch_sanitized = sanitize_bytestring(src, cch_src, sanitized,
858                                                 sizeof(sanitized)/sizeof(char));
859
860             if (cch_sanitized == 0) {
861 #ifdef DEBUG_UNICODE
862                 DebugBreak();
863 #endif
864                 return 0;
865             }
866
867             cch = MultiByteToWideChar(1252, 0, sanitized,
868                                       cch_sanitized * sizeof(char), dest, cch_dest);
869             if (cch == 0) {
870                 /* Well, that didn't work either.  Something is very wrong. */
871 #ifdef DEBUG_UNICODE
872                 DebugBreak();
873 #endif
874                 return 0;
875             } else {
876                 return cch;
877             }
878
879         } else {
880             return 0;
881         }
882     } else {
883         return cch;
884     }
885 }
886
887 cm_unichar_t  * cm_Utf8ToUtf16Alloc(const cm_utf8char_t * src, int cch_src, int *pcch_dest)
888 {
889     cm_unichar_t * ustr = NULL;
890     int cch;
891
892     if (!nls_init)
893         cm_InitNormalization();
894
895     if (cch_src == 0 || src == NULL || *src == '\0') {
896         if (pcch_dest)
897             *pcch_dest = ((cch_src != 0)? 1 : 0);
898         return wcsdup(L"");
899     }
900
901     if (cch_src == -1) {
902         cch_src = (int)strlen(src) + 1;
903     }
904
905     cch = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src,
906                               cch_src * sizeof(char), NULL, 0);
907
908     if (cch == 0) {
909         if (GetLastError() == ERROR_NO_UNICODE_TRANSLATION) {
910             char sanitized[NLSMAXCCH];
911             int cch_sanitized;
912
913             /* If src doesn't have a unicode translation, then it
914                wasn't valid UTF-8.  In this case, we assume that src
915                is CP-1252 and then try to convert again.  But before
916                that, we use a translation table to "sanitize" the
917                input. */
918
919             cch_sanitized = sanitize_bytestring(src, cch_src, sanitized,
920                                                 sizeof(sanitized)/sizeof(char));
921
922             if (cch_sanitized == 0) {
923 #ifdef DEBUG_UNICODE
924                 DebugBreak();
925 #endif
926                 return NULL;
927             }
928
929             cch = MultiByteToWideChar(1252, 0, sanitized,
930                                       cch_sanitized * sizeof(char), NULL, 0);
931             if (cch == 0) {
932                 /* Well, that didn't work either.  Something is very wrong. */
933 #ifdef DEBUG_UNICODE
934                 DebugBreak();
935 #endif
936                 return NULL;
937             }
938
939             ustr = malloc((cch + 1) * sizeof(wchar_t));
940
941             cch = MultiByteToWideChar(1252, 0, sanitized,
942                                       cch_sanitized * sizeof(char), ustr, cch);
943             ustr[cch] = 0;
944         } else {
945             return NULL;
946         }
947     } else {
948
949         ustr = malloc((cch + 1) * sizeof(wchar_t));
950
951         cch = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src,
952                                   cch_src * sizeof(char), ustr, cch);
953         ustr[cch] = 0;
954
955         if (!cm_is_valid_utf16(ustr, cch)) {
956             cm_unichar_t * us = NULL;
957             int cch_s;
958
959             cch_s = sanitize_utf16string(ustr, cch, NULL, 0);
960             if (cch_s != 0) {
961                 us = malloc(cch_s * sizeof(wchar_t));
962                 cch_s = sanitize_utf16string(ustr, cch, us, cch_s);
963             }
964
965             if (cch_s != 0) {
966                 free(ustr);
967                 ustr = us;
968                 us = NULL;
969             } else {
970                 if (us)
971                     free(us);
972                 free(ustr);
973                 ustr = NULL;
974             }
975         }
976     }
977
978     if (pcch_dest)
979         *pcch_dest = cch;
980
981     return ustr;
982 }
983
984
985
986 /* \brief Normalize a UTF-8 string.
987
988    \param[in] src String to normalize.
989
990    \param[in] cch_src : Count of characters in src.  If this value is
991        -1, then src is assumed to be NULL terminated.  The translated
992        string will be NULL terminated only if this is -1 or the count
993        includes the terminating NULL.
994
995    \param[out] adest : Destination string.  Only considered valid if
996        \a cch_adest is non-zero.
997
998    \param[in] cch_adest : Number of characters in the destination
999        string.  If this is zero, then the return value is the number
1000        of bytes required.
1001
1002    \return If \a cch_adest is non-zero, then the return value is the
1003        number of bytes stored into adest.  If \a cch_adest is zero,
1004        then the return value is the number of bytes required.  In both
1005        cases, the return value is 0 if the call was unsuccessful.
1006  */
1007 long cm_NormalizeUtf8String(const char * src, int cch_src,
1008                             char * adest, int cch_adest)
1009 {
1010     wchar_t wsrcbuf[NLSMAXCCH];
1011     wchar_t *wnorm;
1012     int cch;
1013     int cch_norm;
1014
1015     if (!nls_init)
1016         cm_InitNormalization();
1017
1018     /* Get some edge cases out first, so we don't have to worry about
1019        cch_src being 0 etc. */
1020     if (cch_src == 0) {
1021         return 0;
1022     } else if (*src == '\0') {
1023         if (cch_adest >= 1)
1024             *adest = '\0';
1025         return 1;
1026     }
1027
1028     if (cch_src == -1) {
1029         cch_src = (int)strlen(src) + 1;
1030     }
1031
1032     cch = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src,
1033                               cch_src * sizeof(char), wsrcbuf, NLSMAXCCH);
1034
1035     if (cch != 0 && !cm_is_valid_utf16(wsrcbuf, cch)) {
1036         wchar_t wsanitized[NLSMAXCCH];
1037
1038         cch = sanitize_utf16string(wsrcbuf, cch, wsanitized, NLSMAXCCH);
1039         if (cch != 0) {
1040             memcpy(wsrcbuf, wsanitized, cch * sizeof(wchar_t));
1041         }
1042     }
1043
1044     if (cch == 0) {
1045         if (GetLastError() == ERROR_NO_UNICODE_TRANSLATION) {
1046             char sanitized[NLSMAXCCH];
1047             int cch_sanitized;
1048
1049             /* If src doesn't have a unicode translation, then it
1050                wasn't valid UTF-8.  In this case, we assume that src
1051                is CP-1252 and then try to convert again.  But before
1052                that, we use a translation table to "sanitize" the
1053                input. */
1054
1055             cch_sanitized = sanitize_bytestring(src, cch_src, sanitized,
1056                                                 sizeof(sanitized)/sizeof(char));
1057
1058             if (cch_sanitized == 0) {
1059 #ifdef DEBUG_UNICODE
1060                 DebugBreak();
1061 #endif
1062                 return 0;
1063             }
1064
1065             cch = MultiByteToWideChar(1252, 0, sanitized,
1066                                       cch_sanitized * sizeof(char), wsrcbuf, NLSMAXCCH);
1067             if (cch == 0) {
1068                 /* Well, that didn't work either.  Something is very wrong. */
1069 #ifdef DEBUG_UNICODE
1070                 DebugBreak();
1071 #endif
1072                 return 0;
1073             }
1074         } else {
1075             return 0;
1076         }
1077     }
1078
1079     cch_norm = 0;
1080     wnorm = NormalizeUtf16String(wsrcbuf, cch, NULL, &cch_norm);
1081     if (wnorm == NULL) {
1082 #ifdef DEBUG_UNICODE
1083         DebugBreak();
1084 #endif
1085         return 0;
1086     }
1087
1088     cch = WideCharToMultiByte(CP_UTF8, 0, wnorm,
1089                               cch_norm, adest, cch_adest * sizeof(char),
1090                               NULL, FALSE);
1091
1092     if (wnorm)
1093         free(wnorm);
1094
1095     return cch;
1096 }
1097
1098 /*! \brief Case insensitive comparison with specific length
1099
1100   \param[in] str1 First string to compare.  Assumed to be encoded in UTF-8.
1101
1102   \param[in] str2 Second string to compare.  Assumed to be encoded in UTF-8.
1103
1104   \param[in] n Max byte count.
1105
1106  */
1107 int cm_strnicmp_utf8(const char * str1, const char * str2, int n)
1108 {
1109     wchar_t wstr1[NLSMAXCCH];
1110     int len1;
1111     int len2;
1112     wchar_t wstr2[NLSMAXCCH];
1113     int rv;
1114
1115     if (!nls_init)
1116         cm_InitNormalization();
1117
1118     if (n == 0)
1119         return 0;
1120
1121     /* first check for NULL pointers (assume NULL < "") */
1122     if (str1 == NULL) {
1123         if (str2 == NULL)
1124             return 0;
1125         else
1126             return -1;
1127     } else if (str2 == NULL) {
1128         return 1;
1129     }
1130
1131     len1 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str1, n, wstr1, NLSMAXCCH);
1132     if (len1 == 0) {
1133 #ifdef DEBUG
1134         DebugBreak();
1135 #endif
1136         wstr1[0] = L'\0';
1137     }
1138
1139     len2 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str2, n, wstr2, NLSMAXCCH);
1140     if (len2 == 0) {
1141 #ifdef DEBUG
1142         DebugBreak();
1143 #endif
1144         wstr2[0] = L'\0';
1145     }
1146
1147     rv = CompareStringW(nls_lcid, NORM_IGNORECASE, wstr1, len1, wstr2, len2);
1148     if (rv > 0)
1149         return (rv - 2);
1150     else {
1151 #ifdef DEBUG
1152         DebugBreak();
1153 #endif
1154         return 0;
1155     }
1156 }
1157
1158 int cm_strnicmp_utf16(const cm_unichar_t * str1, const cm_unichar_t * str2, int len)
1159 {
1160     int rv;
1161     size_t cch1;
1162     size_t cch2;
1163
1164     if (!nls_init)
1165         cm_InitNormalization();
1166
1167     if (len == 0)
1168         return 0;
1169
1170     /* first check for NULL pointers */
1171     if (str1 == NULL) {
1172         if (str2 == NULL)
1173             return 0;
1174         else
1175             return -1;
1176     } else if (str2 == NULL) {
1177         return 1;
1178     }
1179
1180     if (FAILED(StringCchLengthW(str1, len, &cch1)))
1181         cch1 = len;
1182
1183     if (FAILED(StringCchLengthW(str2, len, &cch2)))
1184         cch2 = len;
1185
1186     rv = CompareStringW(nls_lcid, NORM_IGNORECASE, str1, (int)cch1, str2, (int)cch2);
1187     if (rv > 0)
1188         return (rv - 2);
1189     else {
1190 #ifdef DEBUG
1191         DebugBreak();
1192 #endif
1193         return 0;
1194     }
1195 }
1196
1197 int cm_stricmp_utf16(const cm_unichar_t * str1, const cm_unichar_t * str2)
1198 {
1199     int rv;
1200
1201     if (!nls_init)
1202         cm_InitNormalization();
1203
1204     /* first check for NULL pointers */
1205     if (str1 == NULL) {
1206         if (str2 == NULL)
1207             return 0;
1208         else
1209             return -1;
1210     } else if (str2 == NULL) {
1211         return 1;
1212     }
1213
1214     rv = CompareStringW(nls_lcid, NORM_IGNORECASE, str1, -1, str2, -1);
1215     if (rv > 0)
1216         return (rv - 2);
1217     else {
1218 #ifdef DEBUG
1219         DebugBreak();
1220 #endif
1221         return 0;
1222     }
1223 }
1224
1225 cm_unichar_t *cm_strlwr_utf16(cm_unichar_t * str)
1226 {
1227     int rv;
1228     int len;
1229
1230     if (!nls_init)
1231         cm_InitNormalization();
1232
1233     len = (int)wcslen(str) + 1;
1234     rv = LCMapStringW(nls_lcid, LCMAP_LOWERCASE, str, len, str, len);
1235 #ifdef DEBUG
1236     if (rv == 0) {
1237         DebugBreak();
1238     }
1239 #endif
1240
1241     return str;
1242 }
1243
1244 cm_unichar_t *cm_strupr_utf16(cm_unichar_t * str)
1245 {
1246     int rv;
1247     int len;
1248
1249     if (!nls_init)
1250         cm_InitNormalization();
1251
1252     len = (int)wcslen(str) + 1;
1253     rv = LCMapStringW(nls_lcid, LCMAP_UPPERCASE, str, len, str, len);
1254 #ifdef DEBUG
1255     if (rv == 0) {
1256         DebugBreak();
1257     }
1258 #endif
1259
1260     return str;
1261 }
1262
1263
1264 int cm_stricmp_utf8(const char * str1, const char * str2)
1265 {
1266     wchar_t wstr1[NLSMAXCCH];
1267     int len1;
1268     int len2;
1269     wchar_t wstr2[NLSMAXCCH];
1270     int rv;
1271
1272     if (!nls_init)
1273         cm_InitNormalization();
1274
1275     /* first check for NULL pointers */
1276     if (str1 == NULL) {
1277         if (str2 == NULL)
1278             return 0;
1279         else
1280             return -1;
1281     } else if (str2 == NULL) {
1282         return 1;
1283     }
1284
1285     len1 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str1, -1, wstr1, NLSMAXCCH);
1286     if (len1 == 0) {
1287 #ifdef DEBUG
1288         DebugBreak();
1289 #endif
1290         wstr1[0] = L'\0';
1291     }
1292
1293     len2 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str2, -1, wstr2, NLSMAXCCH);
1294     if (len2 == 0) {
1295 #ifdef DEBUG
1296         DebugBreak();
1297 #endif
1298         wstr2[0] = L'\0';
1299     }
1300
1301     rv = CompareStringW(nls_lcid, NORM_IGNORECASE, wstr1, len1, wstr2, len2);
1302     if (rv > 0)
1303         return (rv - 2);
1304     else {
1305 #ifdef DEBUG
1306         DebugBreak();
1307 #endif
1308         return 0;
1309     }
1310 }
1311
1312 #if 0
1313 wchar_t * strupr_utf16(wchar_t * wstr, size_t cbstr)
1314 {
1315     wchar_t wstrd[NLSMAXCCH];
1316     int len;
1317
1318     if (!nls_init)
1319         cm_InitNormalization();
1320
1321     len = cbstr / sizeof(wchar_t);
1322     len = LCMapStringW(nls_lcid, LCMAP_UPPERCASE, wstr, len, wstrd, NLSMAXCCH);
1323     StringCbCopyW(wstr, cbstr, wstrd);
1324
1325     return wstr;
1326 }
1327 #endif
1328
1329 char * strupr_utf8(char * str, size_t cbstr)
1330 {
1331     wchar_t wstr[NLSMAXCCH];
1332     wchar_t wstrd[NLSMAXCCH];
1333     int len;
1334
1335     if (!nls_init)
1336         cm_InitNormalization();
1337
1338     len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, wstr, NLSMAXCCH);
1339     if (len == 0)
1340         return str;
1341
1342     len = LCMapStringW(nls_lcid, LCMAP_UPPERCASE, wstr, len, wstrd, NLSMAXCCH);
1343
1344     len = WideCharToMultiByte(CP_UTF8, 0, wstrd, -1, str, (int)cbstr, NULL, FALSE);
1345
1346     return str;
1347 }
1348
1349 char * char_next_utf8(const char * c)
1350 {
1351 #define CH (*((const unsigned char *)c))
1352
1353     if ((CH & 0x80) == 0)
1354         return (char *) c+1;
1355     else {
1356         switch (CH & 0xf0) {
1357         case 0xc0:
1358         case 0xd0:
1359             return (char *) c+2;
1360
1361         case 0xe0:
1362             return (char *) c+3;
1363
1364         case 0xf0:
1365             return (char *) c+4;
1366
1367         default:
1368             return (char *) c+1;
1369         }
1370     }
1371 #undef CH
1372 }
1373
1374
1375 char * char_prev_utf8(const char * c)
1376 {
1377 #define CH (*((const unsigned char *)c))
1378
1379     c--;
1380
1381     if ((CH & 0x80) == 0)
1382         return (char *) c;
1383     else
1384         while ((CH & 0xc0) == 0x80)
1385             (char *) c--;
1386     return (char *) c;
1387
1388 #undef CH
1389 }
1390
1391 wchar_t * char_next_utf16(const wchar_t * c)
1392 {
1393     unsigned short sc = (unsigned short) *c;
1394
1395     if (sc >= 0xd800 && sc <= 0xdbff)
1396         return (wchar_t *) c+2;
1397     return (wchar_t *) c+1;
1398 }
1399
1400 wchar_t * char_prev_utf16(const wchar_t * c)
1401 {
1402     unsigned short sc = (unsigned short) *(--c);
1403
1404     if (sc >= 0xdc00 && sc <= 0xdfff)
1405         return (wchar_t *) --c;
1406     return (wchar_t *) c;
1407 }
1408
1409 wchar_t * char_this_utf16(const wchar_t * c)
1410 {
1411     unsigned short sc = (unsigned short) *c;
1412
1413     if (sc >= 0xdc00 && sc <= 0xdfff)
1414         return (wchar_t *) --c;
1415     return (wchar_t *) c;
1416 }
1417
1418 int cm_is_valid_utf16(const wchar_t * c, int cch)
1419 {
1420     if (cch < 0)
1421         cch = wcslen(c) + 1;
1422
1423     for (; cch > 0; c++, cch--) {
1424         if (*c >= 0xd800 && *c < 0xdc00) {
1425             c++; cch--;
1426             if (cch == 0 || *c < 0xdc00 || *c > 0xdfff)
1427                 return 0;
1428         } else if (*c >= 0xdc00 && *c <= 0xdfff) {
1429             return 0;
1430         }
1431     }
1432
1433     return 1;
1434 }
1435
1436 #ifdef DEBUG
1437 wchar_t * cm_GetRawCharsAlloc(const wchar_t * c, int len)
1438 {
1439     wchar_t * ret;
1440     wchar_t * current;
1441     size_t cb;
1442
1443     if (len == -1)
1444         len = wcslen(c);
1445
1446     if (len == 0)
1447         return wcsdup(L"(empty)");
1448
1449     cb = len * 5 * sizeof(wchar_t);
1450     current = ret = malloc(cb);
1451     if (ret == NULL)
1452         return NULL;
1453
1454     for (; len > 0; ++c, --len) {
1455         StringCbPrintfExW(current, cb, &current, &cb, 0,
1456                          L"%04x", (int) *c);
1457         if (len > 1)
1458             StringCbCatExW(current, cb, L",", &current, &cb, 0);
1459     }
1460
1461     return ret;
1462 }
1463 #endif