Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Commit b5dc523

Browse files
kbleeskasal
authored andcommitted
Win32: add Unicode conversion functions
Add Unicode conversion functions to convert between Windows native UTF-16LE encoding to UTF-8 and back. To support repositories with legacy-encoded file names, the UTF-8 to UTF-16 conversion function tries to create valid, unique file names even for invalid UTF-8 byte sequences, so that these repositories can be checked out without error. The current implementation leaves invalid UTF-8 bytes in range 0xa0 - 0xff as is (producing printable Unicode chars \u00a0 - \u00ff, equivalent to ISO-8859-1), and converts 0x80 - 0x9f to hex-code (\u0080 - \u009f are control chars). The Windows MultiByteToWideChar API was not used as it either drops invalid UTF-8 sequences (on Win2k/XP; producing non-unique or even empty file names) or converts them to the replacement char \ufffd (Vista/7; causing ERROR_INVALID_NAME in subsequent calls to file system APIs). Signed-off-by: Karsten Blees <[email protected]>
1 parent c575cc5 commit b5dc523

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed

compat/mingw.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,91 @@ pid_t waitpid(pid_t pid, int *status, int options)
18111811
return -1;
18121812
}
18131813

1814+
int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
1815+
{
1816+
int upos = 0, wpos = 0;
1817+
const unsigned char *utf = (const unsigned char*) utfs;
1818+
if (!utf || !wcs || wcslen < 1) {
1819+
errno = EINVAL;
1820+
return -1;
1821+
}
1822+
/* reserve space for \0 */
1823+
wcslen--;
1824+
if (utflen < 0)
1825+
utflen = INT_MAX;
1826+
1827+
while (upos < utflen) {
1828+
int c = utf[upos++] & 0xff;
1829+
if (utflen == INT_MAX && c == 0)
1830+
break;
1831+
1832+
if (wpos >= wcslen) {
1833+
wcs[wpos] = 0;
1834+
errno = ERANGE;
1835+
return -1;
1836+
}
1837+
1838+
if (c < 0x80) {
1839+
/* ASCII */
1840+
wcs[wpos++] = c;
1841+
} else if (c >= 0xc2 && c < 0xe0 && upos < utflen &&
1842+
(utf[upos] & 0xc0) == 0x80) {
1843+
/* 2-byte utf-8 */
1844+
c = ((c & 0x1f) << 6);
1845+
c |= (utf[upos++] & 0x3f);
1846+
wcs[wpos++] = c;
1847+
} else if (c >= 0xe0 && c < 0xf0 && upos + 1 < utflen &&
1848+
!(c == 0xe0 && utf[upos] < 0xa0) && /* over-long encoding */
1849+
(utf[upos] & 0xc0) == 0x80 &&
1850+
(utf[upos + 1] & 0xc0) == 0x80) {
1851+
/* 3-byte utf-8 */
1852+
c = ((c & 0x0f) << 12);
1853+
c |= ((utf[upos++] & 0x3f) << 6);
1854+
c |= (utf[upos++] & 0x3f);
1855+
wcs[wpos++] = c;
1856+
} else if (c >= 0xf0 && c < 0xf5 && upos + 2 < utflen &&
1857+
wpos + 1 < wcslen &&
1858+
!(c == 0xf0 && utf[upos] < 0x90) && /* over-long encoding */
1859+
!(c == 0xf4 && utf[upos] >= 0x90) && /* > \u10ffff */
1860+
(utf[upos] & 0xc0) == 0x80 &&
1861+
(utf[upos + 1] & 0xc0) == 0x80 &&
1862+
(utf[upos + 2] & 0xc0) == 0x80) {
1863+
/* 4-byte utf-8: convert to \ud8xx \udcxx surrogate pair */
1864+
c = ((c & 0x07) << 18);
1865+
c |= ((utf[upos++] & 0x3f) << 12);
1866+
c |= ((utf[upos++] & 0x3f) << 6);
1867+
c |= (utf[upos++] & 0x3f);
1868+
c -= 0x10000;
1869+
wcs[wpos++] = 0xd800 | (c >> 10);
1870+
wcs[wpos++] = 0xdc00 | (c & 0x3ff);
1871+
} else if (c >= 0xa0) {
1872+
/* invalid utf-8 byte, printable unicode char: convert 1:1 */
1873+
wcs[wpos++] = c;
1874+
} else {
1875+
/* invalid utf-8 byte, non-printable unicode: convert to hex */
1876+
static const char *hex = "0123456789abcdef";
1877+
wcs[wpos++] = hex[c >> 4];
1878+
if (wpos < wcslen)
1879+
wcs[wpos++] = hex[c & 0x0f];
1880+
}
1881+
}
1882+
wcs[wpos] = 0;
1883+
return wpos;
1884+
}
1885+
1886+
int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
1887+
{
1888+
if (!wcs || !utf || utflen < 1) {
1889+
errno = EINVAL;
1890+
return -1;
1891+
}
1892+
utflen = WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf, utflen, NULL, NULL);
1893+
if (utflen)
1894+
return utflen - 1;
1895+
errno = ERANGE;
1896+
return -1;
1897+
}
1898+
18141899
/*
18151900
* Disable MSVCRT command line wildcard expansion (__getmainargs called from
18161901
* mingw startup code, see init.c in mingw runtime).

compat/mingw.h

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,110 @@ void mingw_open_html(const char *path);
351351
char **make_augmented_environ(const char *const *vars);
352352
void free_environ(char **env);
353353

354+
/**
355+
* Converts UTF-8 encoded string to UTF-16LE.
356+
*
357+
* To support repositories with legacy-encoded file names, invalid UTF-8 bytes
358+
* 0xa0 - 0xff are converted to corresponding printable Unicode chars \u00a0 -
359+
* \u00ff, and invalid UTF-8 bytes 0x80 - 0x9f (which would make non-printable
360+
* Unicode) are converted to hex-code.
361+
*
362+
* Lead-bytes not followed by an appropriate number of trail-bytes, over-long
363+
* encodings and 4-byte encodings > \u10ffff are detected as invalid UTF-8.
364+
*
365+
* Maximum space requirement for the target buffer is two wide chars per UTF-8
366+
* char (((strlen(utf) * 2) + 1) [* sizeof(wchar_t)]).
367+
*
368+
* The maximum space is needed only if the entire input string consists of
369+
* invalid UTF-8 bytes in range 0x80-0x9f, as per the following table:
370+
*
371+
* | | UTF-8 | UTF-16 |
372+
* Code point | UTF-8 sequence | bytes | words | ratio
373+
* --------------+-------------------+-------+--------+-------
374+
* 000000-00007f | 0-7f | 1 | 1 | 1
375+
* 000080-0007ff | c2-df + 80-bf | 2 | 1 | 0.5
376+
* 000800-00ffff | e0-ef + 2 * 80-bf | 3 | 1 | 0.33
377+
* 010000-10ffff | f0-f4 + 3 * 80-bf | 4 | 2 (a) | 0.5
378+
* invalid | 80-9f | 1 | 2 (b) | 2
379+
* invalid | a0-ff | 1 | 1 | 1
380+
*
381+
* (a) encoded as UTF-16 surrogate pair
382+
* (b) encoded as two hex digits
383+
*
384+
* Note that, while the UTF-8 encoding scheme can be extended to 5-byte, 6-byte
385+
* or even indefinite-byte sequences, the largest valid code point \u10ffff
386+
* encodes as only 4 UTF-8 bytes.
387+
*
388+
* Parameters:
389+
* wcs: wide char target buffer
390+
* utf: string to convert
391+
* wcslen: size of target buffer (in wchar_t's)
392+
* utflen: size of string to convert, or -1 if 0-terminated
393+
*
394+
* Returns:
395+
* length of converted string (_wcslen(wcs)), or -1 on failure
396+
*
397+
* Errors:
398+
* EINVAL: one of the input parameters is invalid (e.g. NULL)
399+
* ERANGE: the output buffer is too small
400+
*/
401+
int xutftowcsn(wchar_t *wcs, const char *utf, size_t wcslen, int utflen);
402+
403+
/**
404+
* Simplified variant of xutftowcsn, assumes input string is \0-terminated.
405+
*/
406+
static inline int xutftowcs(wchar_t *wcs, const char *utf, size_t wcslen)
407+
{
408+
return xutftowcsn(wcs, utf, wcslen, -1);
409+
}
410+
411+
/**
412+
* Simplified file system specific variant of xutftowcsn, assumes output
413+
* buffer size is MAX_PATH wide chars and input string is \0-terminated,
414+
* fails with ENAMETOOLONG if input string is too long.
415+
*/
416+
static inline int xutftowcs_path(wchar_t *wcs, const char *utf)
417+
{
418+
int result = xutftowcsn(wcs, utf, MAX_PATH, -1);
419+
if (result < 0 && errno == ERANGE)
420+
errno = ENAMETOOLONG;
421+
return result;
422+
}
423+
424+
/**
425+
* Converts UTF-16LE encoded string to UTF-8.
426+
*
427+
* Maximum space requirement for the target buffer is three UTF-8 chars per
428+
* wide char ((_wcslen(wcs) * 3) + 1).
429+
*
430+
* The maximum space is needed only if the entire input string consists of
431+
* UTF-16 words in range 0x0800-0xd7ff or 0xe000-0xffff (i.e. \u0800-\uffff
432+
* modulo surrogate pairs), as per the following table:
433+
*
434+
* | | UTF-16 | UTF-8 |
435+
* Code point | UTF-16 sequence | words | bytes | ratio
436+
* --------------+-----------------------+--------+-------+-------
437+
* 000000-00007f | 0000-007f | 1 | 1 | 1
438+
* 000080-0007ff | 0080-07ff | 1 | 2 | 2
439+
* 000800-00ffff | 0800-d7ff / e000-ffff | 1 | 3 | 3
440+
* 010000-10ffff | d800-dbff + dc00-dfff | 2 | 4 | 2
441+
*
442+
* Note that invalid code points > 10ffff cannot be represented in UTF-16.
443+
*
444+
* Parameters:
445+
* utf: target buffer
446+
* wcs: wide string to convert
447+
* utflen: size of target buffer
448+
*
449+
* Returns:
450+
* length of converted string, or -1 on failure
451+
*
452+
* Errors:
453+
* EINVAL: one of the input parameters is invalid (e.g. NULL)
454+
* ERANGE: the output buffer is too small
455+
*/
456+
int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen);
457+
354458
/*
355459
* A critical section used in the implementation of the spawn
356460
* functions (mingw_spawnv[p]e()) and waitpid(). Intialised in

0 commit comments

Comments
 (0)