Commit 30b5ede7 authored by Heikki Linnakangas's avatar Heikki Linnakangas

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.
parent 2af0971f
...@@ -1107,7 +1107,71 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum) ...@@ -1107,7 +1107,71 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
} }
/* /*
* Escape single quotes used in connection parameters * Escape a parameter value so that it can be used as part of a libpq
* connection string, e.g. in:
*
* application_name=<value>
*
* The returned string is malloc'd. Return NULL on out-of-memory.
*/
static char *
escapeConnectionParameter(const char *src)
{
bool need_quotes = false;
bool need_escaping = false;
const char *p;
char *dstbuf;
char *dst;
/*
* First check if quoting is needed. Any quote (') or backslash (\)
* characters need to be escaped. Parameters are separated by whitespace,
* so any string containing whitespace characters need to be quoted. An
* empty string is represented by ''.
*/
if (strchr(src, '\'') != NULL || strchr(src, '\\') != NULL)
need_escaping = true;
for (p = src; *p; p++)
{
if (isspace(*p))
{
need_quotes = true;
break;
}
}
if (*src == '\0')
return pg_strdup("''");
if (!need_quotes && !need_escaping)
return pg_strdup(src); /* no quoting or escaping needed */
/*
* Allocate a buffer large enough for the worst case that all the source
* characters need to be escaped, plus quotes.
*/
dstbuf = pg_malloc(strlen(src) * 2 + 2 + 1);
dst = dstbuf;
if (need_quotes)
*(dst++) = '\'';
for (; *src; src++)
{
if (*src == '\'' || *src == '\\')
*(dst++) = '\\';
*(dst++) = *src;
}
if (need_quotes)
*(dst++) = '\'';
*dst = '\0';
return dstbuf;
}
/*
* Escape a string so that it can be used as a value in a key-value pair
* a configuration file.
*/ */
static char * static char *
escape_quotes(const char *src) escape_quotes(const char *src)
...@@ -1130,6 +1194,8 @@ GenerateRecoveryConf(PGconn *conn) ...@@ -1130,6 +1194,8 @@ GenerateRecoveryConf(PGconn *conn)
{ {
PQconninfoOption *connOptions; PQconninfoOption *connOptions;
PQconninfoOption *option; PQconninfoOption *option;
PQExpBufferData conninfo_buf;
char *escaped;
recoveryconfcontents = createPQExpBuffer(); recoveryconfcontents = createPQExpBuffer();
if (!recoveryconfcontents) if (!recoveryconfcontents)
...@@ -1146,12 +1212,10 @@ GenerateRecoveryConf(PGconn *conn) ...@@ -1146,12 +1212,10 @@ GenerateRecoveryConf(PGconn *conn)
} }
appendPQExpBufferStr(recoveryconfcontents, "standby_mode = 'on'\n"); appendPQExpBufferStr(recoveryconfcontents, "standby_mode = 'on'\n");
appendPQExpBufferStr(recoveryconfcontents, "primary_conninfo = '");
initPQExpBuffer(&conninfo_buf);
for (option = connOptions; option && option->keyword; option++) for (option = connOptions; option && option->keyword; option++)
{ {
char *escaped;
/* /*
* Do not emit this setting if: - the setting is "replication", * Do not emit this setting if: - the setting is "replication",
* "dbname" or "fallback_application_name", since these would be * "dbname" or "fallback_application_name", since these would be
...@@ -1165,24 +1229,37 @@ GenerateRecoveryConf(PGconn *conn) ...@@ -1165,24 +1229,37 @@ GenerateRecoveryConf(PGconn *conn)
(option->val != NULL && option->val[0] == '\0')) (option->val != NULL && option->val[0] == '\0'))
continue; continue;
/* Separate key-value pairs with spaces */
if (conninfo_buf.len != 0)
appendPQExpBufferStr(&conninfo_buf, " ");
/* /*
* Write "keyword='value'" pieces, the value string is escaped if * Write "keyword=value" pieces, the value string is escaped and/or
* necessary and doubled single quotes around the value string. * quoted if necessary.
*/ */
escaped = escape_quotes(option->val); escaped = escapeConnectionParameter(option->val);
appendPQExpBuffer(&conninfo_buf, "%s=%s", option->keyword, escaped);
appendPQExpBuffer(recoveryconfcontents, "%s=''%s'' ", option->keyword, escaped);
free(escaped); free(escaped);
} }
appendPQExpBufferStr(recoveryconfcontents, "'\n"); /*
if (PQExpBufferBroken(recoveryconfcontents)) * Escape the connection string, so that it can be put in the config file.
* Note that this is different from the escaping of individual connection
* options above!
*/
escaped = escape_quotes(conninfo_buf.data);
appendPQExpBuffer(recoveryconfcontents, "primary_conninfo = '%s'\n", escaped);
free(escaped);
if (PQExpBufferBroken(recoveryconfcontents) ||
PQExpBufferDataBroken(conninfo_buf))
{ {
fprintf(stderr, _("%s: out of memory\n"), progname); fprintf(stderr, _("%s: out of memory\n"), progname);
disconnect_and_exit(1); disconnect_and_exit(1);
} }
termPQExpBuffer(&conninfo_buf);
PQconninfoFree(connOptions); PQconninfoFree(connOptions);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment