From 30b5ede7157e34e77c7914b8ecfd55aa8da6edc3 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Mon, 20 May 2013 19:34:27 +0300 Subject: [PATCH] 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. --- src/bin/pg_basebackup/pg_basebackup.c | 101 +++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 12 deletions(-) diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c index 45585069a5..84c3497922 100644 --- a/src/bin/pg_basebackup/pg_basebackup.c +++ b/src/bin/pg_basebackup/pg_basebackup.c @@ -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= + * + * 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 * escape_quotes(const char *src) @@ -1130,6 +1194,8 @@ GenerateRecoveryConf(PGconn *conn) { PQconninfoOption *connOptions; PQconninfoOption *option; + PQExpBufferData conninfo_buf; + char *escaped; recoveryconfcontents = createPQExpBuffer(); if (!recoveryconfcontents) @@ -1146,12 +1212,10 @@ GenerateRecoveryConf(PGconn *conn) } appendPQExpBufferStr(recoveryconfcontents, "standby_mode = 'on'\n"); - appendPQExpBufferStr(recoveryconfcontents, "primary_conninfo = '"); + initPQExpBuffer(&conninfo_buf); for (option = connOptions; option && option->keyword; option++) { - char *escaped; - /* * Do not emit this setting if: - the setting is "replication", * "dbname" or "fallback_application_name", since these would be @@ -1165,24 +1229,37 @@ GenerateRecoveryConf(PGconn *conn) (option->val != NULL && option->val[0] == '\0')) 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 - * necessary and doubled single quotes around the value string. + * Write "keyword=value" pieces, the value string is escaped and/or + * quoted if necessary. */ - escaped = escape_quotes(option->val); - - appendPQExpBuffer(recoveryconfcontents, "%s=''%s'' ", option->keyword, escaped); - + escaped = escapeConnectionParameter(option->val); + appendPQExpBuffer(&conninfo_buf, "%s=%s", option->keyword, 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); disconnect_and_exit(1); } + termPQExpBuffer(&conninfo_buf); + PQconninfoFree(connOptions); } -- 2.40.0