* decimal point from '.' to the correct decimal separator of the current
* locale.
*/
-static void set_server_decimal_point(char *num)
+static void set_server_decimal_point(char *num, SQLLEN len)
{
char current_decimal_point = get_current_decimal_point();
char *str;
+ SQLLEN i;
if ('.' == current_decimal_point)
return;
+ i = 0;
for (str = num; '\0' != *str; str++)
{
if (*str == current_decimal_point)
*str = '.';
break;
}
+
+ if (len != SQL_NTS && i++ >= len)
+ break;
}
}
LENADDR_SHIFT(bic->used, offset), LENADDR_SHIFT(bic->indicator, offset));
}
+/*
+ * Is 'str' a valid integer literal, consisting only of ASCII characters
+ * 0-9 ?
+ *
+ * We don't check for overflow here. This is just to decide if we need to
+ * quote the value.
+ */
+static BOOL
+valid_int_literal(const char *str, SQLLEN len)
+{
+ SQLLEN i;
+
+ i = 0;
+
+ if (str[0] == '-')
+ i++;
+
+ for (; str[i] && (len == SQL_NTS || i < len); i++)
+ {
+ if (str[i] >= '0' && str[i] <= '9')
+ continue;
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
static double get_double_value(const char *str)
{
if (stricmp(str, NAN_STRING) == 0)
SFLOAT flv;
SQL_INTERVAL_STRUCT *ivstruct;
const char *ivsign;
+ BOOL final_binary_convert = FALSE;
RETCODE retval = SQL_ERROR;
*isnull = FALSE;
#endif /* WIN32 */
{
sprintf(param_string, "%.*g", PG_DOUBLE_DIGITS, dbv);
- set_server_decimal_point(param_string);
+ set_server_decimal_point(param_string, SQL_NTS);
}
#ifdef WIN32
else if (_isnan(dbv))
#endif /* WIN32 */
{
sprintf(param_string, "%.*g", PG_REAL_DIGITS, flv);
- set_server_decimal_point(param_string);
+ set_server_decimal_point(param_string, SQL_NTS);
}
#ifdef WIN32
else if (_isnan(flv))
retval = SQL_SUCCESS;
goto cleanup;
}
- if (!req_bind)
+
+ /*
+ * We now have the value we want to print in one of these three canonical
+ * formats:
+ *
+ * 1. As a string in 'buf', with length indicated by 'used' (can be
+ * SQL_NTS).
+ * 2. As a null-terminated string in 'param_string'.
+ * 3. Time-related fields in 'st'.
+ */
+
+ /*
+ * For simplicity, fold the param_string representation into 'buf'.
+ */
+ if (!buf && param_string[0])
{
- switch (param_sqltype)
- {
- case SQL_INTEGER:
- case SQL_SMALLINT:
- break;
- case SQL_CHAR:
- case SQL_VARCHAR:
- case SQL_LONGVARCHAR:
- case SQL_BINARY:
- case SQL_VARBINARY:
- case SQL_LONGVARBINARY:
-#ifdef UNICODE_SUPPORT
- case SQL_WCHAR:
- case SQL_WVARCHAR:
- case SQL_WLONGVARCHAR:
-#endif /* UNICODE_SUPPORT */
-mylog("buf=%p flag=%d\n", buf, qb->flags);
- if (buf && (qb->flags & FLGB_LITERAL_EXTENSION) != 0)
- {
- CVT_APPEND_CHAR(qb, LITERAL_EXT);
- }
- default:
- CVT_APPEND_CHAR(qb, LITERAL_QUOTE);
- add_quote = TRUE;
- break;
- }
+ buf = param_string;
+ used = SQL_NTS;
}
+
+ /*
+ * Do some further processing to create the final string we want to output.
+ * This will use the fields in 'st' to create a string if it's a time/date
+ * value, and do some other conversions.
+ */
switch (param_sqltype)
{
case SQL_CHAR:
case SQL_WLONGVARCHAR:
#endif /* UNICODE_SUPPORT */
+ /* Special handling for some column types */
switch (param_pgtype)
{
case PG_TYPE_BOOL:
- /* consider True is -1 case */
- if (NULL != buf)
+ /*
+ * consider True is -1 case.
+ *
+ * FIXME: This actually matches anything that begins
+ * with -1, like "-1234" or "-1foobar". Is that
+ * intentional?
+ */
+ if (NULL != buf && '-' == buf[0] && '1' == buf[1])
{
- if ('-' == buf[0] &&
- '1' == buf[1])
- strcpy(buf, "1");
+ buf = "1";
+ used = 1;
}
- else if ('-' == param_string[0] &&
- '1' == param_string[1])
- strcpy(param_string, "1");
break;
case PG_TYPE_FLOAT4:
case PG_TYPE_FLOAT8:
case PG_TYPE_NUMERIC:
if (NULL != buf)
- set_server_decimal_point(buf);
- else
- set_server_decimal_point(param_string);
+ set_server_decimal_point(buf, used);
break;
}
- /* it was a SQL_C_CHAR */
- if (buf)
- CVT_SPECIAL_CHARS(qb, buf, used);
- /* it was a numeric type */
- else if (param_string[0] != '\0')
- CVT_APPEND_STR(qb, param_string);
-
- /* it was date,time,timestamp -- use m,d,y,hh,mm,ss */
- else
+ if (!buf)
{
+ /* it was date,time,timestamp -- use m,d,y,hh,mm,ss */
snprintf(tmp, sizeof(tmp), "%.4d-%.2d-%.2d %.2d:%.2d:%.2d",
st.y, st.m, st.d, st.hh, st.mm, st.ss);
-
- CVT_APPEND_STR(qb, tmp);
+ buf = tmp;
+ used = SQL_NTS;
}
break;
else
sprintf(tmp, "%.4d-%.2d-%.2d", st.y, st.m, st.d);
lastadd = "::date";
- CVT_APPEND_STR(qb, tmp);
+ buf = tmp;
+ used = SQL_NTS;
break;
case SQL_TIME:
sprintf(tmp, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss);
lastadd = "::time";
- CVT_APPEND_STR(qb, tmp);
+ buf = tmp;
+ used = SQL_NTS;
break;
case SQL_TIMESTAMP:
/* Time zone stuff is unreliable */
stime2timestamp(&st, tmp, sizeof(tmp), USE_ZONE, 6);
lastadd = "::timestamp";
- CVT_APPEND_STR(qb, tmp);
-
+ buf = tmp;
+ used = SQL_NTS;
break;
case SQL_BINARY:
if (0 != (qb->flags & FLGB_BINARY_AS_POSSIBLE))
{
mylog("sending binary data leng=%d\n", used);
- CVT_APPEND_DATA(qb, buf, used);
*isbinary = TRUE;
}
else
* converted to octal
*/
mylog("SQL_VARBINARY: about to call convert_to_pgbinary, used = %d\n", used);
-
- CVT_APPEND_BINARY(qb, buf, used);
+ final_binary_convert = TRUE;
}
break;
}
*/
sprintf(param_string, "%u", lobj_oid);
lastadd = "::lo";
- CVT_APPEND_STR(qb, param_string);
-
+ buf = param_string;
+ used = SQL_NTS;
break;
/*
/* must be quoted (0 or 1 is ok to use inside the quotes) */
case SQL_REAL:
- if (buf)
- my_strcpy(param_string, sizeof(param_string), buf, used);
- set_server_decimal_point(param_string);
+ set_server_decimal_point(buf, used);
lastadd = "::float4";
- CVT_APPEND_STR(qb, param_string);
break;
case SQL_FLOAT:
case SQL_DOUBLE:
- if (buf)
- my_strcpy(param_string, sizeof(param_string), buf, used);
- set_server_decimal_point(param_string);
+ set_server_decimal_point(buf, used);
lastadd = "::float8";
- CVT_APPEND_STR(qb, param_string);
break;
case SQL_NUMERIC:
- if (buf)
- {
- my_strcpy(cbuf, sizeof(cbuf), buf, used);
- }
- else
- sprintf(cbuf, "%s", param_string);
- CVT_APPEND_STR(qb, cbuf);
break;
default: /* a numeric type or SQL_BIT */
+ break;
+ }
- if (buf)
- {
- switch (used)
- {
- case SQL_NULL_DATA:
- break;
- case SQL_NTS:
- CVT_APPEND_STR(qb, buf);
- break;
- default:
- CVT_APPEND_DATA(qb, buf, used);
- }
- }
- else
- CVT_APPEND_STR(qb, param_string);
+ /*
+ * Ok, we now have the final string representation in 'buf', length 'used'.
+ *
+ * Do we need to escape it?
+ */
+ if (!req_bind)
+ {
+ switch (param_sqltype)
+ {
+ case SQL_INTEGER:
+ case SQL_SMALLINT:
+ if (valid_int_literal(buf, used))
+ break;
+ /* fall through */
+ case SQL_CHAR:
+ case SQL_VARCHAR:
+ case SQL_LONGVARCHAR:
+ case SQL_BINARY:
+ case SQL_VARBINARY:
+ case SQL_LONGVARBINARY:
+#ifdef UNICODE_SUPPORT
+ case SQL_WCHAR:
+ case SQL_WVARCHAR:
+ case SQL_WLONGVARCHAR:
+#endif /* UNICODE_SUPPORT */
+mylog("buf=%p flag=%d\n", buf, qb->flags);
+ default:
+ if ((qb->flags & FLGB_LITERAL_EXTENSION) != 0)
+ CVT_APPEND_CHAR(qb, LITERAL_EXT);
+ CVT_APPEND_CHAR(qb, LITERAL_QUOTE);
+ add_quote = TRUE;
+ break;
+ }
+ }
- break;
+ if (used == SQL_NTS)
+ used = strlen(buf);
+ if (add_quote)
+ {
+ if (final_binary_convert)
+ CVT_APPEND_BINARY(qb, buf, used);
+ else
+ CVT_SPECIAL_CHARS(qb, buf, used);
}
+ else
+ CVT_APPEND_DATA(qb, buf, used);
+
if (add_quote)
{
CVT_APPEND_CHAR(qb, LITERAL_QUOTE);
--- /dev/null
+/*
+ * Test conversion of parameter values from C to SQL datatypes.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "common.h"
+
+static void test_convert(const char *sql, SQLUSMALLINT c_type,
+ SQLSMALLINT sql_type, char *value);
+
+static HSTMT hstmt = SQL_NULL_HSTMT;
+
+int main(int argc, char **argv)
+{
+ SQLRETURN rc;
+
+ test_connect();
+
+ rc = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);
+ if (!SQL_SUCCEEDED(rc))
+ {
+ print_diag("failed to allocate stmt handle", SQL_HANDLE_DBC, conn);
+ exit(1);
+ }
+
+ /*** Test proper escaping of parameters ***/
+ printf("\nTesting conversions...\n");
+
+ test_convert("SELECT 1 > ?", SQL_C_CHAR, SQL_INTEGER, "2");
+ test_convert("SELECT 1 > ?", SQL_C_CHAR, SQL_INTEGER, "-2");
+ test_convert("SELECT 2.2 > ?", SQL_C_CHAR, SQL_FLOAT, "2.3");
+ test_convert("SELECT 3.3 > ?", SQL_C_CHAR, SQL_DOUBLE, "3.01");
+ test_convert("SELECT 1 > ?", SQL_C_CHAR, SQL_CHAR, "5 escapes: \\ and '");
+
+ printf("\nTesting conversions with invalid values...\n");
+
+ test_convert("SELECT 2 > ?", SQL_C_CHAR, SQL_INTEGER, "2, 'injected, BAD!'");
+ test_convert("SELECT 1.3 > ?", SQL_C_CHAR, SQL_FLOAT, "3', 'injected, BAD!', '1");
+ test_convert("SELECT 1.4 > ?", SQL_C_CHAR, SQL_FLOAT, "4 \\'bad', '1");
+ test_convert("SELECT 1 > ?", SQL_C_CHAR, SQL_INTEGER, "99999999999999999999999");
+
+ /* Clean up */
+ test_disconnect();
+
+ return 0;
+}
+
+/*
+ * Execute a query with one parameter, with given C and SQL types. Print
+ * error or result.
+ */
+static void
+test_convert(const char *sql, SQLUSMALLINT c_type, SQLSMALLINT sql_type,
+ char *value)
+{
+ SQLRETURN rc;
+ SQLLEN cbParam = SQL_NTS;
+ int failed = 0;
+
+ /* a query with an SQL_INTEGER param. */
+ rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,
+ c_type, /* value type */
+ sql_type, /* param type */
+ 20, /* column size */
+ 0, /* dec digits */
+ value, /* param value ptr */
+ 0, /* buffer len */
+ &cbParam /* StrLen_or_IndPtr */);
+ CHECK_STMT_RESULT(rc, "SQLBindParameter failed", hstmt);
+
+ rc = SQLExecDirect(hstmt, (SQLCHAR *) sql, SQL_NTS);
+ if (!SQL_SUCCEEDED(rc))
+ {
+ print_diag("SQLExecDirect failed", SQL_HANDLE_STMT, hstmt);
+ failed = 1;
+ }
+ else
+ print_result(hstmt);
+
+ rc = SQLFreeStmt(hstmt, SQL_CLOSE);
+ CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt);
+
+ /*
+ * In error_on_rollback=0 mode, we don't currently recover from the error.
+ * I think that's a bug in the driver, but meanwhile, let's just force
+ * a rollback manually
+ */
+ if (failed)
+ {
+ rc = SQLExecDirect(hstmt, (SQLCHAR *) "ROLLBACK /* clean up after failed test */", SQL_NTS);
+ CHECK_STMT_RESULT(rc, "SQLExecDirect(ROLLBACK) failed", hstmt);
+ }
+}
+