📄 odbc.c
字号:
char errorString[ MAX_ERRMSG_SIZE + 8 ];
SQLHANDLE handle;
SQLUINTEGER dwNativeError = 0;
SQLSMALLINT handleType, errorStringLength;
SQLRETURN sqlStatus;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );
REQUIRES( errorLevel == SQL_ERRLVL_STMT || \
errorLevel == SQL_ERRLVL_DBC || \
errorLevel == SQL_ERRLVL_ENV );
REQUIRES( cryptStatusError( defaultStatus ) );
/* Set up the handle information for the diagnostic information that we
want to retrieve */
switch( errorLevel )
{
case SQL_ERRLVL_STMT:
handleType = SQL_HANDLE_STMT;
handle = hStmt;
break;
case SQL_ERRLVL_DBC:
handleType = SQL_HANDLE_DBC;
handle = dbmsInfo->hDbc;
break;
case SQL_ERRLVL_ENV:
handleType = SQL_HANDLE_ENV;
handle = dbmsInfo->hEnv;
break;
default:
retIntError();
}
/* Get the ODBC error information at the most detailed level that we can
manage */
sqlStatus = SQLGetDiagRec( handleType, handle, 1, szSqlState,
&dwNativeError, errorString, MAX_ERRMSG_SIZE,
&errorStringLength );
if( !sqlStatusOK( sqlStatus ) && errorLevel == SQL_ERRLVL_STMT )
{
/* If we couldn't get information at the statement-handle level, try
again at the connection handle level */
sqlStatus = SQLGetDiagRec( SQL_HANDLE_DBC, dbmsInfo->hDbc, 1,
szSqlState, &dwNativeError,
errorString, MAX_ERRMSG_SIZE,
&errorStringLength );
}
if( !sqlStatusOK( sqlStatus ) )
{
assert( DEBUG_WARN ); /* Catch this if it ever occurs */
setErrorString( errorInfo,
"Couldn't get error information from database "
"backend", 52 );
return( CRYPT_ERROR_READ );
}
/* In some (rare) cases SQLGetDiagRec() can return an empty error string
with only szSqlState set, in which case we clear the error string */
if( errorStringLength > 0 )
setErrorString( errorInfo, errorString, errorStringLength );
else
clearErrorString( errorInfo );
/* Check for a not-found error status. We can also get an sqlStatus of
SQL_NO_DATA with SQLSTATE set to "00000" and the error message string
empty in some cases, in which case we provide our own error string */
if( !strCompare( szSqlState, "S0002", 5 ) || /* ODBC 2.x */
!strCompare( szSqlState, "42S02", 5 ) || /* ODBC 3.x */
( !strCompare( szSqlState, "00000", 5 ) && \
sqlStatus == SQL_NO_DATA ) )
{
/* Make sure that the caller gets a sensible error message if they
try to examine the extended error information */
if( errorStringLength <= 0 )
setErrorString( errorInfo, "No data found", 13 );
return( CRYPT_ERROR_NOTFOUND );
}
/* When we're trying to create a new keyset, there may already be one
present giving an S0001/42S01 (table already exists) or S0011/42S11
(index already exists) error . We could check for the table by doing
a dummy read, but it's easier to just try the update anyway and
convert the error code to the correct value here if there's a
problem */
if( !strCompare( szSqlState, "S0001", 5 ) ||
!strCompare( szSqlState, "S0011", 5 ) || /* ODBC 2.x */
!strCompare( szSqlState, "42S01", 5 ) ||
!strCompare( szSqlState, "42S11", 5 ) ) /* ODBX 3.x */
return( CRYPT_ERROR_DUPLICATE );
/* This one is a bit odd: An integrity constraint violation occurred,
which means (among other things) that an attempt was made to write a
duplicate value to a column constrained to contain unique values. It
can also include things like writing a NULL value to a column
constrained to be NOT NULL, but this wouldn't normally happen so we
can convert this one to a duplicate data error */
if( !strCompare( szSqlState, "23000", 5 ) )
return( CRYPT_ERROR_DUPLICATE );
return( defaultStatus );
}
/* Rewrite the SQL query to handle the back-end specific blob and date type,
and to work around problems with some back-end types (and we're
specifically talking Access here) */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 6 ) ) \
static int rewriteString( OUT_BUFFER( stringMaxLength, \
*stringLength ) char *string,
IN_LENGTH_SHORT const int stringMaxLength,
OUT_LENGTH_SHORT_Z int *stringLength,
IN_LENGTH_SHORT const int subStringLength,
IN_LENGTH_SHORT const int origStringLength,
IN_BUFFER( newSubStringLength ) \
const char *newSubString,
IN_LENGTH_SHORT const int newSubStringLength )
{
const int remainder = origStringLength - subStringLength;
const int newStringLength = newSubStringLength + remainder;
assert( isWritePtr( string, stringMaxLength ) );
assert( isReadPtr( newSubString, newSubStringLength ) );
REQUIRES( stringMaxLength > 0 && stringMaxLength < MAX_INTLENGTH_SHORT );
REQUIRES( subStringLength > 0 && \
subStringLength < origStringLength && \
subStringLength < MAX_INTLENGTH_SHORT );
REQUIRES( origStringLength > 0 && \
origStringLength <= stringMaxLength && \
origStringLength < MAX_INTLENGTH_SHORT );
/* Clear return value */
*stringLength = 0;
/* Make sure that the parameters are in order and there's room to
rewrite the string */
ENSURES( remainder > 0 && newStringLength > 0 && \
newStringLength < stringMaxLength );
/* Open/close up a gap and replace the existing substring with the new
one:
origStringLength
|<----------- string -------------->|
+---------------+-------------------+-----------+
|///////////////|...................| |
+---------------+-------------------+-----------+
|<- subString ->| |
stringMaxLength */
memmove( string + newSubStringLength, string + subStringLength,
remainder );
memcpy( string, newSubString, newSubStringLength );
*stringLength = newSubStringLength - subStringLength;
return( CRYPT_OK );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4, 5 ) ) \
static int convertQuery( INOUT DBMS_STATE_INFO *dbmsInfo,
OUT_BUFFER( queryMaxLen, queryLength ) char *query,
IN_LENGTH_SHORT_MIN( 16 ) const int queryMaxLen,
OUT_LENGTH_SHORT_Z int *queryLength,
IN_BUFFER( commandLength ) const char *command,
IN_LENGTH_SHORT const int commandLength )
{
int currentLength = commandLength;
int offset, length, status;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );
assert( isWritePtr( query, queryMaxLen ) );
assert( isReadPtr( command, commandLength ) );
REQUIRES( queryMaxLen >= 16 && queryMaxLen < MAX_INTLENGTH_SHORT );
REQUIRES( commandLength > 0 && commandLength < queryMaxLen && \
commandLength < MAX_INTLENGTH_SHORT );
/* Clear return value */
*queryLength = 0;
/* Copy the SQL command across to the query buffer */
memcpy( query, command, commandLength );
/* If it's a CREATE TABLE command rewrite the blob and date types to the
appropriate values for the database back-end */
if( !strCompare( command, "CREATE TABLE", 12 ) )
{
offset = strFindStr( query, currentLength, " BLOB", 5 );
if( offset > 0 )
{
offset++; /* Skip space before blob name */
status = rewriteString( query + offset, queryMaxLen - offset,
&length, 4, currentLength - offset,
dbmsInfo->blobName,
dbmsInfo->blobNameLength );
if( cryptStatusError( status ) )
return( status );
currentLength += length;
}
offset = strFindStr( query, currentLength, " DATETIME", 9 );
if( offset > 0 && \
!( dbmsInfo->dateTimeNameLength == 8 && \
!strCompare( dbmsInfo->dateTimeName, "DATETIME", 8 ) ) )
{
offset++; /* Skip space before date/time name */
status = rewriteString( query + offset, queryMaxLen - offset,
&length, 8, currentLength - offset,
dbmsInfo->dateTimeName,
dbmsInfo->dateTimeNameLength );
if( cryptStatusError( status ) )
return( status );
currentLength += length;
}
}
/* If it's not one of the back-ends that require special-case handling,
we're done */
switch( dbmsInfo->backendType )
{
case DBMS_ACCESS:
/* If it's not a SELECT/DELETE with wildcards used, there's
nothing to do */
if( ( strCompare( query, "SELECT", 6 ) && \
strCompare( query, "DELETE", 6 ) ) || \
strFindStr( query, currentLength, " LIKE ", 6 ) <= 7 )
{
*queryLength = currentLength;
return( CRYPT_OK );
}
break;
case DBMS_INTERBASE:
/* If it's not a CREATE TABLE/INSERT/DELETE/SELECT with the
'type' column involved, there's nothing to do */
if( strCompare( query, "CREATE TABLE", 12 ) && \
strCompare( query, "SELECT", 6 ) && \
strCompare( query, "DELETE", 6 ) && \
strCompare( query, "INSERT", 6 ) )
{
*queryLength = currentLength;
return( CRYPT_OK );
}
if( strFindStr( query, currentLength, " type ", 6 ) <= 7 )
{
*queryLength = currentLength;
return( CRYPT_OK );
}
break;
default:
/* Currently no other back-ends need special handling */
*queryLength = currentLength;
return( CRYPT_OK );
}
/* Unlike everything else in the known universe Access uses * and ?
instead of the standard SQL wildcards so if we find a LIKE ... %
we rewrite the % as a * */
if( ( dbmsInfo->backendType == DBMS_ACCESS ) && \
( offset = strFindStr( query, currentLength, " LIKE ", 6 ) ) > 0 )
{
int i;
/* Search up to 6 characters ahead for a wildcard and replace it
with the one needed by Access if we find it. We search 6 chars
ahead because the higher-level SQL code uses expressions like
"SELECT .... WHERE foo LIKE '--%'", which is 5 chars plus one as
a safety margin */
for( i = offset + 7; i < offset + 11 && i < currentLength; i++ )
{
if( query[ i ] == '%' )
query[ i ] = '*';
}
}
/* Interbase treats TYPE as a reserved word so we can't use 'type' for a
column name */
if( ( dbmsInfo->backendType == DBMS_INTERBASE ) && \
( offset = strFindStr( query, currentLength, " type ", 6 ) ) > 0 )
{
offset++; /* Skip space before type name */
status = rewriteString( query + offset, queryMaxLen - offset,
&length, 4, currentLength - offset,
"ctype", 5 );
if( cryptStatusError( status ) )
return( status );
currentLength += length;
}
*queryLength = currentLength;
return( CRYPT_OK );
}
/* Bind parameters for a query/update. The caller has to supply the bound
data storage since it still has to exist later on when the query is
executed */
CHECK_RETVAL STDC_NONNULL_ARG( ( 2, 3, 4 ) ) \
static int bindParameters( const SQLHSTMT hStmt,
IN_ARRAY( BOUND_DATA_MAXITEMS ) \
const BOUND_DATA *boundData,
INOUT BOUND_DATA_STATE *boundDataState,
INOUT DBMS_STATE_INFO *dbmsInfo )
{
SQLUSMALLINT paramNo = 1;
int i;
assert( isReadPtr( boundData, \
sizeof( BOUND_DATA ) * BOUND_DATA_MAXITEMS ) );
assert( isWritePtr( boundDataState, sizeof( BOUND_DATA_STATE ) ) );
assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );
/* Bind in any necessary parameters to the hStmt */
for( i = 0; boundData[ i ].type != BOUND_DATA_NONE && \
i < BOUND_DATA_MAXITEMS; i++ )
{
const BOUND_DATA *boundDataPtr = &boundData[ i ];
SQLSMALLINT valueType, parameterType;
SQLRETURN sqlStatus;
if( boundDataPtr->type == BOUND_DATA_TIME )
{
SQL_TIMESTAMP_STRUCT *timestampStorage = \
&boundDataState->timestampStorage;
struct tm timeInfo, *timeInfoPtr = &timeInfo;
REQUIRES( boundDataPtr->dataLength == sizeof( time_t ) );
/* Sanity check the input parameters */
timeInfoPtr = gmTime_s( boundDataPtr->data, timeInfoPtr );
if( timeInfoPtr == NULL )
return( CRYPT_ERROR_BADDATA );
/* Bind in the date. The handling of the ColumnSize parameter
is ugly, this value should be implicit in the underlying data
type but a small number of back-ends (e.g. ones derived from
the Sybase 4.2 code line, which includes the current Sybase
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -