Skip to content

Commit 31b29b1

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 2c4d0f3 commit 31b29b1

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
@@ -2571,14 +2571,39 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
25712571
/*
25722572
* Variables that are marked GUC_LIST_QUOTE were already fully
25732573
* quoted by flatten_set_variable_args() before they were put
2574-
* into the proconfig array; we mustn't re-quote them or we'll
2575-
* make a mess. Variables that are not so marked should just
2576-
* be emitted as simple string literals. If the variable is
2577-
* not known to guc.c, we'll do the latter; this makes it
2578-
* unsafe to use GUC_LIST_QUOTE for extension variables.
2574+
* into the proconfig array. However, because the quoting
2575+
* rules used there aren't exactly like SQL's, we have to
2576+
* break the list value apart and then quote the elements as
2577+
* string literals. (The elements may be double-quoted as-is,
2578+
* but we can't just feed them to the SQL parser; it would do
2579+
* the wrong thing with elements that are zero-length or
2580+
* longer than NAMEDATALEN.)
2581+
*
2582+
* Variables that are not so marked should just be emitted as
2583+
* simple string literals. If the variable is not known to
2584+
* guc.c, we'll do that; this makes it unsafe to use
2585+
* GUC_LIST_QUOTE for extension variables.
25792586
*/
25802587
if (GetConfigOptionFlags(configitem, true) & GUC_LIST_QUOTE)
2581-
appendStringInfoString(&buf, pos);
2588+
{
2589+
List *namelist;
2590+
ListCell *lc;
2591+
2592+
/* Parse string into list of identifiers */
2593+
if (!SplitGUCList(pos, ',', &namelist))
2594+
{
2595+
/* this shouldn't fail really */
2596+
elog(ERROR, "invalid list syntax in proconfig item");
2597+
}
2598+
foreach(lc, namelist)
2599+
{
2600+
char *curname = (char *) lfirst(lc);
2601+
2602+
simple_quote_literal(&buf, curname);
2603+
if (lnext(lc))
2604+
appendStringInfoString(&buf, ", ");
2605+
}
2606+
}
25822607
else
25832608
simple_quote_literal(&buf, pos);
25842609
appendStringInfoChar(&buf, '\n');

src/backend/utils/adt/varlena.c

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

34793479

3480+
/*
3481+
* SplitGUCList --- parse a string containing identifiers or file names
3482+
*
3483+
* This is used to split the value of a GUC_LIST_QUOTE GUC variable, without
3484+
* presuming whether the elements will be taken as identifiers or file names.
3485+
* We assume the input has already been through flatten_set_variable_args(),
3486+
* so that we need never downcase (if appropriate, that was done already).
3487+
* Nor do we ever truncate, since we don't know the correct max length.
3488+
* We disallow embedded whitespace for simplicity (it shouldn't matter,
3489+
* because any embedded whitespace should have led to double-quoting).
3490+
* Otherwise the API is identical to SplitIdentifierString.
3491+
*
3492+
* XXX it's annoying to have so many copies of this string-splitting logic.
3493+
* However, it's not clear that having one function with a bunch of option
3494+
* flags would be much better.
3495+
*
3496+
* XXX there is a version of this function in src/bin/pg_dump/dumputils.c.
3497+
* Be sure to update that if you have to change this.
3498+
*
3499+
* Inputs:
3500+
* rawstring: the input string; must be overwritable! On return, it's
3501+
* been modified to contain the separated identifiers.
3502+
* separator: the separator punctuation expected between identifiers
3503+
* (typically '.' or ','). Whitespace may also appear around
3504+
* identifiers.
3505+
* Outputs:
3506+
* namelist: filled with a palloc'd list of pointers to identifiers within
3507+
* rawstring. Caller should list_free() this even on error return.
3508+
*
3509+
* Returns true if okay, false if there is a syntax error in the string.
3510+
*/
3511+
bool
3512+
SplitGUCList(char *rawstring, char separator,
3513+
List **namelist)
3514+
{
3515+
char *nextp = rawstring;
3516+
bool done = false;
3517+
3518+
*namelist = NIL;
3519+
3520+
while (scanner_isspace(*nextp))
3521+
nextp++; /* skip leading whitespace */
3522+
3523+
if (*nextp == '\0')
3524+
return true; /* allow empty string */
3525+
3526+
/* At the top of the loop, we are at start of a new identifier. */
3527+
do
3528+
{
3529+
char *curname;
3530+
char *endp;
3531+
3532+
if (*nextp == '"')
3533+
{
3534+
/* Quoted name --- collapse quote-quote pairs */
3535+
curname = nextp + 1;
3536+
for (;;)
3537+
{
3538+
endp = strchr(nextp + 1, '"');
3539+
if (endp == NULL)
3540+
return false; /* mismatched quotes */
3541+
if (endp[1] != '"')
3542+
break; /* found end of quoted name */
3543+
/* Collapse adjacent quotes into one quote, and look again */
3544+
memmove(endp, endp + 1, strlen(endp));
3545+
nextp = endp;
3546+
}
3547+
/* endp now points at the terminating quote */
3548+
nextp = endp + 1;
3549+
}
3550+
else
3551+
{
3552+
/* Unquoted name --- extends to separator or whitespace */
3553+
curname = nextp;
3554+
while (*nextp && *nextp != separator &&
3555+
!scanner_isspace(*nextp))
3556+
nextp++;
3557+
endp = nextp;
3558+
if (curname == nextp)
3559+
return false; /* empty unquoted name not allowed */
3560+
}
3561+
3562+
while (scanner_isspace(*nextp))
3563+
nextp++; /* skip trailing whitespace */
3564+
3565+
if (*nextp == separator)
3566+
{
3567+
nextp++;
3568+
while (scanner_isspace(*nextp))
3569+
nextp++; /* skip leading whitespace for next */
3570+
/* we expect another name, so done remains false */
3571+
}
3572+
else if (*nextp == '\0')
3573+
done = true;
3574+
else
3575+
return false; /* invalid syntax */
3576+
3577+
/* Now safe to overwrite separator with a null */
3578+
*endp = '\0';
3579+
3580+
/*
3581+
* Finished isolating current name --- add it to list
3582+
*/
3583+
*namelist = lappend(*namelist, curname);
3584+
3585+
/* Loop back if we didn't reach end of string */
3586+
} while (!done);
3587+
3588+
return true;
3589+
}
3590+
3591+
34803592
/*****************************************************************************
34813593
* Comparison Functions used for bytea
34823594
*

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

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

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
@@ -11482,14 +11482,36 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
1148211482
/*
1148311483
* Variables that are marked GUC_LIST_QUOTE were already fully quoted
1148411484
* by flatten_set_variable_args() before they were put into the
11485-
* proconfig array; we mustn't re-quote them or we'll make a mess.
11485+
* proconfig array. However, because the quoting rules used there
11486+
* aren't exactly like SQL's, we have to break the list value apart
11487+
* and then quote the elements as string literals. (The elements may
11488+
* be double-quoted as-is, but we can't just feed them to the SQL
11489+
* parser; it would do the wrong thing with elements that are
11490+
* zero-length or longer than NAMEDATALEN.)
11491+
*
1148611492
* Variables that are not so marked should just be emitted as simple
1148711493
* string literals. If the variable is not known to
11488-
* variable_is_guc_list_quote(), we'll do the latter; this makes it
11489-
* unsafe to use GUC_LIST_QUOTE for extension variables.
11494+
* variable_is_guc_list_quote(), we'll do that; this makes it unsafe
11495+
* to use GUC_LIST_QUOTE for extension variables.
1149011496
*/
1149111497
if (variable_is_guc_list_quote(configitem))
11492-
appendPQExpBufferStr(q, pos);
11498+
{
11499+
char **namelist;
11500+
char **nameptr;
11501+
11502+
/* Parse string into list of identifiers */
11503+
/* this shouldn't fail really */
11504+
if (SplitGUCList(pos, ',', &namelist))
11505+
{
11506+
for (nameptr = namelist; *nameptr; nameptr++)
11507+
{
11508+
if (nameptr != namelist)
11509+
appendPQExpBufferStr(q, ", ");
11510+
appendStringLiteralAH(q, *nameptr, fout);
11511+
}
11512+
}
11513+
pg_free(namelist);
11514+
}
1149311515
else
1149411516
appendStringLiteralAH(q, pos, fout);
1149511517
}

