mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-10-02 03:41:18 +02:00
Fix incautious handling of possibly-miscoded strings in client code.
An incorrectly-encoded multibyte character near the end of a string could cause various processing loops to run past the string's terminating NUL, with results ranging from no detectable issue to a program crash, depending on what happens to be in the following memory. This isn't an issue in the server, because we take care to verify the encoding of strings before doing any interesting processing on them. However, that lack of care leaked into client-side code which shouldn't assume that anyone has validated the encoding of its input. Although this is certainly a bug worth fixing, the PG security team elected not to regard it as a security issue, primarily because any untrusted text should be sanitized by PQescapeLiteral or the like before being incorporated into a SQL or psql command. (If an app fails to do so, the same technique can be used to cause SQL injection, with probably much more dire consequences than a mere client-program crash.) Those functions were already made proof against this class of problem, cf CVE-2006-2313. To fix, invent PQmblenBounded() which is like PQmblen() except it won't return more than the number of bytes remaining in the string. In HEAD we can make this a new libpq function, as PQmblen() is. It seems imprudent to change libpq's API in stable branches though, so in the back branches define PQmblenBounded as a macro in the files that need it. (Note that just changing PQmblen's behavior would not be a good idea; notably, it would completely break the escaping functions' defense against this exact problem. So we just want a version for those callers that don't have any better way of handling this issue.) Per private report from houjingyi. Back-patch to all supported branches.
This commit is contained in:
parent
b4c027b5f5
commit
5b64368742
@ -29,6 +29,8 @@
|
|||||||
#include "portability/instr_time.h"
|
#include "portability/instr_time.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
|
#define PQmblenBounded(s, e) strnlen(s, PQmblen(s, e))
|
||||||
|
|
||||||
static bool DescribeQuery(const char *query, double *elapsed_msec);
|
static bool DescribeQuery(const char *query, double *elapsed_msec);
|
||||||
static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
|
static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
|
||||||
static bool command_no_begin(const char *query);
|
static bool command_no_begin(const char *query);
|
||||||
@ -1842,7 +1844,7 @@ skip_white_space(const char *query)
|
|||||||
|
|
||||||
while (*query)
|
while (*query)
|
||||||
{
|
{
|
||||||
int mblen = PQmblen(query, pset.encoding);
|
int mblen = PQmblenBounded(query, pset.encoding);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note: we assume the encoding is a superset of ASCII, so that for
|
* Note: we assume the encoding is a superset of ASCII, so that for
|
||||||
@ -1879,7 +1881,7 @@ skip_white_space(const char *query)
|
|||||||
query++;
|
query++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
query += PQmblen(query, pset.encoding);
|
query += PQmblenBounded(query, pset.encoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (cnestlevel > 0)
|
else if (cnestlevel > 0)
|
||||||
@ -1914,7 +1916,7 @@ command_no_begin(const char *query)
|
|||||||
*/
|
*/
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Transaction control commands. These should include every keyword that
|
* Transaction control commands. These should include every keyword that
|
||||||
@ -1945,7 +1947,7 @@ command_no_begin(const char *query)
|
|||||||
|
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
|
|
||||||
if (wordlen == 11 && pg_strncasecmp(query, "transaction", 11) == 0)
|
if (wordlen == 11 && pg_strncasecmp(query, "transaction", 11) == 0)
|
||||||
return true;
|
return true;
|
||||||
@ -1979,7 +1981,7 @@ command_no_begin(const char *query)
|
|||||||
|
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
|
|
||||||
if (wordlen == 8 && pg_strncasecmp(query, "database", 8) == 0)
|
if (wordlen == 8 && pg_strncasecmp(query, "database", 8) == 0)
|
||||||
return true;
|
return true;
|
||||||
@ -1995,7 +1997,7 @@ command_no_begin(const char *query)
|
|||||||
|
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wordlen == 5 && pg_strncasecmp(query, "index", 5) == 0)
|
if (wordlen == 5 && pg_strncasecmp(query, "index", 5) == 0)
|
||||||
@ -2006,7 +2008,7 @@ command_no_begin(const char *query)
|
|||||||
|
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
|
|
||||||
if (wordlen == 12 && pg_strncasecmp(query, "concurrently", 12) == 0)
|
if (wordlen == 12 && pg_strncasecmp(query, "concurrently", 12) == 0)
|
||||||
return true;
|
return true;
|
||||||
@ -2023,7 +2025,7 @@ command_no_begin(const char *query)
|
|||||||
|
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
|
|
||||||
/* ALTER SYSTEM isn't allowed in xacts */
|
/* ALTER SYSTEM isn't allowed in xacts */
|
||||||
if (wordlen == 6 && pg_strncasecmp(query, "system", 6) == 0)
|
if (wordlen == 6 && pg_strncasecmp(query, "system", 6) == 0)
|
||||||
@ -2046,7 +2048,7 @@ command_no_begin(const char *query)
|
|||||||
|
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
|
|
||||||
if (wordlen == 8 && pg_strncasecmp(query, "database", 8) == 0)
|
if (wordlen == 8 && pg_strncasecmp(query, "database", 8) == 0)
|
||||||
return true;
|
return true;
|
||||||
@ -2061,7 +2063,7 @@ command_no_begin(const char *query)
|
|||||||
query = skip_white_space(query);
|
query = skip_white_space(query);
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* REINDEX [ TABLE | INDEX ] CONCURRENTLY are not allowed in
|
* REINDEX [ TABLE | INDEX ] CONCURRENTLY are not allowed in
|
||||||
@ -2080,7 +2082,7 @@ command_no_begin(const char *query)
|
|||||||
|
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
|
|
||||||
if (wordlen == 12 && pg_strncasecmp(query, "concurrently", 12) == 0)
|
if (wordlen == 12 && pg_strncasecmp(query, "concurrently", 12) == 0)
|
||||||
return true;
|
return true;
|
||||||
@ -2100,7 +2102,7 @@ command_no_begin(const char *query)
|
|||||||
|
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
|
|
||||||
if (wordlen == 3 && pg_strncasecmp(query, "all", 3) == 0)
|
if (wordlen == 3 && pg_strncasecmp(query, "all", 3) == 0)
|
||||||
return true;
|
return true;
|
||||||
@ -2136,7 +2138,7 @@ is_select_command(const char *query)
|
|||||||
*/
|
*/
|
||||||
wordlen = 0;
|
wordlen = 0;
|
||||||
while (isalpha((unsigned char) query[wordlen]))
|
while (isalpha((unsigned char) query[wordlen]))
|
||||||
wordlen += PQmblen(&query[wordlen], pset.encoding);
|
wordlen += PQmblenBounded(&query[wordlen], pset.encoding);
|
||||||
|
|
||||||
if (wordlen == 6 && pg_strncasecmp(query, "select", 6) == 0)
|
if (wordlen == 6 && pg_strncasecmp(query, "select", 6) == 0)
|
||||||
return true;
|
return true;
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
%{
|
%{
|
||||||
#include "fe_utils/psqlscan_int.h"
|
#include "fe_utils/psqlscan_int.h"
|
||||||
|
|
||||||
|
#define PQmblenBounded(s, e) strnlen(s, PQmblen(s, e))
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We must have a typedef YYSTYPE for yylex's first argument, but this lexer
|
* We must have a typedef YYSTYPE for yylex's first argument, but this lexer
|
||||||
* doesn't presently make use of that argument, so just declare it as int.
|
* doesn't presently make use of that argument, so just declare it as int.
|
||||||
@ -753,7 +755,7 @@ dequote_downcase_identifier(char *str, bool downcase, int encoding)
|
|||||||
{
|
{
|
||||||
if (downcase && !inquotes)
|
if (downcase && !inquotes)
|
||||||
*cp = pg_tolower((unsigned char) *cp);
|
*cp = pg_tolower((unsigned char) *cp);
|
||||||
cp += PQmblen(cp, encoding);
|
cp += PQmblenBounded(cp, encoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "stringutils.h"
|
#include "stringutils.h"
|
||||||
|
|
||||||
|
#define PQmblenBounded(s, e) strnlen(s, PQmblen(s, e))
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Replacement for strtok() (a.k.a. poor man's flex)
|
* Replacement for strtok() (a.k.a. poor man's flex)
|
||||||
@ -143,7 +145,7 @@ strtokx(const char *s,
|
|||||||
/* okay, we have a quoted token, now scan for the closer */
|
/* okay, we have a quoted token, now scan for the closer */
|
||||||
char thisquote = *p++;
|
char thisquote = *p++;
|
||||||
|
|
||||||
for (; *p; p += PQmblen(p, encoding))
|
for (; *p; p += PQmblenBounded(p, encoding))
|
||||||
{
|
{
|
||||||
if (*p == escape && p[1] != '\0')
|
if (*p == escape && p[1] != '\0')
|
||||||
p++; /* process escaped anything */
|
p++; /* process escaped anything */
|
||||||
@ -262,7 +264,7 @@ strip_quotes(char *source, char quote, char escape, int encoding)
|
|||||||
else if (c == escape && src[1] != '\0')
|
else if (c == escape && src[1] != '\0')
|
||||||
src++; /* process escaped character */
|
src++; /* process escaped character */
|
||||||
|
|
||||||
i = PQmblen(src, encoding);
|
i = PQmblenBounded(src, encoding);
|
||||||
while (i--)
|
while (i--)
|
||||||
*dst++ = *src++;
|
*dst++ = *src++;
|
||||||
}
|
}
|
||||||
@ -324,7 +326,7 @@ quote_if_needed(const char *source, const char *entails_quote,
|
|||||||
else if (strchr(entails_quote, c))
|
else if (strchr(entails_quote, c))
|
||||||
need_quotes = true;
|
need_quotes = true;
|
||||||
|
|
||||||
i = PQmblen(src, encoding);
|
i = PQmblenBounded(src, encoding);
|
||||||
while (i--)
|
while (i--)
|
||||||
*dst++ = *src++;
|
*dst++ = *src++;
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,8 @@
|
|||||||
#define USE_FILENAME_QUOTING_FUNCTIONS 1
|
#define USE_FILENAME_QUOTING_FUNCTIONS 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define PQmblenBounded(s, e) strnlen(s, PQmblen(s, e))
|
||||||
|
|
||||||
/* word break characters */
|
/* word break characters */
|
||||||
#define WORD_BREAKS "\t\n@$><=;|&{() "
|
#define WORD_BREAKS "\t\n@$><=;|&{() "
|
||||||
|
|
||||||
@ -4140,7 +4142,7 @@ _complete_from_query(const char *simple_query,
|
|||||||
while (*pstr)
|
while (*pstr)
|
||||||
{
|
{
|
||||||
char_length++;
|
char_length++;
|
||||||
pstr += PQmblen(pstr, pset.encoding);
|
pstr += PQmblenBounded(pstr, pset.encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free any prior result */
|
/* Free any prior result */
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
#define ERRCODE_UNDEFINED_TABLE "42P01"
|
#define ERRCODE_UNDEFINED_TABLE "42P01"
|
||||||
|
|
||||||
|
#define PQmblenBounded(s, e) strnlen(s, PQmblen(s, e))
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Provide strictly harmonized handling of --help and --version
|
* Provide strictly harmonized handling of --help and --version
|
||||||
* options.
|
* options.
|
||||||
@ -368,7 +370,7 @@ splitTableColumnsSpec(const char *spec, int encoding,
|
|||||||
cp++;
|
cp++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
cp += PQmblen(cp, encoding);
|
cp += PQmblenBounded(cp, encoding);
|
||||||
}
|
}
|
||||||
*table = pnstrdup(spec, cp - spec);
|
*table = pnstrdup(spec, cp - spec);
|
||||||
*columns = cp;
|
*columns = cp;
|
||||||
|
@ -738,7 +738,7 @@ json_lex_string(JsonLexContext *lex)
|
|||||||
ch = (ch * 16) + (*s - 'A') + 10;
|
ch = (ch * 16) + (*s - 'A') + 10;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lex->token_terminator = s + pg_encoding_mblen(lex->input_encoding, s);
|
lex->token_terminator = s + pg_encoding_mblen_bounded(lex->input_encoding, s);
|
||||||
return JSON_UNICODE_ESCAPE_FORMAT;
|
return JSON_UNICODE_ESCAPE_FORMAT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -844,7 +844,7 @@ json_lex_string(JsonLexContext *lex)
|
|||||||
default:
|
default:
|
||||||
/* Not a valid string escape, so signal error. */
|
/* Not a valid string escape, so signal error. */
|
||||||
lex->token_start = s;
|
lex->token_start = s;
|
||||||
lex->token_terminator = s + pg_encoding_mblen(lex->input_encoding, s);
|
lex->token_terminator = s + pg_encoding_mblen_bounded(lex->input_encoding, s);
|
||||||
return JSON_ESCAPING_INVALID;
|
return JSON_ESCAPING_INVALID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -858,7 +858,7 @@ json_lex_string(JsonLexContext *lex)
|
|||||||
* shown it's not a performance win.
|
* shown it's not a performance win.
|
||||||
*/
|
*/
|
||||||
lex->token_start = s;
|
lex->token_start = s;
|
||||||
lex->token_terminator = s + pg_encoding_mblen(lex->input_encoding, s);
|
lex->token_terminator = s + pg_encoding_mblen_bounded(lex->input_encoding, s);
|
||||||
return JSON_ESCAPING_INVALID;
|
return JSON_ESCAPING_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1549,6 +1549,11 @@ const pg_wchar_tbl pg_wchar_table[] = {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the byte length of a multibyte character.
|
* Returns the byte length of a multibyte character.
|
||||||
|
*
|
||||||
|
* Caution: when dealing with text that is not certainly valid in the
|
||||||
|
* specified encoding, the result may exceed the actual remaining
|
||||||
|
* string length. Callers that are not prepared to deal with that
|
||||||
|
* should use pg_encoding_mblen_bounded() instead.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
pg_encoding_mblen(int encoding, const char *mbstr)
|
pg_encoding_mblen(int encoding, const char *mbstr)
|
||||||
@ -1558,6 +1563,16 @@ pg_encoding_mblen(int encoding, const char *mbstr)
|
|||||||
pg_wchar_table[PG_SQL_ASCII].mblen((const unsigned char *) mbstr));
|
pg_wchar_table[PG_SQL_ASCII].mblen((const unsigned char *) mbstr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the byte length of a multibyte character; but not more than
|
||||||
|
* the distance to end of string.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pg_encoding_mblen_bounded(int encoding, const char *mbstr)
|
||||||
|
{
|
||||||
|
return strnlen(mbstr, pg_encoding_mblen(encoding, mbstr));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the display length of a multibyte character.
|
* Returns the display length of a multibyte character.
|
||||||
*/
|
*/
|
||||||
|
@ -3652,6 +3652,9 @@ strlen_max_width(unsigned char *str, int *target_width, int encoding)
|
|||||||
curr_width += char_width;
|
curr_width += char_width;
|
||||||
|
|
||||||
str += PQmblen((char *) str, encoding);
|
str += PQmblen((char *) str, encoding);
|
||||||
|
|
||||||
|
if (str > end) /* Don't overrun invalid string */
|
||||||
|
str = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
*target_width = curr_width;
|
*target_width = curr_width;
|
||||||
|
@ -553,6 +553,7 @@ extern int pg_valid_server_encoding_id(int encoding);
|
|||||||
* earlier in this file are also available from libpgcommon.
|
* earlier in this file are also available from libpgcommon.
|
||||||
*/
|
*/
|
||||||
extern int pg_encoding_mblen(int encoding, const char *mbstr);
|
extern int pg_encoding_mblen(int encoding, const char *mbstr);
|
||||||
|
extern int pg_encoding_mblen_bounded(int encoding, const char *mbstr);
|
||||||
extern int pg_encoding_dsplen(int encoding, const char *mbstr);
|
extern int pg_encoding_dsplen(int encoding, const char *mbstr);
|
||||||
extern int pg_encoding_verifymb(int encoding, const char *mbstr, int len);
|
extern int pg_encoding_verifymb(int encoding, const char *mbstr, int len);
|
||||||
extern int pg_encoding_max_length(int encoding);
|
extern int pg_encoding_max_length(int encoding);
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "libpq-fe.h"
|
#include "libpq-fe.h"
|
||||||
#include "libpq-int.h"
|
#include "libpq-int.h"
|
||||||
|
|
||||||
|
#define PQmblenBounded(s, e) strnlen(s, PQmblen(s, e))
|
||||||
|
|
||||||
static void do_field(const PQprintOpt *po, const PGresult *res,
|
static void do_field(const PQprintOpt *po, const PGresult *res,
|
||||||
const int i, const int j, const int fs_len,
|
const int i, const int j, const int fs_len,
|
||||||
@ -365,7 +366,7 @@ do_field(const PQprintOpt *po, const PGresult *res,
|
|||||||
/* Detect whether field contains non-numeric data */
|
/* Detect whether field contains non-numeric data */
|
||||||
char ch = '0';
|
char ch = '0';
|
||||||
|
|
||||||
for (p = pval; *p; p += PQmblen(p, res->client_encoding))
|
for (p = pval; *p; p += PQmblenBounded(p, res->client_encoding))
|
||||||
{
|
{
|
||||||
ch = *p;
|
ch = *p;
|
||||||
if (!((ch >= '0' && ch <= '9') ||
|
if (!((ch >= '0' && ch <= '9') ||
|
||||||
|
@ -39,6 +39,8 @@
|
|||||||
((id) == 'T' || (id) == 'D' || (id) == 'd' || (id) == 'V' || \
|
((id) == 'T' || (id) == 'D' || (id) == 'd' || (id) == 'V' || \
|
||||||
(id) == 'E' || (id) == 'N' || (id) == 'A')
|
(id) == 'E' || (id) == 'N' || (id) == 'A')
|
||||||
|
|
||||||
|
#define PQmblenBounded(s, e) strnlen(s, PQmblen(s, e))
|
||||||
|
|
||||||
|
|
||||||
static void handleSyncLoss(PGconn *conn, char id, int msgLength);
|
static void handleSyncLoss(PGconn *conn, char id, int msgLength);
|
||||||
static int getRowDescriptions(PGconn *conn, int msgLength);
|
static int getRowDescriptions(PGconn *conn, int msgLength);
|
||||||
@ -1241,7 +1243,7 @@ reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding)
|
|||||||
if (w <= 0)
|
if (w <= 0)
|
||||||
w = 1;
|
w = 1;
|
||||||
scroffset += w;
|
scroffset += w;
|
||||||
qoffset += pg_encoding_mblen(encoding, &wquery[qoffset]);
|
qoffset += PQmblenBounded(&wquery[qoffset], encoding);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1309,7 +1311,7 @@ reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding)
|
|||||||
* width.
|
* width.
|
||||||
*/
|
*/
|
||||||
scroffset = 0;
|
scroffset = 0;
|
||||||
for (; i < msg->len; i += pg_encoding_mblen(encoding, &msg->data[i]))
|
for (; i < msg->len; i += PQmblenBounded(&msg->data[i], encoding))
|
||||||
{
|
{
|
||||||
int w = pg_encoding_dsplen(encoding, &msg->data[i]);
|
int w = pg_encoding_dsplen(encoding, &msg->data[i]);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user