⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 odbc.c

📁 cryptlib安全工具包
💻 C
📖 第 1 页 / 共 5 页
字号:
	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 + -