src/bin/pg_dump/pg_dumpall.c

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,14 +1708,35 @@ makeAlterConfigCommand(PGconn *conn, const char *arrayitem,
17081708
/*
17091709
* Variables that are marked GUC_LIST_QUOTE were already fully quoted by
17101710
* flatten_set_variable_args() before they were put into the setconfig
1711-
* array; we mustn't re-quote them or we'll make a mess. Variables that
1712-
* are not so marked should just be emitted as simple string literals. If
1713-
* the variable is not known to variable_is_guc_list_quote(), we'll do the
1714-
* latter; this makes it unsafe to use GUC_LIST_QUOTE for extension
1715-
* variables.
1711+
* array. However, because the quoting rules used there aren't exactly
1712+
* like SQL's, we have to break the list value apart and then quote the
1713+
* elements as string literals. (The elements may be double-quoted as-is,
1714+
* but we can't just feed them to the SQL parser; it would do the wrong
1715+
* thing with elements that are zero-length or longer than NAMEDATALEN.)
1716+
*
1717+
* Variables that are not so marked should just be emitted as simple
1718+
* string literals. If the variable is not known to
1719+
* variable_is_guc_list_quote(), we'll do that; this makes it unsafe to
1720+
* use GUC_LIST_QUOTE for extension variables.
17161721
*/
17171722
if (variable_is_guc_list_quote(mine))
1718-
appendPQExpBufferStr(buf, pos + 1);
1723+
{
1724+
char **namelist;
1725+
char **nameptr;
1726+
1727+
/* Parse string into list of identifiers */
1728+
/* this shouldn't fail really */
1729+
if (SplitGUCList(pos + 1, ',', &namelist))
1730+
{
1731+
for (nameptr = namelist; *nameptr; nameptr++)
1732+
{
1733+
if (nameptr != namelist)
1734+
appendPQExpBufferStr(buf, ", ");
1735+
appendStringLiteralConn(buf, *nameptr, conn);
1736+
}
1737+
}
1738+
pg_free(namelist);
1739+
}
17191740
else
17201741
appendStringLiteralConn(buf, pos + 1, conn);
17211742
appendPQExpBufferStr(buf, ";\n");

src/include/utils/varlena.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ extern bool SplitIdentifierString(char *rawstring, char separator,
3131
List **namelist);
3232
extern bool SplitDirectoriesString(char *rawstring, char separator,
3333
List **namelist);
34+
extern bool SplitGUCList(char *rawstring, char separator,
35+
List **namelist);
3436
extern text *replace_text_regexp(text *src_text, void *regexp,
3537
text *replace_text, bool glob);
3638

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy