Commit 77c1791a authored by Tom Lane's avatar Tom Lane

Use PQescapeString to ensure that tab-completion queries are not messed

up by quotes or backslashes in words that are being matched to database
names (per gripe from Ian Barwick, though I didn't use his patch).
Also fix possible memory leakage if _complete_with_query isn't run to
completion (not clear if that can happen or not, but be safe).
parent 2a0f1c08
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* *
* Copyright (c) 2000-2003, PostgreSQL Global Development Group * Copyright (c) 2000-2003, PostgreSQL Global Development Group
* *
* $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.85 2003/09/07 15:26:54 tgl Exp $ * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.86 2003/10/14 22:47:12 tgl Exp $
*/ */
/*---------------------------------------------------------------------- /*----------------------------------------------------------------------
...@@ -85,17 +85,20 @@ static char *complete_from_const(const char *text, int state); ...@@ -85,17 +85,20 @@ static char *complete_from_const(const char *text, int state);
static char *complete_from_list(const char *text, int state); static char *complete_from_list(const char *text, int state);
static PGresult *exec_query(char *query); static PGresult *exec_query(char *query);
char *quote_file_name(char *text, int match_type, char *quote_pointer);
/*static char * dequote_file_name(char *text, char quote_char);*/
static char *previous_word(int point, int skip); static char *previous_word(int point, int skip);
#if 0
static char *quote_file_name(char *text, int match_type, char *quote_pointer);
static char *dequote_file_name(char *text, char quote_char);
#endif
/* These variables are used to pass information into the completion functions. /* These variables are used to pass information into the completion functions.
Realizing that this is the cardinal sin of programming, I don't see a better Realizing that this is the cardinal sin of programming, I don't see a better
way. */ way. */
char *completion_charp; /* if you need to pass a string */ static char *completion_charp; /* if you need to pass a string */
char **completion_charpp; /* if you need to pass a list of strings */ static char **completion_charpp; /* if you need to pass a list of strings */
char *completion_info_charp; /* if you need to pass another static char *completion_info_charp; /* if you need to pass another
* string */ * string */
/* Store how many records from a database query we want to return at most /* Store how many records from a database query we want to return at most
...@@ -124,7 +127,10 @@ initialize_readline(void) ...@@ -124,7 +127,10 @@ initialize_readline(void)
/* /*
* Queries to get lists of names of various kinds of things, possibly * Queries to get lists of names of various kinds of things, possibly
* restricted to names matching a partially entered name. In these queries, * restricted to names matching a partially entered name. In these queries,
* the %s will be replaced by the text entered so far, the %d by its length. * %s will be replaced by the text entered so far (suitably escaped to
* become a SQL literal string). %d will be replaced by the length of the
* string (in unescaped form). Beware that the allowed sequences of %s and
* %d are determined by _complete_from_query().
*/ */
#define Query_for_list_of_aggregates \ #define Query_for_list_of_aggregates \
...@@ -401,6 +407,15 @@ initialize_readline(void) ...@@ -401,6 +407,15 @@ initialize_readline(void)
" FROM pg_catalog.pg_user "\ " FROM pg_catalog.pg_user "\
" WHERE substr(usename,1,%d)='%s'" " WHERE substr(usename,1,%d)='%s'"
/* the silly-looking length condition is just to eat up the current word */
#define Query_for_table_owning_index \
"SELECT c1.relname "\
" FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i"\
" WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid"\
" and (%d = length('%s'))"\
" and c2.relname='%s'"\
" and pg_catalog.pg_table_is_visible(c2.oid)"
/* This is a list of all "things" in Pgsql, which can show up after CREATE or /* This is a list of all "things" in Pgsql, which can show up after CREATE or
DROP; and there is also a query to get a list of them. DROP; and there is also a query to get a list of them.
*/ */
...@@ -754,15 +769,8 @@ psql_completion(char *text, int start, int end) ...@@ -754,15 +769,8 @@ psql_completion(char *text, int start, int end)
else if (strcasecmp(prev3_wd, "CLUSTER") == 0 && else if (strcasecmp(prev3_wd, "CLUSTER") == 0 &&
strcasecmp(prev_wd, "ON") == 0) strcasecmp(prev_wd, "ON") == 0)
{ {
char query_buffer[BUF_SIZE]; /* Some room to build completion_info_charp = prev2_wd;
* queries. */ COMPLETE_WITH_QUERY(Query_for_table_owning_index);
if (snprintf(query_buffer, BUF_SIZE,
"SELECT c1.relname FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid and c2.relname='%s' and pg_catalog.pg_table_is_visible(c2.oid)",
prev2_wd) == -1)
ERROR_QUERY_TOO_LONG;
else
COMPLETE_WITH_QUERY(query_buffer);
} }
/* COMMENT */ /* COMMENT */
...@@ -1425,6 +1433,9 @@ complete_from_schema_query(const char *text, int state) ...@@ -1425,6 +1433,9 @@ complete_from_schema_query(const char *text, int state)
%d %s %d %s %d %s %s %d %s %d %s %d %s %d %s %s %d %s
where %d is the string length of the text and %s the text itself. where %d is the string length of the text and %s the text itself.
It is assumed that strings should be escaped to become SQL literals
(that is, what is in the query is actually ... '%s' ...)
See top of file for examples of both kinds of query. See top of file for examples of both kinds of query.
*/ */
...@@ -1434,8 +1445,6 @@ _complete_from_query(int is_schema_query, const char *text, int state) ...@@ -1434,8 +1445,6 @@ _complete_from_query(int is_schema_query, const char *text, int state)
static int list_index, static int list_index,
string_length; string_length;
static PGresult *result = NULL; static PGresult *result = NULL;
char query_buffer[BUF_SIZE];
const char *item;
/* /*
* If this is the first time for this completion, we fetch a list of * If this is the first time for this completion, we fetch a list of
...@@ -1443,39 +1452,86 @@ _complete_from_query(int is_schema_query, const char *text, int state) ...@@ -1443,39 +1452,86 @@ _complete_from_query(int is_schema_query, const char *text, int state)
*/ */
if (state == 0) if (state == 0)
{ {
char query_buffer[BUF_SIZE];
char *e_text;
char *e_info_charp;
list_index = 0; list_index = 0;
string_length = strlen(text); string_length = strlen(text);
/* Free any prior result */
PQclear(result);
result = NULL;
/* Need to have a query */ /* Need to have a query */
if (completion_charp == NULL) if (completion_charp == NULL)
return NULL; return NULL;
if (is_schema_query) /* Set up suitably-escaped copies of textual inputs */
{ if (text)
if (snprintf(query_buffer, BUF_SIZE, completion_charp, string_length, text, string_length, text, string_length, text, text, string_length, text, string_length, text) == -1)
{ {
ERROR_QUERY_TOO_LONG; e_text = (char *) malloc(strlen(text) * 2 + 1);
if (!e_text)
return NULL; return NULL;
} PQescapeString(e_text, text, strlen(text));
} }
else else
e_text = NULL;
if (completion_info_charp)
{ {
if (snprintf(query_buffer, BUF_SIZE, completion_charp, string_length, text, completion_info_charp) == -1) e_info_charp = (char *)
malloc(strlen(completion_info_charp) * 2 + 1);
if (!e_info_charp)
{ {
ERROR_QUERY_TOO_LONG; if (e_text)
free(e_text);
return NULL; return NULL;
} }
PQescapeString(e_info_charp, completion_info_charp,
strlen(completion_info_charp));
} }
else
e_info_charp = NULL;
if (is_schema_query)
{
if (snprintf(query_buffer, BUF_SIZE, completion_charp,
string_length, e_text,
string_length, e_text,
string_length, e_text,
e_text,
string_length, e_text,
string_length, e_text) == -1)
ERROR_QUERY_TOO_LONG;
else
result = exec_query(query_buffer);
}
else
{
if (snprintf(query_buffer, BUF_SIZE, completion_charp,
string_length, e_text, e_info_charp) == -1)
ERROR_QUERY_TOO_LONG;
else
result = exec_query(query_buffer); result = exec_query(query_buffer);
} }
if (e_text)
free(e_text);
if (e_info_charp)
free(e_info_charp);
}
/* Find something that matches */ /* Find something that matches */
if (result && PQresultStatus(result) == PGRES_TUPLES_OK) if (result && PQresultStatus(result) == PGRES_TUPLES_OK)
{
const char *item;
while (list_index < PQntuples(result) && while (list_index < PQntuples(result) &&
(item = PQgetvalue(result, list_index++, 0))) (item = PQgetvalue(result, list_index++, 0)))
if (strncasecmp(text, item, string_length) == 0) if (strncasecmp(text, item, string_length) == 0)
return xstrdup(item); return xstrdup(item);
}
/* If nothing matches, free the db structure and return null */ /* If nothing matches, free the db structure and return null */
PQclear(result); PQclear(result);
...@@ -1585,7 +1641,8 @@ exec_query(char *query) ...@@ -1585,7 +1641,8 @@ exec_query(char *query)
assert(query[strlen(query) - 1] != ';'); assert(query[strlen(query) - 1] != ';');
#endif #endif
if (snprintf(query_buffer, BUF_SIZE, "%s LIMIT %d;", query, completion_max_records) == -1) if (snprintf(query_buffer, BUF_SIZE, "%s LIMIT %d;",
query, completion_max_records) == -1)
{ {
ERROR_QUERY_TOO_LONG; ERROR_QUERY_TOO_LONG;
return NULL; return NULL;
...@@ -1684,7 +1741,7 @@ previous_word(int point, int skip) ...@@ -1684,7 +1741,7 @@ previous_word(int point, int skip)
* psql internal. Currently disable because it is reported not to * psql internal. Currently disable because it is reported not to
* cooperate with certain versions of readline. * cooperate with certain versions of readline.
*/ */
char * static char *
quote_file_name(char *text, int match_type, char *quote_pointer) quote_file_name(char *text, int match_type, char *quote_pointer)
{ {
char *s; char *s;
......
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