ODBC/SQL: Fix usage of SQLGetData

SQLGetData was called with a nullptr for the output buffer to determine
the buffer length which is not allowed. Fix it by passing a valid buffer
with a buffer size of 0.

Fixes: QTBUG-70362
Fixes: QTBUG-110803
Change-Id: I4d547383100714901a4e2ca3b4777326cfab12fe
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Christian Ehrlicher 2023-02-03 22:20:28 +01:00
parent a53fe60a92
commit efce30bb43
1 changed files with 50 additions and 95 deletions

View File

@ -370,112 +370,67 @@ static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true)
return QMetaType(type);
}
static QVariant qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode)
template <typename CT>
static QVariant getStringDataImpl(SQLHANDLE hStmt, SQLUSMALLINT column, qsizetype colSize, SQLSMALLINT targetType)
{
QString fieldVal;
SQLRETURN r = SQL_ERROR;
SQLLEN lengthIndicator = 0;
QVarLengthArray<CT> buf(colSize);
while (true) {
r = SQLGetData(hStmt,
column + 1,
targetType,
SQLPOINTER(buf.data()), SQLINTEGER(buf.size() * sizeof(CT)),
&lengthIndicator);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
if (lengthIndicator == SQL_NULL_DATA) {
return {};
}
// starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned
// instead of the length (which sometimes was wrong in older versions)
// see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx
// if length indicator equals SQL_NO_TOTAL, indicating that
// more data can be fetched, but size not known, collect data
// and fetch next block
if (lengthIndicator == SQL_NO_TOTAL) {
fieldVal += fromSQLTCHAR<QVarLengthArray<CT>, sizeof(CT)>(buf, buf.size());
continue;
}
// if SQL_SUCCESS_WITH_INFO is returned, indicating that
// more data can be fetched, the length indicator does NOT
// contain the number of bytes returned - it contains the
// total number of bytes that CAN be fetched
const qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO)
? buf.size()
: qsizetype(lengthIndicator / sizeof(CT));
fieldVal += fromSQLTCHAR<QVarLengthArray<CT>, sizeof(CT)>(buf, rSize);
// lengthIndicator does not contain the termination character
if (lengthIndicator < SQLLEN((buf.size() - 1) * sizeof(CT))) {
// workaround for Drivermanagers that don't return SQL_NO_DATA
break;
}
} else if (r == SQL_NO_DATA) {
break;
} else {
qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
return {};
}
}
return fieldVal;
}
// NB! colSize must be a multiple of 2 for unicode enabled DBs
static QVariant qGetStringData(SQLHANDLE hStmt, SQLUSMALLINT column, int colSize, bool unicode)
{
if (colSize <= 0) {
colSize = 256;
colSize = 256; // default Prealloc size of QVarLengthArray
} else if (colSize > 65536) { // limit buffer size to 64 KB
colSize = 65536;
} else {
colSize++; // make sure there is room for more than the 0 termination
}
if (unicode) {
r = SQLGetData(hStmt,
column+1,
SQL_C_TCHAR,
NULL,
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
colSize = int(lengthIndicator / sizeof(SQLTCHAR) + 1);
QVarLengthArray<SQLTCHAR> buf(colSize);
while (true) {
r = SQLGetData(hStmt,
column+1,
SQL_C_TCHAR,
(SQLPOINTER)buf.data(),
colSize*sizeof(SQLTCHAR),
&lengthIndicator);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
if (lengthIndicator == SQL_NULL_DATA) {
return {};
}
// starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned
// instead of the length (which sometimes was wrong in older versions)
// see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx
// if length indicator equals SQL_NO_TOTAL, indicating that
// more data can be fetched, but size not known, collect data
// and fetch next block
if (lengthIndicator == SQL_NO_TOTAL) {
fieldVal += fromSQLTCHAR(buf, colSize);
continue;
}
// if SQL_SUCCESS_WITH_INFO is returned, indicating that
// more data can be fetched, the length indicator does NOT
// contain the number of bytes returned - it contains the
// total number of bytes that CAN be fetched
int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : int(lengthIndicator / sizeof(SQLTCHAR));
fieldVal += fromSQLTCHAR(buf, rSize);
if (lengthIndicator < SQLLEN(colSize*sizeof(SQLTCHAR))) {
// workaround for Drivermanagers that don't return SQL_NO_DATA
break;
}
} else if (r == SQL_NO_DATA) {
break;
} else {
qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
return {};
}
}
} else {
r = SQLGetData(hStmt,
column+1,
SQL_C_CHAR,
NULL,
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
colSize = lengthIndicator + 1;
QVarLengthArray<SQLCHAR> buf(colSize);
while (true) {
r = SQLGetData(hStmt,
column+1,
SQL_C_CHAR,
(SQLPOINTER)buf.data(),
colSize,
&lengthIndicator);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
return {};
}
// if SQL_SUCCESS_WITH_INFO is returned, indicating that
// more data can be fetched, the length indicator does NOT
// contain the number of bytes returned - it contains the
// total number of bytes that CAN be fetched
qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator;
// Remove any trailing \0 as some drivers misguidedly append one
int realsize = qMin(rSize, buf.size());
if (realsize > 0 && buf[realsize - 1] == 0)
realsize--;
fieldVal += QString::fromUtf8(reinterpret_cast<const char *>(buf.constData()), realsize);
if (lengthIndicator < SQLLEN(colSize)) {
// workaround for Drivermanagers that don't return SQL_NO_DATA
break;
}
} else if (r == SQL_NO_DATA) {
break;
} else {
qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
return {};
}
}
}
return fieldVal;
return unicode ? getStringDataImpl<SQLTCHAR>(hStmt, column, colSize, SQL_C_TCHAR)
: getStringDataImpl<SQLCHAR>(hStmt, column, colSize, SQL_C_CHAR);
}
static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)