diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index b51b11baa3..ad463e71c1 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -769,18 +769,33 @@ testdb=>
quotes that single character, whatever it is.
-
- Within an argument, text that is enclosed in backquotes
- (`) is taken as a command line that is passed to the
- shell. The output of the command (with any trailing newline removed)
- replaces the backquoted text.
-
-
If an unquoted colon (:) followed by a
psql> variable name appears within an argument, it is
replaced by the variable's value, as described in .
+ The forms :'variable_name>' and
+ :"variable_name>" described there
+ work as well.
+
+
+
+ Within an argument, text that is enclosed in backquotes
+ (`) is taken as a command line that is passed to the
+ shell. The output of the command (with any trailing newline removed)
+ replaces the backquoted text. Within the text enclosed in backquotes,
+ no special quoting or other processing occurs, except that appearances
+ of :variable_name> where
+ variable_name> is a psql> variable name
+ are replaced by the variable's value. Also, appearances of
+ :'variable_name>' are replaced by the
+ variable's value suitably quoted to become a single shell command
+ argument. (The latter form is almost always preferable, unless you are
+ very sure of what is in the variable.) Because carriage return and line
+ feed characters cannot be safely quoted on all platforms, the
+ :'variable_name>' form prints an
+ error message and does not substitute the variable value when such
+ characters appear in the value.
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index b06ae9779d..a2f1259c1e 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -116,19 +116,19 @@ setQFout(const char *fname)
* If the specified variable exists, return its value as a string (malloc'd
* and expected to be freed by the caller); else return NULL.
*
- * If "escape" is true, return the value suitably quoted and escaped,
- * as an identifier or string literal depending on "as_ident".
- * (Failure in escaping should lead to returning NULL.)
+ * If "quote" isn't PQUOTE_PLAIN, then return the value suitably quoted and
+ * escaped for the specified quoting requirement. (Failure in escaping
+ * should lead to printing an error and returning NULL.)
*
* "passthrough" is the pointer previously given to psql_scan_set_passthrough.
* In psql, passthrough points to a ConditionalStack, which we check to
* determine whether variable expansion is allowed.
*/
char *
-psql_get_variable(const char *varname, bool escape, bool as_ident,
+psql_get_variable(const char *varname, PsqlScanQuoteType quote,
void *passthrough)
{
- char *result;
+ char *result = NULL;
const char *value;
/* In an inactive \if branch, suppress all variable substitutions */
@@ -139,40 +139,74 @@ psql_get_variable(const char *varname, bool escape, bool as_ident,
if (!value)
return NULL;
- if (escape)
+ switch (quote)
{
- char *escaped_value;
+ case PQUOTE_PLAIN:
+ result = pg_strdup(value);
+ break;
+ case PQUOTE_SQL_LITERAL:
+ case PQUOTE_SQL_IDENT:
+ {
+ /*
+ * For these cases, we use libpq's quoting functions, which
+ * assume the string is in the connection's client encoding.
+ */
+ char *escaped_value;
- if (!pset.db)
- {
- psql_error("cannot escape without active connection\n");
- return NULL;
- }
+ if (!pset.db)
+ {
+ psql_error("cannot escape without active connection\n");
+ return NULL;
+ }
- if (as_ident)
- escaped_value =
- PQescapeIdentifier(pset.db, value, strlen(value));
- else
- escaped_value =
- PQescapeLiteral(pset.db, value, strlen(value));
+ if (quote == PQUOTE_SQL_LITERAL)
+ escaped_value =
+ PQescapeLiteral(pset.db, value, strlen(value));
+ else
+ escaped_value =
+ PQescapeIdentifier(pset.db, value, strlen(value));
- if (escaped_value == NULL)
- {
- const char *error = PQerrorMessage(pset.db);
+ if (escaped_value == NULL)
+ {
+ const char *error = PQerrorMessage(pset.db);
- psql_error("%s", error);
- return NULL;
- }
+ psql_error("%s", error);
+ return NULL;
+ }
- /*
- * Rather than complicate the lexer's API with a notion of which
- * free() routine to use, just pay the price of an extra strdup().
- */
- result = pg_strdup(escaped_value);
- PQfreemem(escaped_value);
+ /*
+ * Rather than complicate the lexer's API with a notion of
+ * which free() routine to use, just pay the price of an extra
+ * strdup().
+ */
+ result = pg_strdup(escaped_value);
+ PQfreemem(escaped_value);
+ break;
+ }
+ case PQUOTE_SHELL_ARG:
+ {
+ /*
+ * For this we use appendShellStringNoError, which is
+ * encoding-agnostic, which is fine since the shell probably
+ * is too. In any case, the only special character is "'",
+ * which is not known to appear in valid multibyte characters.
+ */
+ PQExpBufferData buf;
+
+ initPQExpBuffer(&buf);
+ if (!appendShellStringNoError(&buf, value))
+ {
+ psql_error("shell command argument contains a newline or carriage return: \"%s\"\n",
+ value);
+ free(buf.data);
+ return NULL;
+ }
+ result = buf.data;
+ break;
+ }
+
+ /* No default: we want a compiler warning for missing cases */
}
- else
- result = pg_strdup(value);
return result;
}
diff --git a/src/bin/psql/common.h b/src/bin/psql/common.h
index 3d8b8da7fe..1ceb8ae386 100644
--- a/src/bin/psql/common.h
+++ b/src/bin/psql/common.h
@@ -12,11 +12,12 @@
#include "libpq-fe.h"
#include "fe_utils/print.h"
+#include "fe_utils/psqlscan.h"
extern bool openQueryOutputFile(const char *fname, FILE **fout, bool *is_pipe);
extern bool setQFout(const char *fname);
-extern char *psql_get_variable(const char *varname, bool escape, bool as_ident,
+extern char *psql_get_variable(const char *varname, PsqlScanQuoteType quote,
void *passthrough);
extern void psql_error(const char *fmt,...) pg_attribute_printf(1, 2);
diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l
index 319afdc744..db7a1b9eea 100644
--- a/src/bin/psql/psqlscanslash.l
+++ b/src/bin/psql/psqlscanslash.l
@@ -242,8 +242,7 @@ other .
yytext + 1,
yyleng - 1);
value = cur_state->callbacks->get_variable(varname,
- false,
- false,
+ PQUOTE_PLAIN,
cur_state->cb_passthrough);
free(varname);
@@ -268,14 +267,16 @@ other .
}
:'{variable_char}+' {
- psqlscan_escape_variable(cur_state, yytext, yyleng, false);
+ psqlscan_escape_variable(cur_state, yytext, yyleng,
+ PQUOTE_SQL_LITERAL);
*option_quote = ':';
unquoted_option_chars = 0;
}
:\"{variable_char}+\" {
- psqlscan_escape_variable(cur_state, yytext, yyleng, true);
+ psqlscan_escape_variable(cur_state, yytext, yyleng,
+ PQUOTE_SQL_IDENT);
*option_quote = ':';
unquoted_option_chars = 0;
}
@@ -337,9 +338,8 @@ other .
{
/*
- * backticked text: copy everything until next backquote, then evaluate.
- *
- * XXX Possible future behavioral change: substitute for :VARIABLE?
+ * backticked text: copy everything until next backquote (expanding
+ * variable references, but doing nought else), then evaluate.
*/
"`" {
@@ -350,6 +350,44 @@ other .
BEGIN(xslasharg);
}
+:{variable_char}+ {
+ /* Possible psql variable substitution */
+ if (cur_state->callbacks->get_variable == NULL)
+ ECHO;
+ else
+ {
+ char *varname;
+ char *value;
+
+ varname = psqlscan_extract_substring(cur_state,
+ yytext + 1,
+ yyleng - 1);
+ value = cur_state->callbacks->get_variable(varname,
+ PQUOTE_PLAIN,
+ cur_state->cb_passthrough);
+ free(varname);
+
+ if (value)
+ {
+ appendPQExpBufferStr(output_buf, value);
+ free(value);
+ }
+ else
+ ECHO;
+ }
+ }
+
+:'{variable_char}+' {
+ psqlscan_escape_variable(cur_state, yytext, yyleng,
+ PQUOTE_SHELL_ARG);
+ }
+
+:'{variable_char}* {
+ /* Throw back everything but the colon */
+ yyless(1);
+ ECHO;
+ }
+
{other}|\n { ECHO; }
}
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 19b3e57aa4..27689d72da 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -699,8 +699,7 @@ other .
yyleng - 1);
if (cur_state->callbacks->get_variable)
value = cur_state->callbacks->get_variable(varname,
- false,
- false,
+ PQUOTE_PLAIN,
cur_state->cb_passthrough);
else
value = NULL;
@@ -737,11 +736,13 @@ other .
}
:'{variable_char}+' {
- psqlscan_escape_variable(cur_state, yytext, yyleng, false);
+ psqlscan_escape_variable(cur_state, yytext, yyleng,
+ PQUOTE_SQL_LITERAL);
}
:\"{variable_char}+\" {
- psqlscan_escape_variable(cur_state, yytext, yyleng, true);
+ psqlscan_escape_variable(cur_state, yytext, yyleng,
+ PQUOTE_SQL_IDENT);
}
/*
@@ -1415,7 +1416,7 @@ psqlscan_extract_substring(PsqlScanState state, const char *txt, int len)
*/
void
psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
- bool as_ident)
+ PsqlScanQuoteType quote)
{
char *varname;
char *value;
@@ -1423,7 +1424,7 @@ psqlscan_escape_variable(PsqlScanState state, const char *txt, int len,
/* Variable lookup. */
varname = psqlscan_extract_substring(state, txt + 2, len - 3);
if (state->callbacks->get_variable)
- value = state->callbacks->get_variable(varname, true, as_ident,
+ value = state->callbacks->get_variable(varname, quote,
state->cb_passthrough);
else
value = NULL;
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index d1a9ddc4c6..dc84d32a09 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -425,13 +425,30 @@ appendByteaLiteral(PQExpBuffer buf, const unsigned char *str, size_t length,
* arguments containing LF or CR characters. A future major release should
* reject those characters in CREATE ROLE and CREATE DATABASE, because use
* there eventually leads to errors here.
+ *
+ * appendShellString() simply prints an error and dies if LF or CR appears.
+ * appendShellStringNoError() omits those characters from the result, and
+ * returns false if there were any.
*/
void
appendShellString(PQExpBuffer buf, const char *str)
+{
+ if (!appendShellStringNoError(buf, str))
+ {
+ fprintf(stderr,
+ _("shell command argument contains a newline or carriage return: \"%s\"\n"),
+ str);
+ exit(EXIT_FAILURE);
+ }
+}
+
+bool
+appendShellStringNoError(PQExpBuffer buf, const char *str)
{
#ifdef WIN32
int backslash_run_length = 0;
#endif
+ bool ok = true;
const char *p;
/*
@@ -442,7 +459,7 @@ appendShellString(PQExpBuffer buf, const char *str)
strspn(str, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_./:") == strlen(str))
{
appendPQExpBufferStr(buf, str);
- return;
+ return ok;
}
#ifndef WIN32
@@ -451,10 +468,8 @@ appendShellString(PQExpBuffer buf, const char *str)
{
if (*p == '\n' || *p == '\r')
{
- fprintf(stderr,
- _("shell command argument contains a newline or carriage return: \"%s\"\n"),
- str);
- exit(EXIT_FAILURE);
+ ok = false;
+ continue;
}
if (*p == '\'')
@@ -481,10 +496,8 @@ appendShellString(PQExpBuffer buf, const char *str)
{
if (*p == '\n' || *p == '\r')
{
- fprintf(stderr,
- _("shell command argument contains a newline or carriage return: \"%s\"\n"),
- str);
- exit(EXIT_FAILURE);
+ ok = false;
+ continue;
}
/* Change N backslashes before a double quote to 2N+1 backslashes. */
@@ -524,6 +537,8 @@ appendShellString(PQExpBuffer buf, const char *str)
}
appendPQExpBufferStr(buf, "^\"");
#endif /* WIN32 */
+
+ return ok;
}
diff --git a/src/include/fe_utils/psqlscan.h b/src/include/fe_utils/psqlscan.h
index 0cc632b821..e9c8143925 100644
--- a/src/include/fe_utils/psqlscan.h
+++ b/src/include/fe_utils/psqlscan.h
@@ -48,13 +48,22 @@ typedef enum _promptStatus
PROMPT_COPY
} promptStatus_t;
+/* Quoting request types for get_variable() callback */
+typedef enum
+{
+ PQUOTE_PLAIN, /* just return the actual value */
+ PQUOTE_SQL_LITERAL, /* add quotes to make a valid SQL literal */
+ PQUOTE_SQL_IDENT, /* quote if needed to make a SQL identifier */
+ PQUOTE_SHELL_ARG /* quote if needed to be safe in a shell cmd */
+} PsqlScanQuoteType;
+
/* Callback functions to be used by the lexer */
typedef struct PsqlScanCallbacks
{
- /* Fetch value of a variable, as a pfree'able string; NULL if unknown */
+ /* Fetch value of a variable, as a free'able string; NULL if unknown */
/* This pointer can be NULL if no variable substitution is wanted */
- char *(*get_variable) (const char *varname, bool escape,
- bool as_ident, void *passthrough);
+ char *(*get_variable) (const char *varname, PsqlScanQuoteType quote,
+ void *passthrough);
/* Print an error message someplace appropriate */
/* (very old gcc versions don't support attributes on function pointers) */
#if defined(__GNUC__) && __GNUC__ < 4
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index b4044e806a..af62f5ebdf 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -141,6 +141,6 @@ extern char *psqlscan_extract_substring(PsqlScanState state,
const char *txt, int len);
extern void psqlscan_escape_variable(PsqlScanState state,
const char *txt, int len,
- bool as_ident);
+ PsqlScanQuoteType quote);
#endif /* PSQLSCAN_INT_H */
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index 6fb7f5e30e..c68234335e 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -42,6 +42,7 @@ extern void appendByteaLiteral(PQExpBuffer buf,
bool std_strings);
extern void appendShellString(PQExpBuffer buf, const char *str);
+extern bool appendShellStringNoError(PQExpBuffer buf, const char *str);
extern void appendConnStrVal(PQExpBuffer buf, const char *str);
extern void appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname);