Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 6680d19

Browse files
committed
Further fixes for quoted-list GUC values in pg_dump and ruleutils.c.
Commits 7428699 et al turn out to be a couple bricks shy of a load. We were dumping the stored values of GUC_LIST_QUOTE variables as they appear in proconfig or setconfig catalog columns. However, although that quoting rule looks a lot like SQL-identifier double quotes, there are two critical differences: empty strings ("") are legal, and depending on which variable you're considering, values longer than NAMEDATALEN might be valid too. So the current technique fails altogether on empty-string list entries (as reported by Steven Winfield in bug #15248) and it also risks truncating file pathnames during dump/reload of GUC values that are lists of pathnames. To fix, split the stored value without any downcasing or truncation, and then emit each element as a SQL string literal. This is a tad annoying, because we now have three copies of the comma-separated-string splitting logic in varlena.c as well as a fourth one in dumputils.c. (Not to mention the randomly-different-from-those splitting logic in libpq...) I looked at unifying these, but it would be rather a mess unless we're willing to tweak the API definitions of SplitIdentifierString, SplitDirectoriesString, or both. That might be worth doing in future; but it seems pretty unsafe for a back-patched bug fix, so for now accept the duplication. Back-patch to all supported branches, as the previous fix was. Discussion: https://postgr.es/m/7585.1529435872@sss.pgh.pa.us
1 parent 8c7f64b commit 6680d19

File tree

9 files changed

+326
-30
lines changed

9 files changed

+326
-30
lines changed

src/backend/utils/adt/ruleutils.c

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,14 +2200,39 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
22002200
/*
22012201
* Variables that are marked GUC_LIST_QUOTE were already fully
22022202
* quoted by flatten_set_variable_args() before they were put
2203-
* into the proconfig array; we mustn't re-quote them or we'll
2204-
* make a mess. Variables that are not so marked should just
2205-
* be emitted as simple string literals. If the variable is
2206-
* not known to guc.c, we'll do the latter; this makes it
2207-
* unsafe to use GUC_LIST_QUOTE for extension variables.
2203+
* into the proconfig array. However, because the quoting
2204+
* rules used there aren't exactly like SQL's, we have to
2205+
* break the list value apart and then quote the elements as
2206+
* string literals. (The elements may be double-quoted as-is,
2207+
* but we can't just feed them to the SQL parser; it would do
2208+
* the wrong thing with elements that are zero-length or
2209+
* longer than NAMEDATALEN.)
2210+
*
2211+
* Variables that are not so marked should just be emitted as
2212+
* simple string literals. If the variable is not known to
2213+
* guc.c, we'll do that; this makes it unsafe to use
2214+
* GUC_LIST_QUOTE for extension variables.
22082215
*/
22092216
if (GetConfigOptionFlags(configitem, true) & GUC_LIST_QUOTE)
2210-
appendStringInfoString(&buf, pos);
2217+
{
2218+
List *namelist;
2219+
ListCell *lc;
2220+
2221+
/* Parse string into list of identifiers */
2222+
if (!SplitGUCList(pos, ',', &namelist))
2223+
{
2224+
/* this shouldn't fail really */
2225+
elog(ERROR, "invalid list syntax in proconfig item");
2226+
}
2227+
foreach(lc, namelist)
2228+
{
2229+
char *curname = (char *) lfirst(lc);
2230+
2231+
simple_quote_literal(&buf, curname);
2232+
if (lnext(lc))
2233+
appendStringInfoString(&buf, ", ");
2234+
}
2235+
}
22112236
else
22122237
simple_quote_literal(&buf, pos);
22132238
appendStringInfoChar(&buf, '\n');

src/backend/utils/adt/varlena.c

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3339,6 +3339,118 @@ SplitDirectoriesString(char *rawstring, char separator,
33393339
}
33403340

33413341

