/*++ Copyright (c) 1995 Microsoft Corporation Module Name: odbcreq.cxx Abstract: ODBC Request class used for ODBC requests from a query file Author: John Ludeman (johnl) 22-Feb-1995 Revision History: MuraliK 25-Aug-1995 Fixed a heap corruption problem Phillich 24-Jan-1996 Fixed nested Ifs problem --*/ #include #include #include "dbgutil.h" // // System include files. // #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #ifdef __cplusplus } // extern "C" #include #include #include #include #include #endif // __cplusplus #ifdef __cplusplus extern "C" { #endif // __cplusplus #ifdef __cplusplus } // extern "C" #endif // __cplusplus #include #include #include #include #include // // Globals // extern BOOL g_fIsSystemDBCS; // Is this system DBCS? // // Local Function Prototypes // BOOL DoSynchronousReadFile( IN HANDLE hFile, IN PCHAR Buffer, IN DWORD nBuffer, OUT PDWORD nRead, IN LPOVERLAPPED Overlapped ); BOOL GetFileData(IN const CHAR * pchFile, OUT BYTE * * ppbData, OUT DWORD * pcbData, IN int nCharset, IN BOOL fUseWin32Canon); // // Accumulate and output data in chunks of this size // #define OUTPUT_BUFFER_SIZE 8192 // // This is the maximum value for the expires time. It's 10 years in seconds // #define MAX_EXPIRES_TIME 0x12cc0300 // // The special tag names for marking the beginning and ending of the // special tag sections // #define BEGIN_DETAIL_TEXT "begindetail" #define END_DETAIL_TEXT "enddetail" #define IF_TEXT "if" #define ELSE_TEXT "else" #define END_IF_TEXT "endif" // // Does a case insensitive compare of a .wdg field name // #define COMP_FIELD( pchName, pchField, cch ) ((toupper(*(pchName)) == \ toupper(*(pchField))) && \ !_strnicmp( (pchName), (pchField), (cch))) // // Given a pointer to a token, skips to the next white space delimited token // #define NEXT_TOKEN( pchToken ) SkipWhite( SkipNonWhite( pchToken ) ) BOOL SetOdbcOptions( ODBC_CONNECTION * pOdbcConn, STR * pStrOptions ); BOOL BuildMultiValue( const CHAR * pchValue, STR * pstrMulti, BOOL fQuoteElements ); const CHAR * SkipNonWhite( const CHAR * pch ); const CHAR * SkipTo( const CHAR * pch, CHAR ch ); const CHAR * SkipWhite( const CHAR * pch ); ODBC_REQ::ODBC_REQ( CONST CHAR * pszQueryFile, CONST CHAR * pszParameters, DWORD csecConnPool, ODBC_REQ_FIND_SYMBOL pfnClientFindSymbol, VOID * pFindSymbolContext, int nCharset ) : _cchMaxFieldSize ( 0 ), _cMaxRecords ( 0xffffffff ), _cCurrentRecordNum ( 0 ), _cClientParams ( 0 ), _podbcstmt ( NULL ), _podbcconnPool ( NULL ), _pfnClientFindSymbol( pfnClientFindSymbol ), _pFindSymbolContext ( pFindSymbolContext ), _cbQueryFile ( 0 ), _cNestedIfs ( 0 ), _strQueryFile ( pszQueryFile ), _fDirect ( FALSE ), _fValid ( FALSE ), _pbufOut ( NULL ), _csecExpires ( 0 ), _csecExpiresAt ( 0 ), _pstrValues ( NULL ), _pcbValues ( NULL ), _cQueries ( 0 ), _csecConnPool ( csecConnPool ), _pSecDesc ( NULL ), _pstrCols ( NULL ), _nCharset ( nCharset ) { if ( _strQueryFile.IsValid() && _plParams.ParsePairs( pszParameters, FALSE, FALSE, FALSE )) { _fValid = TRUE; } _cClientParams = _plParams.GetCount(); } ODBC_REQ::~ODBC_REQ() { Close(); if ( _podbcstmt ) { delete _podbcstmt; } if ( _pbufOut ) { delete _pbufOut; } if ( _pSecDesc ) { LocalFree( _pSecDesc ); } } BOOL ODBC_REQ::OpenQueryFile( BOOL * pfAccessDenied ) { CHAR * pchQueryFile; if ( !GetFileData( _strQueryFile.QueryStr(), (BYTE **) &pchQueryFile, &_cbQueryFile, _nCharset, TRUE)) { STR strError; LPCSTR apsz[1]; apsz[0] = _strQueryFile.QueryStr(); strError.FormatString( ODBCMSG_QUERY_FILE_NOT_FOUND, apsz, HTTP_ODBC_DLL ); SetErrorText( strError.QueryStr() ); DWORD dwE = GetLastError(); if ( dwE == ERROR_ACCESS_DENIED || dwE == ERROR_LOGON_FAILURE ) *pfAccessDenied = TRUE; return FALSE; } // // CODEWORK - It is possible to avoid this copy by not modifying the // contents of the query file. Would save a buffer copy // if ( !_bufQueryFile.Resize( _cbQueryFile )) { return FALSE; } memcpy( _bufQueryFile.QueryPtr(), pchQueryFile, _cbQueryFile ); return TRUE; } BOOL ODBC_REQ::ParseAndQuery( CHAR * pszLoggedOnUser ) /*++ Routine Description: This method parses the query file and executes the SQL statement Arguments: pchLoggedOnUser - The NT user account this user is running under Return Value: TRUE if successful, FALSE on error --*/ { STACK_STR( strDatasource, 64 ); STACK_STR( strUsername, 64 ); STACK_STR( strPassword, 64 ); CHAR * pch; CHAR * pchEnd; CHAR * pszField; CHAR * pszValue; BOOL fRet; VOID * pCookie = NULL; DWORD csecPoolConnection = _csecConnPool; BOOL fRetried; // // We don't allow some security related parameters to be specified from // the client so remove those now // _plParams.RemoveEntry( "REMOTE_USER" ); _plParams.RemoveEntry( "LOGON_USER" ); _plParams.RemoveEntry( "AUTH_USER" ); // // Do a quick Scan for the DefaultParameters value to fill in the blanks // in the parameter list from the web browser // { pch = (CHAR *) _bufQueryFile.QueryPtr(); pchEnd = pch + strlen(pch); INET_PARSER Parser( (CHAR *) _bufQueryFile.QueryPtr() ); while ( (pszField = Parser.QueryToken()) < pchEnd ) { if ( COMP_FIELD( "DefaultParameters:", pszField, 18 )) { Parser.SkipTo( ':' ); Parser += 1; Parser.EatWhite(); if ( !_plParams.ParsePairs( Parser.QueryLine(), TRUE)) return FALSE; break; } Parser.NextLine(); } } // // Replace any %XXX% fields with the corresponding parameter. Note // we reassign pch in case of a pointer shift during ReplaceParams // if ( !ReplaceParams( &_bufQueryFile, &_plParams )) { return FALSE; } pch = (CHAR *) _bufQueryFile.QueryPtr(); pchEnd = pch + strlen(pch); // // Loop through the fields looking for values we recognize // { INET_PARSER Parser( pch ); while ( (pszField = Parser.QueryToken()) < pchEnd ) { // // Ignore blank lines and Ctrl-Zs // if ( !*pszField || *pszField == 0x1a) { Parser.NextLine(); continue; } Parser.SkipTo( ':' ); Parser += 1; Parser.EatWhite(); // // Ignore comment fields // if ( *pszField == '#' || *pszField == ';' ) { Parser.NextLine(); continue; } if ( COMP_FIELD( "Datasource:", pszField, 11 )) { fRet = Parser.CopyToEOL( &strDatasource ); } else if ( COMP_FIELD( "Username:", pszField, 9 )) { fRet = Parser.CopyToEOL( &strUsername ); } else if ( COMP_FIELD( "Password:", pszField, 9 )) { fRet = Parser.CopyToEOL( &strPassword ); } else if ( COMP_FIELD( "Template:", pszField, 9 )) { fRet = Parser.CopyToEOL( &_strTemplateFile ); // // Specifying a template of "Direct" means return the // first column of data as raw data to the client // if ( !_stricmp( _strTemplateFile.QueryStr(), "Direct" )) { _fDirect = TRUE; } } else if ( COMP_FIELD( "MaxFieldSize:", pszField, 13 )) { _cchMaxFieldSize = atoi( Parser.QueryPos() ); } else if ( COMP_FIELD( "MaxRecords:", pszField, 11 )) { _cMaxRecords = atoi( Parser.QueryPos() ); } else if ( COMP_FIELD( "RequiredParameters:", pszField, 12 )) { fRet = _plReqParams.ParseSimpleList( Parser.QueryLine() ); } else if ( COMP_FIELD( "Content-Type:", pszField, 13 )) { fRet = Parser.CopyToEOL( &_strContentType ); } else if ( COMP_FIELD( "DefaultParameters:", pszField, 18 )) { // // Ignore, already processed // } else if ( COMP_FIELD( "Expires:", pszField, 8 )) { // _csecExpires = min( (DWORD) atoi( Parser.QueryPos() ), // MAX_EXPIRES_TIME ); } else if ( COMP_FIELD( "ODBCOptions:", pszField, 12 )) { fRet = Parser.CopyToEOL( &_strOdbcOptions ); } else if ( COMP_FIELD( "ODBCConnection:", pszField, 15 )) { // // Is there an override to the default? // if ( !_strnicmp( Parser.QueryToken(), "Pool", 4 )) { if ( !csecPoolConnection ) { // This is bogus - if somebody has turned off connection // pooling on the vroot and enabled it in the idc, // there's no defined way to set the timeout // need to add a timeout here csecPoolConnection = 30; } } else if ( !_strnicmp( Parser.QueryToken(), "NoPool", 6 )) { csecPoolConnection = 0; } } else if ( COMP_FIELD( "SQLStatement:", pszField, 13 )) { if ( _cQueries >= MAX_QUERIES ) { STR strError; strError.FormatString( ODBCMSG_TOO_MANY_SQL_STATEMENTS, NULL, HTTP_ODBC_DLL ); SetErrorText( strError.QueryStr() ); return FALSE; } while ( TRUE ) { if ( !_strQueries[_cQueries].Append( Parser.QueryLine() ) ) return FALSE; Parser.NextLine(); // // Line continuation is signified by putting a '+' at // the beginning of the line // if ( *Parser.QueryLine() == '+' ) { if ( !_strQueries[_cQueries].Append( " " )) return FALSE; Parser += 1; } else { // // Ignore blank line // if ( !*Parser.QueryLine() && Parser.QueryLine() < pchEnd ) { continue; } break; } } _cQueries++; continue; } else if ( COMP_FIELD( IDC_FIELDNAME_CHARSET, pszField, sizeof(IDC_FIELDNAME_CHARSET)-1 )) { // // Ignore "Charset:" field // Parser.NextLine(); continue; } else if ( COMP_FIELD( "TranslationFile:", pszField, 16 )) { fRet = Parser.CopyToEOL( &_strTranslationFile ); } else { // // Unrecognized field, generate an error // STR strError; LPCSTR apsz[1]; apsz[0] = pszField; strError.FormatString( ODBCMSG_UNREC_FIELD, apsz, HTTP_ODBC_DLL ); SetErrorText( strError.QueryStr() ); fRet = FALSE; } if ( !fRet ) return FALSE; Parser.NextLine(); } } // // Make sure the Datasource and SQLStatement fields are non-empty // if ( strDatasource.IsEmpty() || !_cQueries || _strQueries[0].IsEmpty() ) { STR strError; strError.FormatString( ODBCMSG_DSN_AND_SQLSTATEMENT_REQ, NULL, HTTP_ODBC_DLL ); SetErrorText( strError.QueryStr() ); return FALSE; } // // Make sure all of the required parameters have been supplied // while ( pCookie = _plReqParams.NextPair( pCookie, &pszField, &pszValue )) { if ( !_plParams.FindValue( pszField )) { STR strError; LPCSTR apsz[1]; apsz[0] = pszField; if ( !strError.FormatString( ODBCMSG_MISSING_REQ_PARAM, apsz, HTTP_ODBC_DLL )) { return FALSE; } // // Set the error text to return the user and indicate we couldn't // continue the operation // SetErrorText( strError.QueryStr() ); return FALSE; } } // // Don't retry the connection/query if not pooling. The reason // we do the retry is to report the error that occurred (this // requires the ODBC connection object). // fRetried = csecPoolConnection == 0; RetryConnection: // // Open the database // if ( !OpenConnection( &_odbcconn, &_podbcconnPool, csecPoolConnection, strDatasource.QueryStr(), strUsername.QueryStr(), strPassword.QueryStr(), pszLoggedOnUser ) || !SetOdbcOptions( QueryOdbcConnection(), &_strOdbcOptions ) || !(_podbcstmt = QueryOdbcConnection()->AllocStatement()) || !_podbcstmt->ExecDirect( _strQueries[0].QueryStr(), _strQueries[0].QueryCCH() )) { // // Delete the pooled connection and retry the open // if ( csecPoolConnection ) { delete _podbcstmt; _podbcstmt = NULL; CloseConnection( _podbcconnPool, TRUE ); _podbcconnPool = NULL; csecPoolConnection = 0; } if ( !fRetried ) { fRetried = TRUE; goto RetryConnection; } return FALSE; } CloseConnection( _podbcconnPool, TRUE ); return TRUE; } BOOL ODBC_REQ::OutputResults( ODBC_REQ_CALLBACK pfnCallback, PVOID pvContext, STR * pstrHeaders, ODBC_REQ_HEADER pfnSendHeader, BOOL fIsAuth, BOOL * pfAccessDenied ) /*++ Routine Description: This method reads the template file and does the necessary result set column substitution Arguments: pfnCallback - Send callback function pvContext - Context for send callback Return Value: TRUE if successful, FALSE on error --*/ { DWORD cbOut; DWORD cbFile, cbHigh; DWORD BytesRead; DWORD cbToSend; BOOL fLastRow = FALSE; const CHAR * pchStartDetail; const CHAR * pchIn; const CHAR * pchEOF; const CHAR * pchBOF; CHAR * pchTag; const CHAR * pchValue; DWORD cbValue; enum TAG_TYPE TagType; DWORD err; BOOL fTriedRelative = FALSE; BOOL fExpr; STR strError; const CHAR * CharacterMap[256]; BOOL fIsSelect; BOOL fMoreResults; BOOL fHaveResultSet = FALSE; DWORD iQuery = 1; // // Set up the first buffer in the output chain // if ( !_pbufOut ) { _pbufOut = new BUFFER_CHAIN_ITEM( OUTPUT_BUFFER_SIZE ); if ( !_pbufOut || !_pbufOut->QueryPtr() ) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return FALSE; } } if ( !_fDirect ) { CHAR * pchLastSlash; STACK_STR( str, MAX_PATH); TryAgain: // // Open and read the template file (automatically zero terminated) // if ( !GetFileData( (fTriedRelative ? str.QueryStr() : _strTemplateFile.QueryStr()), // _hToken, (BYTE **)&pchBOF, &BytesRead, _nCharset, TRUE )) { // // If the open fails with a not found error, then make the // template file relative to the query file and try again // if ( fTriedRelative || ((GetLastError() != ERROR_FILE_NOT_FOUND) && (GetLastError() != ERROR_PATH_NOT_FOUND)) || !str.Copy( _strQueryFile ) ) { STR strError; LPCSTR apsz[1]; DWORD dwE = GetLastError(); apsz[0] = _strTemplateFile.QueryStr(); strError.FormatString( ODBCMSG_QUERY_FILE_NOT_FOUND, apsz, HTTP_ODBC_DLL ); SetErrorText( strError.QueryStr()); if ( (dwE == ERROR_ACCESS_DENIED || dwE == ERROR_LOGON_FAILURE) ) { *pfAccessDenied = TRUE; return FALSE; } if ( !pfnSendHeader( pvContext, "500 IDC Query Error", pstrHeaders->QueryStr() ) ) return FALSE; goto ErrorExit; } pchLastSlash = (PCHAR)_mbsrchr( (PUCHAR)str.QueryStr(), '\\' ); if ( !pchLastSlash ) { SetLastError( ERROR_FILE_NOT_FOUND ); return FALSE; } str.SetLen( DIFF(pchLastSlash - str.QueryStr()) + 1 ); if ( !str.Append( _strTemplateFile )) { return FALSE; } fTriedRelative = TRUE; goto TryAgain; } else { // // Update our template file path if it changed // if ( fTriedRelative ) { if ( !_strTemplateFile.Copy( str )) return FALSE; } } } // // Open the translation file if one was specified // if ( !_strTranslationFile.IsEmpty() ) { // CACHE_FILE_INFO cfiTranslationFile; CHAR * pchLastSlash; CHAR * pchTranslationFile; STACK_STR( str, MAX_PATH); BOOL fRet; VOID * pvCookie = NULL; CHAR * pchField; CHAR * pchValue; DWORD cbRead; fTriedRelative = FALSE; TranslationTryAgain: // // Open and read the template file (automatically zero terminated) // if ( !GetFileData( (fTriedRelative ? str.QueryStr() : _strTranslationFile.QueryStr()), // _hToken, (BYTE **)&pchTranslationFile, &cbRead, _nCharset, TRUE)) { // // If the open fails with a not found error, then make the // template file relative to the query file and try again // if ( fTriedRelative || (GetLastError() != ERROR_FILE_NOT_FOUND && GetLastError() != ERROR_PATH_NOT_FOUND) || !str.Copy( _strQueryFile ) ) { STR strError; LPCSTR apsz[1]; DWORD dwE = GetLastError(); apsz[0] = _strTranslationFile.QueryStr(); strError.FormatString( ODBCMSG_QUERY_FILE_NOT_FOUND, apsz, HTTP_ODBC_DLL ); SetErrorText( strError.QueryStr()); if ( (dwE == ERROR_ACCESS_DENIED || dwE == ERROR_LOGON_FAILURE) ) { *pfAccessDenied = TRUE; return FALSE; } goto ErrorExit; } pchLastSlash = (PCHAR)_mbsrchr( (PUCHAR)str.QueryStr(), '\\' ); if ( !pchLastSlash ) { SetLastError( ERROR_FILE_NOT_FOUND ); return FALSE; } str.SetLen( DIFF(pchLastSlash - str.QueryStr()) + 1 ); if ( !str.Append( _strTranslationFile )) { return FALSE; } fTriedRelative = TRUE; goto TranslationTryAgain; } else { // // Update our template file path if it changed // if ( fTriedRelative ) { if ( !_strTranslationFile.Copy( str )) return FALSE; } } fRet = _plTransList.ParsePairs( pchTranslationFile, FALSE, TRUE, FALSE ); /* TCP_REQUIRE( CheckInCachedFile( _pCache, &cfiTranslationFile )); */ if ( !fRet ) return FALSE; // // Build the character map // memset( CharacterMap, 0, sizeof(CharacterMap) ); while ( pvCookie = _plTransList.NextPair( pvCookie, &pchField, &pchValue )) { CharacterMap[ (BYTE) *pchField] = pchValue; } } // // We've already performed the first query at this point // NextResultSet: // // Get the list of column names in the initial result set. The initial // set must be initialized for compatibility with previous versions of // IDC (i.e., column variables can be referenced outside the detail // section). // if ( !_podbcstmt->QueryColNames( &_pstrCols, &_cCols, _cchMaxFieldSize, &fHaveResultSet )) { return FALSE; } if ( !fHaveResultSet ) { // // Check to see if there are anymore result sets for this query // if ( !_podbcstmt->MoreResults( &fMoreResults ) ) { return FALSE; } if ( fMoreResults ) { goto NextResultSet; } else if ( iQuery < _cQueries ) { // // If there are no more result sets, see if there // are more queries. Note calling SQLMoreResults // will discard this result set // if ( !_podbcstmt->ExecDirect( _strQueries[iQuery].QueryStr(), _strQueries[iQuery].QueryCCH() )) { return FALSE; } iQuery++; goto NextResultSet; } } // // Get the first row of values // if ( fHaveResultSet && !NextRow( &fLastRow )) { // // Some SQL statements don't generate any rows (i.e., // insert, delete etc.). So don't bail if there's a column in // the result set // if ( !_cCols ) return FALSE; } // Send reply header if ( !pfnSendHeader( pvContext, "200 OK", pstrHeaders->QueryStr() ) ) return FALSE; // // Copy the template to the output buffer while scanning for column // fields that need to be replaced // #define SEND_DATA( pchData, cbData ) SendData( pfnCallback, \ pvContext, \ (pchData), \ (DWORD)(cbData), \ &_pbufOut, \ &cbOut ) #define SEND_DATA_CHECK_ESC( pchData, cbData ) \ ((TagType == TAG_TYPE_VALUE_TO_ESCAPE) \ ? SendEscapedData( pfnCallback, \ pvContext, \ pchData, \ (DWORD)(cbData), \ &cbOut ) \ : SEND_DATA( pchData, \ (DWORD)(cbData) ) ) cbOut = 0; pchStartDetail = NULL; pchIn = pchBOF; pchEOF = pchBOF + BytesRead; while ( pchIn < pchEOF ) { // // Look for the start of a "