*** ./command.c.orig 2010-01-02 17:57:59.000000000 +0100 --- ./command.c 2010-01-22 13:14:28.325976489 +0100 *************** *** 938,944 **** { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); ! if (!opt0) { /* list all variables */ --- 938,944 ---- { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); ! if (!opt0) { /* list all variables */ *************** *** 947,984 **** } else { ! /* ! * Set variable to the concatenation of the arguments. ! */ ! char *newval; ! char *opt; ! ! opt = psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, false); ! newval = pg_strdup(opt ? opt : ""); ! free(opt); ! ! while ((opt = psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, false))) { ! newval = realloc(newval, strlen(newval) + strlen(opt) + 1); ! if (!newval) { ! psql_error("out of memory\n"); ! exit(EXIT_FAILURE); } - strcat(newval, opt); - free(opt); } ! ! if (!SetVariable(pset.vars, opt0, newval)) ! { ! psql_error("\\%s: error\n", cmd); success = false; ! } ! free(newval); } - free(opt0); } /* \t -- turn off headers and row count */ --- 947,1009 ---- } else { ! if (psql_scan_is_valid(scan_state)) { ! /* ! * Set variable to the concatenation of the arguments. Don't allow add variable, ! * when scanning isn't valid - these check are more strict, we protect content of ! * variables against to invalid value. ! */ ! char *newval; ! char *opt; ! ! opt = psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, false); ! ! if (psql_scan_is_valid(scan_state)) ! { ! newval = pg_strdup(opt ? opt : ""); ! free(opt); ! ! while ((opt = psql_scan_slash_option(scan_state, ! OT_NORMAL, NULL, false))) ! { ! if (psql_scan_is_valid(scan_state)) ! { ! newval = realloc(newval, strlen(newval) + strlen(opt) + 1); ! if (!newval) ! { ! psql_error("out of memory\n"); ! exit(EXIT_FAILURE); ! } ! strcat(newval, opt); ! free(opt); ! } ! else ! { ! success = false; ! free(opt); ! break; ! } ! } ! ! if (success && !SetVariable(pset.vars, opt0, newval)) ! { ! psql_error("\\%s: error\n", cmd); ! success = false; ! } ! free(newval); ! } ! else { ! success = false; ! free(opt); } } ! else success = false; ! free(opt0); } } /* \t -- turn off headers and row count */ *************** *** 1163,1171 **** else status = PSQL_CMD_UNKNOWN; ! if (!success) status = PSQL_CMD_ERROR; ! return status; } --- 1188,1196 ---- else status = PSQL_CMD_UNKNOWN; ! if (!success || !psql_scan_is_valid(scan_state)) status = PSQL_CMD_ERROR; ! return status; } *** ./mainloop.c.orig 2010-01-02 17:57:59.000000000 +0100 --- ./mainloop.c 2010-01-22 12:02:49.273610342 +0100 *************** *** 253,260 **** line_saved_in_history = true; } ! /* execute query */ ! success = SendQuery(query_buf->data); slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; /* transfer query to previous_buf by pointer-swapping */ --- 253,270 ---- line_saved_in_history = true; } ! /* ! * execute query ! * ! * When scanning isn't valid from out of memory ! * or broken chars reason do nothing. Error message ! * was displayed. So only don't send invalid commands ! */ ! if (psql_scan_is_valid(scan_state)) ! success = SendQuery(query_buf->data); ! else ! success = false; ! slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; /* transfer query to previous_buf by pointer-swapping */ *************** *** 267,273 **** resetPQExpBuffer(query_buf); added_nl_pos = -1; ! /* we need not do psql_scan_reset() here */ } else if (scan_result == PSCAN_BACKSLASH) { --- 277,283 ---- resetPQExpBuffer(query_buf); added_nl_pos = -1; ! psql_scan_reset(scan_state); } else if (scan_result == PSCAN_BACKSLASH) { *************** *** 342,347 **** --- 352,360 ---- } else if (slashCmdStatus == PSQL_CMD_TERMINATE) break; + else if (slashCmdStatus != PSQL_CMD_UNKNOWN) + /* reset parsing state - reset flag valid */ + psql_scan_reset(scan_state); } /* fall out of loop if lexer reached EOL */ *** ./psqlscan.h.orig 2010-01-02 17:57:59.000000000 +0100 --- ./psqlscan.h 2010-01-22 11:30:35.236914189 +0100 *************** *** 61,64 **** --- 61,66 ---- extern void psql_scan_slash_command_end(PsqlScanState state); + extern bool psql_scan_is_valid(PsqlScanState state); + #endif /* PSQLSCAN_H */ *** ./psqlscan.l.orig 2010-01-02 17:57:59.000000000 +0100 --- ./psqlscan.l 2010-01-22 12:03:31.210739617 +0100 *************** *** 93,98 **** --- 93,99 ---- int paren_depth; /* depth of nesting in parentheses */ int xcdepth; /* depth of nesting in slash-star comments */ char *dolqstart; /* current $foo$ quote start string */ + bool valid; /* current scan is valid */ } PsqlScanStateData; static PsqlScanState cur_state; /* current state while active */ *************** *** 119,124 **** --- 120,128 ---- static void emit(const char *txt, int len); static bool is_utf16_surrogate_first(uint32 c); + static void appendLiteral(PGconn *conn, PQExpBuffer buf, const char *str); + static void appendIdentifier(PGconn *conn, PQExpBuffer buf, const char *str); + #define ECHO emit(yytext, yyleng) %} *************** *** 706,711 **** --- 710,778 ---- ECHO; } } + + :'[A-Za-z0-9_]+' { + /* + * Possible psql variable substitution with + * literal escaping. + */ + const char *value; + + yytext[yyleng - 1] = '\0'; + value = GetVariable(pset.vars, yytext + 2); + + if (value) + { + /* It is a variable, perform substitution */ + PQExpBufferData buf; + + initPQExpBuffer(&buf); + appendLiteral(pset.db, &buf, value); + push_new_buffer(buf.data); + termPQExpBuffer(&buf); + + /* yy_scan_string already made buffer active */ + } + else + { + /* + * if the variable doesn't exist we'll copy the + * string as is + */ + ECHO; + } + } + + :\"[A-Za-z0-9_]+\" { + /* + * Possible psql variable substitution with + * identifier escaping. + */ + const char *value; + + yytext[yyleng - 1] = '\0'; + value = GetVariable(pset.vars, yytext + 2); + + if (value) + { + /* It is a variable, perform substitution */ + PQExpBufferData buf; + + initPQExpBuffer(&buf); + appendIdentifier(pset.db, &buf, value); + push_new_buffer(buf.data); + termPQExpBuffer(&buf); + /* yy_scan_string already made buffer active */ + } + else + { + /* + * if the variable doesn't exist we'll copy the + * string as is + */ + ECHO; + } + } /* * Back to backend-compatible rules. *************** *** 927,932 **** --- 994,1053 ---- return LEXRES_OK; } + + :'[A-Za-z0-9_]+' { + /* Possible psql variable substitution with literal escaping */ + if (option_type == OT_VERBATIM) + ECHO; + else + { + const char *value; + + yytext[yyleng - 1] = '\0'; + value = GetVariable(pset.vars, yytext + 2); + + /* + * The variable value is just emitted without any + * further examination. This is consistent with the + * pre-8.0 code behavior, if not with the way that + * variables are handled outside backslash commands. + */ + if (value) + appendLiteral(pset.db, output_buf, value); + } + + *option_quote = ':'; + + return LEXRES_OK; + } + + :\"[A-Za-z0-9_]+\" { + /* Possible psql variable substitution with identifier escaping */ + if (option_type == OT_VERBATIM) + ECHO; + else + { + const char *value; + + yytext[yyleng - 1] = '\0'; + value = GetVariable(pset.vars, yytext + 2); + + /* + * The variable value is just emitted without any + * further examination. This is consistent with the + * pre-8.0 code behavior, if not with the way that + * variables are handled outside backslash commands. + */ + if (value) + appendIdentifier(pset.db, output_buf, value); + } + + *option_quote = ':'; + + return LEXRES_OK; + } + + "|" { ECHO; if (option_type == OT_FILEPIPE) *************** *** 1325,1330 **** --- 1446,1452 ---- if (state->dolqstart) free(state->dolqstart); state->dolqstart = NULL; + state->valid = true; } /* *************** *** 1341,1346 **** --- 1463,1478 ---- } /* + * Return true if lexer doesn't detect "out of memory" or + * bad multibyte characters problem. + */ + bool + psql_scan_is_valid(PsqlScanState state) + { + return state->valid; + } + + /* * Scan the command name of a psql backslash command. This should be called * after psql_scan() returns PSCAN_BACKSLASH. It is assumed that the input * has been consumed through the leading backslash. *************** *** 1740,1742 **** --- 1872,1930 ---- { return (c >= 0xD800 && c <= 0xDBFF); } + + /* + * Convert a string value to an SQL Literal and append it to + * the given buffer. + */ + static void + appendLiteral(PGconn *conn, PQExpBuffer buf, const char *str) + { + char *escaped_str; + size_t len; + + len = strlen(str); + escaped_str = PQescapeLiteral(conn, str, len); + + if (escaped_str == NULL) + { + const char *error_message = PQerrorMessage(pset.db); + + if (strlen(error_message)) + psql_error("%s", error_message); + } + else + { + /* only valid value is appended */ + appendPQExpBufferStr(buf, escaped_str); + free(escaped_str); + } + } + + /* + * Convert a string value to an SQL identifier and append it to + * the given buffer. + */ + static void + appendIdentifier(PGconn *conn, PQExpBuffer buf, const char *str) + { + char *escaped_str; + size_t len; + + len = strlen(str); + escaped_str = PQescapeIdentifier(conn, str, len); + + if (escaped_str == NULL) + { + const char *error_message = PQerrorMessage(pset.db); + + if (strlen(error_message)) + psql_error("%s", error_message); + } + else + { + /* only valid value is appended */ + appendPQExpBufferStr(buf, escaped_str); + free(escaped_str); + } + }