3342+
/*
3343+
* SplitGUCList --- parse a string containing identifiers or file names
3344+
*
3345+
* This is used to split the value of a GUC_LIST_QUOTE GUC variable, without
3346+
* presuming whether the elements will be taken as identifiers or file names.
3347+
* We assume the input has already been through flatten_set_variable_args(),
3348+
* so that we need never downcase (if appropriate, that was done already).
3349+
* Nor do we ever truncate, since we don't know the correct max length.
3350+
* We disallow embedded whitespace for simplicity (it shouldn't matter,
3351+
* because any embedded whitespace should have led to double-quoting).
3352+
* Otherwise the API is identical to SplitIdentifierString.
3353+
*
3354+
* XXX it's annoying to have so many copies of this string-splitting logic.
3355+
* However, it's not clear that having one function with a bunch of option
3356+
* flags would be much better.
3357+
*
3358+
* XXX there is a version of this function in src/bin/pg_dump/dumputils.c.
3359+
* Be sure to update that if you have to change this.
3360+
*
3361+
* Inputs:
3362+
* rawstring: the input string; must be overwritable! On return, it's
3363+
* been modified to contain the separated identifiers.
3364+
* separator: the separator punctuation expected between identifiers
3365+
* (typically '.' or ','). Whitespace may also appear around
3366+
* identifiers.
3367+
* Outputs:
3368+
* namelist: filled with a palloc'd list of pointers to identifiers within
3369+
* rawstring. Caller should list_free() this even on error return.
3370+
*
3371+
* Returns true if okay, false if there is a syntax error in the string.
3372+
*/
3373+
bool
3374+
SplitGUCList(char *rawstring, char separator,
3375+
List **namelist)
3376+
{
3377+
char *nextp = rawstring;
3378+
bool done = false;
3379+
3380+
*namelist = NIL;
3381+
3382+
while (scanner_isspace(*nextp))
3383+
nextp++; /* skip leading whitespace */
3384+
3385+
if (*nextp == '\0')
3386+
return true; /* allow empty string */
3387+
3388+
/* At the top of the loop, we are at start of a new identifier. */
3389+
do
3390+
{
3391+
char *curname;
3392+
char *endp;
3393+
3394+
if (*nextp == '"')
3395+
{
3396+
/* Quoted name --- collapse quote-quote pairs */
3397+
curname = nextp + 1;
3398+
for (;;)
3399+
{
3400+
endp = strchr(nextp + 1, '"');
3401+
if (endp == NULL)
3402+
return false; /* mismatched quotes */
3403+
if (endp[1] != '"')
3404+
break; /* found end of quoted name */
3405+
/* Collapse adjacent quotes into one quote, and look again */
3406+
memmove(endp, endp + 1, strlen(endp));
3407+
nextp = endp;
3408+
}
3409+
/* endp now points at the terminating quote */
3410+
nextp = endp + 1;
3411+
}
3412+
else
3413+
{
3414+
/* Unquoted name --- extends to separator or whitespace */
3415+
curname = nextp;
3416+
while (*nextp && *nextp != separator &&
3417+
!scanner_isspace(*nextp))
3418+
nextp++;
3419+
endp = nextp;
3420+
if (curname == nextp)
3421+
return false; /* empty unquoted name not allowed */
3422+
}
3423+
3424+
while (scanner_isspace(*nextp))
3425+
nextp++; /* skip trailing whitespace */
3426+
3427+
if (*nextp == separator)
3428+
{
3429+
nextp++;
3430+
while (scanner_isspace(*nextp))
3431+
nextp++; /* skip leading whitespace for next */
3432+
/* we expect another name, so done remains false */
3433+
}
3434+
else if (*nextp == '\0')
3435+
done = true;
3436+
else
3437+
return false; /* invalid syntax */
3438+
3439+
/* Now safe to overwrite separator with a null */
3440+
*endp = '\0';
3441+
3442+
/*
3443+
* Finished isolating current name --- add it to list
3444+
*/
3445+
*namelist = lappend(*namelist, curname);
3446+
3447+
/* Loop back if we didn't reach end of string */
3448+
} while (!done);
3449+
3450+
return true;
3451+
}
3452+
3453+
33423454
/*****************************************************************************
33433455
* Comparison Functions used for bytea
33443456
*

src/bin/pg_dump/dumputils.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
*/
1515
#include "postgres_fe.h"
1616

17+
#include <ctype.h>
18+
1719
#include "dumputils.h"
1820
#include "fe_utils/string_utils.h"
1921

