diff --git a/src/bin/psql/stringutils.c b/src/bin/psql/stringutils.c index 3b5ce1b..77387dc 100644 *** a/src/bin/psql/stringutils.c --- b/src/bin/psql/stringutils.c *************** *** 272,274 **** strip_quotes(char *source, char quote, char escape, int encoding) --- 272,343 ---- *dst = '\0'; } + + + /* + * quote_if_needed + * + * Opposite of strip_quotes(). If "source" denotes itself literally without + * quoting or escaping, returns NULL. Otherwise, returns a malloc'd copy with + * quoting and escaping applied: + * + * source - string to parse + * entails_quote - any of these present? need outer quotes + * quote - doubled within string, affixed to both ends + * escape - doubled within string + * encoding - the active character-set encoding + * + * Do not use this as a substitute for PQescapeStringConn(). Use it for + * strings to be parsed by strtokx() or psql_scan_slash_option(). + */ + char * + quote_if_needed(const char *source, const char *entails_quote, + char quote, char escape, int encoding) + { + const char *src; + char *ret; + char *dst; + bool need_quotes = false; + + psql_assert(source); + psql_assert(quote); + + src = source; + dst = ret = pg_malloc(2 * strlen(src) + 3); /* excess */ + + *dst++ = quote; + + while (*src) + { + char c = *src; + int i; + + if (c == quote) + { + need_quotes = true; + *dst++ = quote; + } + else if (c == escape) + { + need_quotes = true; + *dst++ = escape; + } + else if (strchr(entails_quote, c)) + need_quotes = true; + + i = PQmblen(src, encoding); + while (i--) + *dst++ = *src++; + } + + *dst++ = quote; + *dst = '\0'; + + if (!need_quotes) + { + free(ret); + ret = NULL; + } + + return ret; + } diff --git a/src/bin/psql/stringuindex c7c5f38..c64fc58 100644 *** a/src/bin/psql/stringutils.h --- b/src/bin/psql/stringutils.h *************** *** 19,22 **** extern char *strtokx(const char *s, --- 19,25 ---- bool del_quotes, int encoding); + extern char *quote_if_needed(const char *source, const char *entails_quote, + char quote, char escape, int encoding); + #endif /* STRINGUTILS_H */ diff --git a/src/bin/psql/tab-comindex a27ef69..d226106 100644 *** a/src/bin/psql/tab-complete.c --- b/src/bin/psql/tab-complete.c *************** *** 670,675 **** static char *complete_from_list(const char *text, int state); --- 670,676 ---- static char *complete_from_const(const char *text, int state); static char **complete_from_variables(char *text, const char *prefix, const char *suffix); + static char *complete_from_files(const char *text, int state); static PGresult *exec_query(const char *query); *************** *** 1619,1625 **** psql_completion(char *text, int start, int end) pg_strcasecmp(prev3_wd, "BINARY") == 0) && (pg_strcasecmp(prev_wd, "FROM") == 0 || pg_strcasecmp(prev_wd, "TO") == 0)) ! matches = completion_matches(text, filename_completion_function); /* Handle COPY|BINARY FROM|TO filename */ else if ((pg_strcasecmp(prev4_wd, "COPY") == 0 || --- 1620,1629 ---- pg_strcasecmp(prev3_wd, "BINARY") == 0) && (pg_strcasecmp(prev_wd, "FROM") == 0 || pg_strcasecmp(prev_wd, "TO") == 0)) ! { ! completion_charp = ""; ! matches = completion_matches(text, complete_from_files); ! } /* Handle COPY|BINARY FROM|TO filename */ else if ((pg_strcasecmp(prev4_wd, "COPY") == 0 || *************** *** 2899,2905 **** psql_completion(char *text, int start, int end) strcmp(prev_wd, "\\s") == 0 || strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0 ) ! matches = completion_matches(text, filename_completion_function); /* * Finally, we look through the list of "things", such as TABLE, INDEX and --- 2903,2912 ---- strcmp(prev_wd, "\\s") == 0 || strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0 ) ! { ! completion_charp = "\\"; ! matches = completion_matches(text, complete_from_files); ! } /* * Finally, we look through the list of "things", such as TABLE, INDEX and *************** *** 3361,3366 **** complete_from_variables(char *text, const char *prefix, const char *suffix) --- 3368,3420 ---- } + /* + * This function wraps rl_filename_completion_function() to strip quotes from + * the input before searching for matches and to quote any matches for which + * the consuming command will require it. + */ + static char * + complete_from_files(const char *text, int state) + { + static const char *unquoted_text; + char *unquoted_match; + char *ret = NULL; + + if (state == 0) + { + /* Initialization: stash the unquoted input. */ + unquoted_text = strtokx(text, "", NULL, "'", *completion_charp, + false, true, pset.encoding); + /* expect a NULL return for the empty string only */ + if (!unquoted_text) + { + psql_assert(!*text); + unquoted_text = text; + } + } + + unquoted_match = filename_completion_function(unquoted_text, state); + if (unquoted_match) + { + /* + * Caller sets completion_charp to a zero- or one-character string + * containing the escape character. This is necessary since \copy has + * no escape character, but every other backslash command recognizes + * "\" as an escape character. Since we have only two callers, don't + * bother providing a macro to simplify this. + */ + ret = quote_if_needed(unquoted_match, " \t\r\n\"`", + '\'', *completion_charp, pset.encoding); + if (ret) + free(unquoted_match); + else + ret = unquoted_match; + } + + return ret; + } + + /* HELPER FUNCTIONS */