diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index a3afa201ea2..7a1aa95568f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -479,6 +479,32 @@ get_publications_str(List *publications, StringInfo dest, bool quote_literal) } } +/* + * Append a suitably-quoted identifier or string literal to buf. + * "quote" should be either a double-quote or single-quote character. + * + * Caution: this quoting logic is sufficient for identifiers and literals + * in the replication grammar, but not always in regular SQL. Specifically, + * it'd fail for a string literal if standard_conforming_strings is off. + */ +static void +appendQuotedString(StringInfo buf, const char *str, char quote) +{ + appendStringInfoChar(buf, quote); + while (*str) + { + char c = *str++; + + if (c == quote) + appendStringInfoChar(buf, c); + appendStringInfoChar(buf, c); + } + appendStringInfoChar(buf, quote); +} + +#define appendQuotedIdentifier(b, s) appendQuotedString(b, s, '"') +#define appendQuotedLiteral(b, s) appendQuotedString(b, s, '\'') + /* * Check that the specified publications are present on the publisher. */ @@ -1850,7 +1876,9 @@ ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missi load_file("libpqwalreceiver", false); initStringInfo(&cmd); - appendStringInfo(&cmd, "DROP_REPLICATION_SLOT %s WAIT", quote_identifier(slotname)); + appendStringInfoString(&cmd, "DROP_REPLICATION_SLOT "); + appendQuotedIdentifier(&cmd, slotname); + appendStringInfoString(&cmd, " WAIT"); PG_TRY(); { diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c index ba0d8446a39..cb425429ae0 100644 --- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c +++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c @@ -113,7 +113,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = { /* Prototypes for private functions */ static PGresult *libpqrcv_PQexec(PGconn *streamConn, const char *query); static PGresult *libpqrcv_PQgetResult(PGconn *streamConn); -static char *stringlist_to_identifierstr(PGconn *conn, List *strings); +static char *stringlist_to_identifierstr(List *strings); /* * Module initialization function @@ -562,6 +562,32 @@ libpqrcv_get_option_from_conninfo(const char *connInfo, const char *keyword) return option; } +/* + * Append a suitably-quoted identifier or string literal to buf. + * "quote" should be either a double-quote or single-quote character. + * + * Caution: this quoting logic is sufficient for identifiers and literals + * in the replication grammar, but not always in regular SQL. Specifically, + * it'd fail for a string literal if standard_conforming_strings is off. + */ +static void +appendQuotedString(StringInfo buf, const char *str, char quote) +{ + appendStringInfoChar(buf, quote); + while (*str) + { + char c = *str++; + + if (c == quote) + appendStringInfoChar(buf, c); + appendStringInfoChar(buf, c); + } + appendStringInfoChar(buf, quote); +} + +#define appendQuotedIdentifier(b, s) appendQuotedString(b, s, '"') +#define appendQuotedLiteral(b, s) appendQuotedString(b, s, '\'') + /* * Start streaming WAL data from given streaming options. * @@ -587,8 +613,10 @@ libpqrcv_startstreaming(WalReceiverConn *conn, /* Build the command. */ appendStringInfoString(&cmd, "START_REPLICATION"); if (options->slotname != NULL) - appendStringInfo(&cmd, " SLOT \"%s\"", - options->slotname); + { + appendStringInfoString(&cmd, " SLOT "); + appendQuotedIdentifier(&cmd, options->slotname); + } if (options->logical) appendStringInfoString(&cmd, " LOGICAL"); @@ -603,7 +631,6 @@ libpqrcv_startstreaming(WalReceiverConn *conn, { char *pubnames_str; List *pubnames; - char *pubnames_literal; appendStringInfoString(&cmd, " ("); @@ -611,8 +638,10 @@ libpqrcv_startstreaming(WalReceiverConn *conn, options->proto.logical.proto_version); if (options->proto.logical.streaming_str) - appendStringInfo(&cmd, ", streaming '%s'", - options->proto.logical.streaming_str); + { + appendStringInfoString(&cmd, ", streaming "); + appendQuotedLiteral(&cmd, options->proto.logical.streaming_str); + } if (options->proto.logical.twophase && PQserverVersion(conn->streamConn) >= 150000) @@ -620,25 +649,15 @@ libpqrcv_startstreaming(WalReceiverConn *conn, if (options->proto.logical.origin && PQserverVersion(conn->streamConn) >= 160000) - appendStringInfo(&cmd, ", origin '%s'", - options->proto.logical.origin); + { + appendStringInfoString(&cmd, ", origin "); + appendQuotedLiteral(&cmd, options->proto.logical.origin); + } pubnames = options->proto.logical.publication_names; - pubnames_str = stringlist_to_identifierstr(conn->streamConn, pubnames); - if (!pubnames_str) - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), /* likely guess */ - errmsg("could not start WAL streaming: %s", - pchomp(PQerrorMessage(conn->streamConn))))); - pubnames_literal = PQescapeLiteral(conn->streamConn, pubnames_str, - strlen(pubnames_str)); - if (!pubnames_literal) - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), /* likely guess */ - errmsg("could not start WAL streaming: %s", - pchomp(PQerrorMessage(conn->streamConn))))); - appendStringInfo(&cmd, ", publication_names %s", pubnames_literal); - PQfreemem(pubnames_literal); + pubnames_str = stringlist_to_identifierstr(pubnames); + appendStringInfoString(&cmd, ", publication_names "); + appendQuotedLiteral(&cmd, pubnames_str); pfree(pubnames_str); if (options->proto.logical.binary && @@ -1045,7 +1064,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname, initStringInfo(&cmd); - appendStringInfo(&cmd, "CREATE_REPLICATION_SLOT \"%s\"", slotname); + appendStringInfoString(&cmd, "CREATE_REPLICATION_SLOT "); + appendQuotedIdentifier(&cmd, slotname); if (temporary) appendStringInfoString(&cmd, " TEMPORARY"); @@ -1152,8 +1172,9 @@ libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname, PGresult *res; initStringInfo(&cmd); - appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )", - quote_identifier(slotname), + appendStringInfoString(&cmd, "ALTER_REPLICATION_SLOT "); + appendQuotedIdentifier(&cmd, slotname); + appendStringInfo(&cmd, " ( FAILOVER %s )", failover ? "true" : "false"); res = libpqrcv_PQexec(conn->streamConn, cmd.data); @@ -1334,10 +1355,10 @@ libpqrcv_exec(WalReceiverConn *conn, const char *query, * * This is essentially the reverse of SplitIdentifierString. * - * The caller should free the result. + * The caller should pfree the result. */ static char * -stringlist_to_identifierstr(PGconn *conn, List *strings) +stringlist_to_identifierstr(List *strings) { ListCell *lc; StringInfoData res; @@ -1348,21 +1369,12 @@ stringlist_to_identifierstr(PGconn *conn, List *strings) foreach(lc, strings) { char *val = strVal(lfirst(lc)); - char *val_escaped; if (first) first = false; else appendStringInfoChar(&res, ','); - - val_escaped = PQescapeIdentifier(conn, val, strlen(val)); - if (!val_escaped) - { - free(res.data); - return NULL; - } - appendStringInfoString(&res, val_escaped); - PQfreemem(val_escaped); + appendQuotedIdentifier(&res, val); } return res.data; diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l index e7def800655..a8c07491e6c 100644 --- a/src/backend/replication/repl_scanner.l +++ b/src/backend/replication/repl_scanner.l @@ -191,6 +191,10 @@ UPLOAD_MANIFEST { return K_UPLOAD_MANIFEST; } return IDENT; } +{xddouble} { + addlitchar('"'); + } + {xdinside} { addlit(yytext, yyleng); } diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c index 7424c475b81..949cd678385 100644 --- a/src/bin/pg_basebackup/pg_recvlogical.c +++ b/src/bin/pg_basebackup/pg_recvlogical.c @@ -242,8 +242,9 @@ StreamLogicalLog(void) /* Initiate the replication stream at specified location */ query = createPQExpBuffer(); - appendPQExpBuffer(query, "START_REPLICATION SLOT \"%s\" LOGICAL %X/%X", - replication_slot, LSN_FORMAT_ARGS(startpos)); + appendPQExpBufferStr(query, "START_REPLICATION SLOT "); + AppendQuotedIdentifier(query, replication_slot); + appendPQExpBuffer(query, " LOGICAL %X/%X", LSN_FORMAT_ARGS(startpos)); /* print options if there are any */ if (noptions) @@ -256,11 +257,14 @@ StreamLogicalLog(void) appendPQExpBufferStr(query, ", "); /* write option name */ - appendPQExpBuffer(query, "\"%s\"", options[(i * 2)]); + AppendQuotedIdentifier(query, options[i * 2]); /* write option value if specified */ - if (options[(i * 2) + 1] != NULL) - appendPQExpBuffer(query, " '%s'", options[(i * 2) + 1]); + if (options[i * 2 + 1] != NULL) + { + appendPQExpBufferChar(query, ' '); + AppendQuotedLiteral(query, options[i * 2 + 1]); + } } if (noptions) diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c index 8543f3576a8..34def1729cf 100644 --- a/src/bin/pg_basebackup/receivelog.c +++ b/src/bin/pg_basebackup/receivelog.c @@ -452,8 +452,7 @@ CheckServerVersionForStreaming(PGconn *conn) bool ReceiveXlogStream(PGconn *conn, StreamCtl *stream) { - char query[128]; - char slotcmd[128]; + PQExpBuffer query; PGresult *res; XLogRecPtr stoppos; @@ -478,7 +477,6 @@ ReceiveXlogStream(PGconn *conn, StreamCtl *stream) if (stream->replication_slot != NULL) { reportFlushPosition = true; - sprintf(slotcmd, "SLOT \"%s\" ", stream->replication_slot); } else { @@ -486,7 +484,6 @@ ReceiveXlogStream(PGconn *conn, StreamCtl *stream) reportFlushPosition = true; else reportFlushPosition = false; - slotcmd[0] = 0; } if (stream->sysidentifier != NULL) @@ -535,8 +532,10 @@ ReceiveXlogStream(PGconn *conn, StreamCtl *stream) */ if (!existsTimeLineHistoryFile(stream)) { - snprintf(query, sizeof(query), "TIMELINE_HISTORY %u", stream->timeline); - res = PQexec(conn, query); + query = createPQExpBuffer(); + appendPQExpBuffer(query, "TIMELINE_HISTORY %u", stream->timeline); + res = PQexec(conn, query->data); + destroyPQExpBuffer(query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { /* FIXME: we might send it ok, but get an error */ @@ -572,11 +571,18 @@ ReceiveXlogStream(PGconn *conn, StreamCtl *stream) return true; /* Initiate the replication stream at specified location */ - snprintf(query, sizeof(query), "START_REPLICATION %s%X/%X TIMELINE %u", - slotcmd, - LSN_FORMAT_ARGS(stream->startpos), - stream->timeline); - res = PQexec(conn, query); + query = createPQExpBuffer(); + appendPQExpBufferStr(query, "START_REPLICATION"); + if (stream->replication_slot != NULL) + { + appendPQExpBufferStr(query, " SLOT "); + AppendQuotedIdentifier(query, stream->replication_slot); + } + appendPQExpBuffer(query, " %X/%X TIMELINE %u", + LSN_FORMAT_ARGS(stream->startpos), + stream->timeline); + res = PQexec(conn, query->data); + destroyPQExpBuffer(query); if (PQresultStatus(res) != PGRES_COPY_BOTH) { pg_log_error("could not send replication command \"%s\": %s", diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index dc604b15380..d316f84277d 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -572,7 +572,8 @@ GetSlotInformation(PGconn *conn, const char *slot_name, *restart_tli = tli_loc; query = createPQExpBuffer(); - appendPQExpBuffer(query, "READ_REPLICATION_SLOT %s", slot_name); + appendPQExpBufferStr(query, "READ_REPLICATION_SLOT "); + AppendQuotedIdentifier(query, slot_name); res = PQexec(conn, query->data); destroyPQExpBuffer(query); @@ -668,13 +669,17 @@ CreateReplicationSlot(PGconn *conn, const char *slot_name, const char *plugin, Assert(slot_name != NULL); /* Build base portion of query */ - appendPQExpBuffer(query, "CREATE_REPLICATION_SLOT \"%s\"", slot_name); + appendPQExpBufferStr(query, "CREATE_REPLICATION_SLOT "); + AppendQuotedIdentifier(query, slot_name); if (is_temporary) appendPQExpBufferStr(query, " TEMPORARY"); if (is_physical) appendPQExpBufferStr(query, " PHYSICAL"); else - appendPQExpBuffer(query, " LOGICAL \"%s\"", plugin); + { + appendPQExpBufferStr(query, " LOGICAL "); + AppendQuotedIdentifier(query, plugin); + } /* Add any requested options */ if (use_new_option_syntax) @@ -770,8 +775,8 @@ DropReplicationSlot(PGconn *conn, const char *slot_name) query = createPQExpBuffer(); /* Build query */ - appendPQExpBuffer(query, "DROP_REPLICATION_SLOT \"%s\"", - slot_name); + appendPQExpBufferStr(query, "DROP_REPLICATION_SLOT "); + AppendQuotedIdentifier(query, slot_name); res = PQexec(conn, query->data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { @@ -799,6 +804,29 @@ DropReplicationSlot(PGconn *conn, const char *slot_name) return true; } +/* + * Append a suitably-quoted identifier or string literal to buf. + * "quote" should be either a double-quote or single-quote character. + * + * Caution: this quoting logic is sufficient for identifiers and literals + * in the replication grammar, but not always in regular SQL. Specifically, + * it'd fail for a string literal if standard_conforming_strings is off. + */ +void +AppendQuotedString(PQExpBuffer buf, const char *str, char quote) +{ + appendPQExpBufferChar(buf, quote); + while (*str) + { + char c = *str++; + + if (c == quote) + appendPQExpBufferChar(buf, c); + appendPQExpBufferChar(buf, c); + } + appendPQExpBufferChar(buf, quote); +} + /* * Append a "plain" option - one with no value - to a server command that * is being constructed. @@ -807,10 +835,13 @@ DropReplicationSlot(PGconn *conn, const char *slot_name) * write things like SOME_COMMAND OPTION1 OPTION2 'opt2value' OPTION3 42. The * new syntax uses a comma-separated list surrounded by parentheses, so the * equivalent is SOME_COMMAND (OPTION1, OPTION2 'optvalue', OPTION3 42). + * + * Note: we assume option names do not require quotes. Do not use this + * with option names coming from outside sources. */ void AppendPlainCommandOption(PQExpBuffer buf, bool use_new_option_syntax, - char *option_name) + const char *option_name) { if (buf->len > 0 && buf->data[buf->len - 1] != '(') { @@ -831,30 +862,26 @@ AppendPlainCommandOption(PQExpBuffer buf, bool use_new_option_syntax, */ void AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, - char *option_name, char *option_value) + const char *option_name, const char *option_value) { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); if (option_value != NULL) { - size_t length = strlen(option_value); - char *escaped_value = palloc(1 + 2 * length); - - PQescapeStringConn(conn, escaped_value, option_value, length, NULL); - appendPQExpBuffer(buf, " '%s'", escaped_value); - pfree(escaped_value); + appendPQExpBufferChar(buf, ' '); + AppendQuotedLiteral(buf, option_value); } } /* - * Append an option with an associated integer value to a server command + * Append an option with an associated integer value to a server command that * is being constructed. * * See comments for AppendPlainCommandOption, above. */ void AppendIntegerCommandOption(PQExpBuffer buf, bool use_new_option_syntax, - char *option_name, int32 option_value) + const char *option_name, int32 option_value) { AppendPlainCommandOption(buf, use_new_option_syntax, option_name); diff --git a/src/bin/pg_basebackup/streamutil.h b/src/bin/pg_basebackup/streamutil.h index 9b38e8c0f38..f9bfe881c6b 100644 --- a/src/bin/pg_basebackup/streamutil.h +++ b/src/bin/pg_basebackup/streamutil.h @@ -44,15 +44,20 @@ extern bool RunIdentifySystem(PGconn *conn, char **sysid, XLogRecPtr *startpos, char **db_name); +extern void AppendQuotedString(PQExpBuffer buf, const char *str, char quote); +#define AppendQuotedIdentifier(b, s) AppendQuotedString(b, s, '"') +#define AppendQuotedLiteral(b, s) AppendQuotedString(b, s, '\'') extern void AppendPlainCommandOption(PQExpBuffer buf, bool use_new_option_syntax, - char *option_name); + const char *option_name); extern void AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax, - char *option_name, char *option_value); + const char *option_name, + const char *option_value); extern void AppendIntegerCommandOption(PQExpBuffer buf, bool use_new_option_syntax, - char *option_name, int32 option_value); + const char *option_name, + int32 option_value); extern bool GetSlotInformation(PGconn *conn, const char *slot_name, XLogRecPtr *restart_lsn,