@@ -876,3 +878,112 @@ variable_is_guc_list_quote(const char *name)
876878
else
877879
return false;
878880
}
881+
882+
/*
883+
* SplitGUCList --- parse a string containing identifiers or file names
884+
*
885+
* This is used to split the value of a GUC_LIST_QUOTE GUC variable, without
886+
* presuming whether the elements will be taken as identifiers or file names.
887+
* See comparable code in src/backend/utils/adt/varlena.c.
888+
*
889+
* Inputs:
890+
* rawstring: the input string; must be overwritable! On return, it's
891+
* been modified to contain the separated identifiers.
892+
* separator: the separator punctuation expected between identifiers
893+
* (typically '.' or ','). Whitespace may also appear around
894+
* identifiers.
895+
* Outputs:
896+
* namelist: receives a malloc'd, null-terminated array of pointers to
897+
* identifiers within rawstring. Caller should free this
898+
* even on error return.
899+
*
900+
* Returns true if okay, false if there is a syntax error in the string.
901+
*/
902+
bool
903+
SplitGUCList(char *rawstring, char separator,
904+
char ***namelist)
905+
{
906+
char *nextp = rawstring;
907+
bool done = false;
908+
char **nextptr;
909+
910+
/*
911+
* Since we disallow empty identifiers, this is a conservative
912+
* overestimate of the number of pointers we could need. Allow one for
913+
* list terminator.
914+
*/
915+
*namelist = nextptr = (char **)
916+
pg_malloc((strlen(rawstring) / 2 + 2) * sizeof(char *));
917+
*nextptr = NULL;
918+
919+
while (isspace((unsigned char) *nextp))
920+
nextp++; /* skip leading whitespace */
921+
922+
if (*nextp == '\0')
923+
return true; /* allow empty string */
924+
925+
/* At the top of the loop, we are at start of a new identifier. */
926+
do
927+
{
928+
char *curname;
929+
char *endp;
930+
931+
if (*nextp == '"')
932+
{
933+
/* Quoted name --- collapse quote-quote pairs */
934+
curname = nextp + 1;
935+
for (;;)
936+
{
937+
endp = strchr(nextp + 1, '"');
938+
if (endp == NULL)
939+
return false; /* mismatched quotes */
940+
if (endp[1] != '"')
941+
break; /* found end of quoted name */
942+
/* Collapse adjacent quotes into one quote, and look again */
943+
memmove(endp, endp + 1, strlen(endp));
944+
nextp = endp;
945+
}
946+
/* endp now points at the terminating quote */
947+
nextp = endp + 1;
948+
}
949+
else
950+
{
951+
/* Unquoted name --- extends to separator or whitespace */
952+
curname = nextp;
953+
while (*nextp && *nextp != separator &&
954+
!isspace((unsigned char) *nextp))
955+
nextp++;
956+
endp = nextp;
957+
if (curname == nextp)
958+
return false; /* empty unquoted name not allowed */
959+
}
960+
961+
while (isspace((unsigned char) *nextp))
962+
nextp++; /* skip trailing whitespace */
963+
964+
if (*nextp == separator)
965+
{
966+
nextp++;
967+
while (isspace((unsigned char) *nextp))
968+
nextp++; /* skip leading whitespace for next */
969+
/* we expect another name, so done remains false */
970+
}
971+
else if (*nextp == '\0')
972+
done = true;
973+
else
974+
return false; /* invalid syntax */
975+
976+
/* Now safe to overwrite separator with a null */
977+
*endp = '\0';
978+
979+
/*
980+
* Finished isolating current name --- add it to output array
981+
*/
982+
*nextptr++ = curname;
983+
984+
/* Loop back if we didn't reach end of string */
985+
} while (!done);
986+
987+
*nextptr = NULL;
988+
return true;
989+
}

src/bin/pg_dump/dumputils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,7 @@ extern void buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
5858

5959
extern bool variable_is_guc_list_quote(const char *name);
6060

61+
extern bool SplitGUCList(char *rawstring, char separator,
62+
char ***namelist);
63+
6164
#endif /* DUMPUTILS_H */

