Skip to content

Commit 30b5ede

Browse files
committed
Fix escaping in generated recovery.conf file.
In the primary_conninfo line that "pg_basebackup -R" generates, single quotes in parameter values need to be escaped into \\'; the libpq parser requires the quotes to be escaped into \', and recovery.conf parser requires the \ to be escaped into \\. Also, don't quote parameter values unnecessarily, to make the connection string prettier. Most options in a libpq connection string don't need quoting. Reported by Hari Babu, closer analysis by Zoltan Boszormenyi, although I didn't use his patch.
1 parent 2af0971 commit 30b5ede

File tree

1 file changed

+89
-12
lines changed

1 file changed

+89
-12
lines changed

src/bin/pg_basebackup/pg_basebackup.c

+89-12
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,71 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
11071107
}
11081108

11091109
/*
1110-
* Escape single quotes used in connection parameters
1110+
* Escape a parameter value so that it can be used as part of a libpq
1111+
* connection string, e.g. in:
1112+
*
1113+
* application_name=<value>
1114+
*
1115+
* The returned string is malloc'd. Return NULL on out-of-memory.
1116+
*/
1117+
static char *
1118+
escapeConnectionParameter(const char *src)
1119+
{
1120+
bool need_quotes = false;
1121+
bool need_escaping = false;
1122+
const char *p;
1123+
char *dstbuf;
1124+
char *dst;
1125+
1126+
/*
1127+
* First check if quoting is needed. Any quote (') or backslash (\)
1128+
* characters need to be escaped. Parameters are separated by whitespace,
1129+
* so any string containing whitespace characters need to be quoted. An
1130+
* empty string is represented by ''.
1131+
*/
1132+
if (strchr(src, '\'') != NULL || strchr(src, '\\') != NULL)
1133+
need_escaping = true;
1134+
1135+
for (p = src; *p; p++)
1136+
{
1137+
if (isspace(*p))
1138+
{
1139+
need_quotes = true;
1140+
break;
1141+
}
1142+
}
1143+
1144+
if (*src == '\0')
1145+
return pg_strdup("''");
1146+
1147+
if (!need_quotes && !need_escaping)
1148+
return pg_strdup(src); /* no quoting or escaping needed */
1149+
1150+
/*
1151+
* Allocate a buffer large enough for the worst case that all the source
1152+
* characters need to be escaped, plus quotes.
1153+
*/
1154+
dstbuf = pg_malloc(strlen(src) * 2 + 2 + 1);
1155+
1156+
dst = dstbuf;
1157+
if (need_quotes)
1158+
*(dst++) = '\'';
1159+
for (; *src; src++)
1160+
{
1161+
if (*src == '\'' || *src == '\\')
1162+
*(dst++) = '\\';
1163+
*(dst++) = *src;
1164+
}
1165+
if (need_quotes)
1166+
*(dst++) = '\'';
1167+
*dst = '\0';
1168+
1169+
return dstbuf;
1170+
}
1171+
1172+
/*
1173+
* Escape a string so that it can be used as a value in a key-value pair
1174+
* a configuration file.
11111175
*/
11121176
static char *
11131177
escape_quotes(const char *src)
@@ -1130,6 +1194,8 @@ GenerateRecoveryConf(PGconn *conn)
11301194
{
11311195
PQconninfoOption *connOptions;
11321196
PQconninfoOption *option;
1197+
PQExpBufferData conninfo_buf;
1198+
char *escaped;
11331199

11341200
recoveryconfcontents = createPQExpBuffer();
11351201
if (!recoveryconfcontents)
@@ -1146,12 +1212,10 @@ GenerateRecoveryConf(PGconn *conn)
11461212
}
11471213

11481214
appendPQExpBufferStr(recoveryconfcontents, "standby_mode = 'on'\n");
1149-
appendPQExpBufferStr(recoveryconfcontents, "primary_conninfo = '");
11501215

1216+
initPQExpBuffer(&conninfo_buf);
11511217
for (option = connOptions; option && option->keyword; option++)
11521218
{
1153-
char *escaped;
1154-
11551219
/*
11561220
* Do not emit this setting if: - the setting is "replication",
11571221
* "dbname" or "fallback_application_name", since these would be
@@ -1165,24 +1229,37 @@ GenerateRecoveryConf(PGconn *conn)
11651229
(option->val != NULL && option->val[0] == '\0'))
11661230
continue;
11671231

1232+
/* Separate key-value pairs with spaces */
1233+
if (conninfo_buf.len != 0)
1234+
appendPQExpBufferStr(&conninfo_buf, " ");
1235+
11681236
/*
1169-
* Write "keyword='value'" pieces, the value string is escaped if
1170-
* necessary and doubled single quotes around the value string.
1237+
* Write "keyword=value" pieces, the value string is escaped and/or
1238+
* quoted if necessary.
11711239
*/
1172-
escaped = escape_quotes(option->val);
1173-
1174-
appendPQExpBuffer(recoveryconfcontents, "%s=''%s'' ", option->keyword, escaped);
1175-
1240+
escaped = escapeConnectionParameter(option->val);
1241+
appendPQExpBuffer(&conninfo_buf, "%s=%s", option->keyword, escaped);
11761242
free(escaped);
11771243
}
11781244

1179-
appendPQExpBufferStr(recoveryconfcontents, "'\n");
1180-
if (PQExpBufferBroken(recoveryconfcontents))
1245+
/*
1246+
* Escape the connection string, so that it can be put in the config file.
1247+
* Note that this is different from the escaping of individual connection
1248+
* options above!
1249+
*/
1250+
escaped = escape_quotes(conninfo_buf.data);
1251+
appendPQExpBuffer(recoveryconfcontents, "primary_conninfo = '%s'\n", escaped);
1252+
free(escaped);
1253+
1254+
if (PQExpBufferBroken(recoveryconfcontents) ||
1255+
PQExpBufferDataBroken(conninfo_buf))
11811256
{
11821257
fprintf(stderr, _("%s: out of memory\n"), progname);
11831258
disconnect_and_exit(1);
11841259
}
11851260

1261+
termPQExpBuffer(&conninfo_buf);
1262+
11861263
PQconninfoFree(connOptions);
11871264
}
11881265

0 commit comments

Comments
 (0)