]> granicus.if.org Git - postgresql/commitdiff
Make ts_locale.c's character-type functions cope with UTF-16.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 3 Nov 2018 17:56:10 +0000 (13:56 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 3 Nov 2018 17:56:10 +0000 (13:56 -0400)
On Windows, in UTF8 database encoding, what char2wchar() produces is
UTF16 not UTF32, ie, characters above U+FFFF will be represented by
surrogate pairs.  t_isdigit() and siblings did not account for this
and failed to provide a large enough result buffer.  That in turn
led to bogus "invalid multibyte character for locale" errors, because
contrary to what you might think from char2wchar()'s documentation,
its Windows code path doesn't cope sanely with buffer overflow.

The solution for t_isdigit() and siblings is pretty clear: provide
a 3-wchar_t result buffer not 2.

char2wchar() also needs some work to provide more consistent, and more
accurately documented, buffer overrun behavior.  But that's a bigger job
and it doesn't actually have any immediate payoff, so leave it for later.

Per bug #15476 from Kenji Uno, who deserves credit for identifying the
cause of the problem.  Back-patch to all active branches.

Discussion: https://postgr.es/m/15476-4314f480acf0f114@postgresql.org

src/backend/tsearch/ts_locale.c

index 85e1cd003ad0a44acd6528da2e252f28a3121be1..a369dad12be5083128e3a2b8b107e462682c5900 100644 (file)
@@ -23,18 +23,29 @@ static void tsearch_readline_callback(void *arg);
 
 #ifdef USE_WIDE_UPPER_LOWER
 
+/*
+ * The reason these functions use a 3-wchar_t output buffer, not 2 as you
+ * might expect, is that on Windows "wchar_t" is 16 bits and what we'll be
+ * getting from char2wchar() is UTF16 not UTF32.  A single input character
+ * may therefore produce a surrogate pair rather than just one wchar_t;
+ * we also need room for a trailing null.  When we do get a surrogate pair,
+ * we pass just the first code to iswdigit() etc, so that these functions will
+ * always return false for characters outside the Basic Multilingual Plane.
+ */
+#define WC_BUF_LEN  3
+
 int
 t_isdigit(const char *ptr)
 {
        int                     clen = pg_mblen(ptr);
-       wchar_t         character[2];
+       wchar_t         character[WC_BUF_LEN];
        Oid                     collation = DEFAULT_COLLATION_OID;              /* TODO */
        pg_locale_t mylocale = 0;       /* TODO */
 
        if (clen == 1 || lc_ctype_is_c(collation))
                return isdigit(TOUCHAR(ptr));
 
-       char2wchar(character, 2, ptr, clen, mylocale);
+       char2wchar(character, WC_BUF_LEN, ptr, clen, mylocale);
 
        return iswdigit((wint_t) character[0]);
 }
@@ -43,14 +54,14 @@ int
 t_isspace(const char *ptr)
 {
        int                     clen = pg_mblen(ptr);
-       wchar_t         character[2];
+       wchar_t         character[WC_BUF_LEN];
        Oid                     collation = DEFAULT_COLLATION_OID;              /* TODO */
        pg_locale_t mylocale = 0;       /* TODO */
 
        if (clen == 1 || lc_ctype_is_c(collation))
                return isspace(TOUCHAR(ptr));
 
-       char2wchar(character, 2, ptr, clen, mylocale);
+       char2wchar(character, WC_BUF_LEN, ptr, clen, mylocale);
 
        return iswspace((wint_t) character[0]);
 }
@@ -59,14 +70,14 @@ int
 t_isalpha(const char *ptr)
 {
        int                     clen = pg_mblen(ptr);
-       wchar_t         character[2];
+       wchar_t         character[WC_BUF_LEN];
        Oid                     collation = DEFAULT_COLLATION_OID;              /* TODO */
        pg_locale_t mylocale = 0;       /* TODO */
 
        if (clen == 1 || lc_ctype_is_c(collation))
                return isalpha(TOUCHAR(ptr));
 
-       char2wchar(character, 2, ptr, clen, mylocale);
+       char2wchar(character, WC_BUF_LEN, ptr, clen, mylocale);
 
        return iswalpha((wint_t) character[0]);
 }
@@ -75,14 +86,14 @@ int
 t_isprint(const char *ptr)
 {
        int                     clen = pg_mblen(ptr);
-       wchar_t         character[2];
+       wchar_t         character[WC_BUF_LEN];
        Oid                     collation = DEFAULT_COLLATION_OID;              /* TODO */
        pg_locale_t mylocale = 0;       /* TODO */
 
        if (clen == 1 || lc_ctype_is_c(collation))
                return isprint(TOUCHAR(ptr));
 
-       char2wchar(character, 2, ptr, clen, mylocale);
+       char2wchar(character, WC_BUF_LEN, ptr, clen, mylocale);
 
        return iswprint((wint_t) character[0]);
 }