src/bin/pg_dump/pg_dump.c

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11801,14 +11801,36 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
1180111801
/*
1180211802
* Variables that are marked GUC_LIST_QUOTE were already fully quoted
1180311803
* by flatten_set_variable_args() before they were put into the
11804-
* proconfig array; we mustn't re-quote them or we'll make a mess.
11804+
* proconfig array. However, because the quoting rules used there
11805+
* aren't exactly like SQL's, we have to break the list value apart
11806+
* and then quote the elements as string literals. (The elements may
11807+
* be double-quoted as-is, but we can't just feed them to the SQL
11808+
* parser; it would do the wrong thing with elements that are
11809+
* zero-length or longer than NAMEDATALEN.)
11810+
*
1180511811
* Variables that are not so marked should just be emitted as simple
1180611812
* string literals. If the variable is not known to
11807-
* variable_is_guc_list_quote(), we'll do the latter; this makes it
11808-
* unsafe to use GUC_LIST_QUOTE for extension variables.
11813+
* variable_is_guc_list_quote(), we'll do that; this makes it unsafe
11814+
* to use GUC_LIST_QUOTE for extension variables.
1180911815
*/
1181011816
if (variable_is_guc_list_quote(configitem))
11811-
appendPQExpBufferStr(q, pos);
11817+
{
11818+
char **namelist;
11819+
char **nameptr;
11820+
11821+
/* Parse string into list of identifiers */
11822+
/* this shouldn't fail really */
11823+
if (SplitGUCList(pos, ',', &namelist))
11824+
{
11825+
for (nameptr = namelist; *nameptr; nameptr++)
11826+
{
11827+
if (nameptr != namelist)
11828+
appendPQExpBufferStr(q, ", ");
11829+
appendStringLiteralAH(q, *nameptr, fout);
11830+
}
11831+
}
11832+
pg_free(namelist);
11833+
}
1181211834
else
1181311835
appendStringLiteralAH(q, pos, fout);
1181411836
}

src/bin/pg_dump/pg_dumpall.c

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,14 +1721,35 @@ makeAlterConfigCommand(PGconn *conn, const char *arrayitem,
17211721
/*
17221722
* Variables that are marked GUC_LIST_QUOTE were already fully quoted by
17231723
* flatten_set_variable_args() before they were put into the setconfig
1724-
* array; we mustn't re-quote them or we'll make a mess. Variables that
1725-
* are not so marked should just be emitted as simple string literals. If
1726-
* the variable is not known to variable_is_guc_list_quote(), we'll do the
1727-
* latter; this makes it unsafe to use GUC_LIST_QUOTE for extension
1728-
* variables.
1724+
* array. However, because the quoting rules used there aren't exactly
1725+
* like SQL's, we have to break the list value apart and then quote the
1726+
* elements as string literals. (The elements may be double-quoted as-is,
1727+
* but we can't just feed them to the SQL parser; it would do the wrong
1728+
* thing with elements that are zero-length or longer than NAMEDATALEN.)
1729+
*
1730+
* Variables that are not so marked should just be emitted as simple
1731+
* string literals. If the variable is not known to
1732+
* variable_is_guc_list_quote(), we'll do that; this makes it unsafe to
1733+
* use GUC_LIST_QUOTE for extension variables.
17291734
*/
17301735
if (variable_is_guc_list_quote(mine))
1731-
appendPQExpBufferStr(buf, pos + 1);
1736+
{
1737+
char **namelist;
1738+
char **nameptr;
1739+
1740+
/* Parse string into list of identifiers */
1741+
/* this shouldn't fail really */
1742+
if (SplitGUCList(pos + 1, ',', &namelist))
1743+
{
1744+
for (nameptr = namelist; *nameptr; nameptr++)
1745+
{
1746+
if (nameptr != namelist)
1747+
appendPQExpBufferStr(buf, ", ");
1748+
appendStringLiteralConn(buf, *nameptr, conn);
1749+
}
1750+
}
1751+
pg_free(namelist);
1752+
}
17321753
else
17331754
appendStringLiteralConn(buf, pos + 1, conn);
17341755
appendPQExpBufferStr(buf, ";\n");

src/include/utils/builtins.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,8 @@ extern bool SplitIdentifierString(char *rawstring, char separator,
860860
List **namelist);
861861
extern bool SplitDirectoriesString(char *rawstring, char separator,
862862
List **namelist);
863+
extern bool SplitGUCList(char *rawstring, char separator,
864+
List **namelist);
863865
extern Datum replace_text(PG_FUNCTION_ARGS);
864866
extern text *replace_text_regexp(text *src_text, void *regexp,
865867
text *replace_text, bool glob);

0 commit comments

Comments
 (0)