/*++ 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 "precomp.hxx" // // 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 .idc 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 ) ) ALLOC_CACHE_HANDLER * ODBC_REQ::sm_pachOdbcRequests; // // Globals // extern BOOL g_fIsSystemDBCS; // Is this system DBCS? // // Local Function Prototypes // HRESULT DoSynchronousReadFile( IN HANDLE hFile, IN PCHAR Buffer, IN DWORD nBuffer, OUT PDWORD nRead, IN LPOVERLAPPED Overlapped ); HRESULT GetFileData( IN const CHAR * pchFile, OUT BYTE * * ppbData, OUT DWORD * pcbData, IN int nCharset, IN BOOL fUseWin32Canon ); HRESULT SetOdbcOptions( ODBC_CONNECTION * pOdbcConn, STRA * pStrOptions ); HRESULT BuildMultiValue( const CHAR * pchValue, STRA * 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( EXTENSION_CONTROL_BLOCK * pECB, DWORD csecConnPool, int nCharset ) : _dwSignature ( ODBC_REQ_SIGNATURE ), _pECB ( pECB ), _cchMaxFieldSize ( 0 ), _cMaxRecords ( 0xffffffff ), _cCurrentRecordNum ( 0 ), _cClientParams ( 0 ), _podbcstmt ( NULL ), _podbcconnPool ( NULL ), _cbQueryFile ( 0 ), _cNestedIfs ( 0 ), _fDirect ( FALSE ), _fValid ( FALSE ), _pbufOut ( NULL ), _csecExpires ( 0 ), _csecExpiresAt ( 0 ), _pstrValues ( NULL ), _pcbValues ( NULL ), _cQueries ( 0 ), _csecConnPool ( csecConnPool ), _pSecDesc ( NULL ), _pstrCols ( NULL ), _nCharset ( nCharset ) {} ODBC_REQ::~ODBC_REQ() { DBG_ASSERT( CheckSignature() ); if ( _podbcstmt ) { delete _podbcstmt; _podbcstmt = NULL; } Close(); if ( _pbufOut ) { delete _pbufOut; _pbufOut = NULL; } if ( _pSecDesc ) { LocalFree( _pSecDesc ); _pSecDesc = NULL; } _dwSignature = ODBC_REQ_FREE_SIGNATURE; } HRESULT ODBC_REQ::Create( CONST CHAR * pszQueryFile, CONST CHAR * pszParameters ) { HRESULT hr; DBG_ASSERT( CheckSignature() ); hr = _strQueryFile.Copy( pszQueryFile ); if( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error copying QueryFile name, hr = 0x%x.\n", hr )); return hr; } hr = _plParams.ParsePairs( pszParameters, FALSE, FALSE, FALSE ); if( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error parsing param pairs, hr = 0x%x.\n", hr )); return hr; } if ( _strQueryFile.IsValid() ) { _fValid = TRUE; } _cClientParams = _plParams.GetCount(); return hr; } HRESULT ODBC_REQ::OpenQueryFile( BOOL * pfAccessDenied ) { CHAR * pchQueryFile; HRESULT hr; DBG_ASSERT( CheckSignature() ); hr = GetFileData( _strQueryFile.QueryStr(), (BYTE **) &pchQueryFile, &_cbQueryFile, _nCharset, TRUE ); if ( FAILED( hr ) ) { STRA strError; LPCSTR apsz[1]; apsz[0] = _pECB->lpszPathInfo; 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; } DBGPRINTF(( DBG_CONTEXT, "Error in GetFileData(), hr = 0x%x.\n", hr )); return hr; } // // 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 HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } memcpy( _bufQueryFile.QueryPtr(), pchQueryFile, _cbQueryFile ); return S_OK; } HRESULT 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: HRESULT --*/ { STACK_STRA( strDatasource, 64 ); STACK_STRA( strUsername, 64 ); STACK_STRA( strPassword, 64 ); CHAR * pch; CHAR * pchEnd; CHAR * pszField; CHAR * pszValue; BOOL fRet; VOID * pCookie = NULL; DWORD csecPoolConnection = _csecConnPool; BOOL fRetried; HRESULT hr; DBG_ASSERT( CheckSignature() ); // // 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); ODBC_PARSER Parser( (CHAR *) _bufQueryFile.QueryPtr() ); while ( ( pszField = Parser.QueryToken() ) < pchEnd ) { if ( COMP_FIELD( "DefaultParameters:", pszField, 18 )) { Parser.SkipTo( ':' ); Parser += 1; Parser.EatWhite(); hr = _plParams.ParsePairs( Parser.QueryLine(), TRUE ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error parsing param pairs, hr = 0x%x.\n", hr )); return hr; } break; } Parser.NextLine(); } } // // Replace any %XXX% fields with the corresponding parameter. // Note we reassign pch in case of a pointer shift during // ReplaceParams // hr = ReplaceParams( &_bufQueryFile, &_plParams ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error in ReplaceParams(), hr = 0x%x.\n", hr )); return hr; } pch = (CHAR *) _bufQueryFile.QueryPtr(); pchEnd = pch + strlen(pch); // // Loop through the fields looking for values we recognize // { ODBC_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 )) { hr = Parser.CopyToEOL( &strDatasource ); } else if ( COMP_FIELD( "Username:", pszField, 9 )) { hr = Parser.CopyToEOL( &strUsername ); } else if ( COMP_FIELD( "Password:", pszField, 9 )) { hr = Parser.CopyToEOL( &strPassword ); } else if ( COMP_FIELD( "Template:", pszField, 9 )) { hr = 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 )) { hr = _plReqParams.ParseSimpleList( Parser.QueryLine() ); } else if ( COMP_FIELD( "Content-Type:", pszField, 13 )) { hr = 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 )) { hr = 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 ) { STRA strError; strError.FormatString( ODBCMSG_TOO_MANY_SQL_STATEMENTS, NULL, HTTP_ODBC_DLL ); SetErrorText( strError.QueryStr() ); return E_FAIL; } while ( TRUE ) { hr = _strQueries[_cQueries].Append( Parser.QueryLine() ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error appeinding value, hr = 0x%x.\n", hr )); return hr; } Parser.NextLine(); // // Line continuation is signified by putting a '+' at // the beginning of the line // if ( *Parser.QueryLine() == '+' ) { hr = _strQueries[_cQueries].Append( " " ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error appending space, hr = 0x%x.\n", hr )); return hr; } 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 )) { hr = Parser.CopyToEOL( &_strTranslationFile ); } else { // // Unrecognized field, generate an error // STRA strError; LPCSTR apsz[1]; apsz[0] = pszField; strError.FormatString( ODBCMSG_UNREC_FIELD, apsz, HTTP_ODBC_DLL ); SetErrorText( strError.QueryStr() ); hr = E_FAIL; } if ( FAILED( hr ) ) { return hr; } Parser.NextLine(); } } // // Make sure the Datasource and SQLStatement fields are non-empty // if ( strDatasource.IsEmpty() || !_cQueries || _strQueries[0].IsEmpty() ) { STRA strError; strError.FormatString( ODBCMSG_DSN_AND_SQLSTATEMENT_REQ, NULL, HTTP_ODBC_DLL ); SetErrorText( strError.QueryStr() ); return E_FAIL; } // // Make sure all of the required parameters have been supplied // while ( pCookie = _plReqParams.NextPair( pCookie, &pszField, &pszValue )) { if ( !_plParams.FindValue( pszField )) { STRA strError; LPCSTR apsz[1]; apsz[0] = pszField; if ( FAILED( hr = strError.FormatString( ODBCMSG_MISSING_REQ_PARAM, apsz, HTTP_ODBC_DLL ))) { return hr; } // // Set the error text to return the user and indicate we // couldn't continue the operation // SetErrorText( strError.QueryStr() ); return E_FAIL; } } // // 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 ( FAILED( OpenConnection( &_odbcconn, &_podbcconnPool, csecPoolConnection, strDatasource.QueryStr(), strUsername.QueryStr(), strPassword.QueryStr(), pszLoggedOnUser ) ) || FAILED( SetOdbcOptions( QueryOdbcConnection(), &_strOdbcOptions ) ) || !( _podbcstmt = QueryOdbcConnection()->AllocStatement() ) || FAILED( _podbcstmt->ExecDirect( _strQueries[0].QueryStr(), _strQueries[0].QueryCCH() ) ) ) { // // Delete the pooled connection and retry the open // if ( csecPoolConnection ) { CloseConnection( _podbcconnPool, TRUE ); _podbcconnPool = NULL; csecPoolConnection = 0; } if ( !fRetried ) { if( _podbcstmt ) { delete _podbcstmt; _podbcstmt = NULL; } fRetried = TRUE; goto RetryConnection; } return E_FAIL; } CloseConnection( _podbcconnPool, TRUE ); return S_OK; } HRESULT ODBC_REQ::OutputResults( ODBC_REQ_CALLBACK pfnCallback, PVOID pvContext, STRA * 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: HRESULT --*/ { DWORD cbOut; DWORD cbFile, cbHigh; DWORD BytesRead; DWORD cbToSend; BOOL fLastRow = FALSE; const CHAR * pchStartDetail; const CHAR * pchIn; const CHAR * pchEOF; const CHAR * pchBOF = NULL; CHAR * pchTag; const CHAR * pchValue; DWORD cbValue; enum TAG_TYPE TagType; DWORD err; BOOL fTriedRelative = FALSE; BOOL fExpr; STRA strError; const CHAR * CharacterMap[256]; BOOL fIsSelect; BOOL fMoreResults; BOOL fHaveResultSet = FALSE; DWORD iQuery = 1; HRESULT hr; DBG_ASSERT( CheckSignature() ); // // Set up the first buffer in the output chain // if ( !_pbufOut ) { _pbufOut = new BUFFER_CHAIN_ITEM( OUTPUT_BUFFER_SIZE ); if ( !_pbufOut || !_pbufOut->QueryPtr() ) { return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } } if ( !_fDirect ) { CHAR * pchLastSlash; STACK_STRA( str, MAX_PATH ); TryAgain: // // Open and read the template file (automatically zero terminated) // hr = GetFileData( ( fTriedRelative ? str.QueryStr() : _strTemplateFile.QueryStr() ), ( BYTE ** )&pchBOF, &BytesRead, _nCharset, TRUE ); if ( FAILED( hr ) ) { // // 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 ) ) || FAILED( str.Copy( _strQueryFile ) ) ) { 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 HRESULT_FROM_WIN32( dwE ); } if (!pfnSendHeader( pvContext, "500 IDC Query Error", pstrHeaders->QueryStr() )) { hr = HRESULT_FROM_WIN32(GetLastError()); DBGPRINTF(( DBG_CONTEXT, "Error in SendHeader(), hr = 0x%x.\n", hr )); return hr; } goto ErrorExit; } pchLastSlash = ( PCHAR )_mbsrchr( ( PUCHAR )str.QueryStr(), '\\' ); if ( !pchLastSlash ) { return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); } str.SetLen( DIFF(pchLastSlash - str.QueryStr()) + 1 ); hr = str.Append( _strTemplateFile ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error appending template file name, hr = 0x%x.\n", hr )); return hr; } fTriedRelative = TRUE; goto TryAgain; } else { // // Update our template file path if it changed // if ( fTriedRelative ) { hr = _strTemplateFile.Copy( str ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error updating template file path, hr = 0x%x.\n", hr )); return hr; } } } } // // Open the translation file if one was specified // if ( !_strTranslationFile.IsEmpty() ) { CHAR * pchLastSlash; CHAR * pchTranslationFile; STACK_STRA( str, MAX_PATH ); BOOL fRet; VOID * pvCookie = NULL; CHAR * pchField; DWORD cbRead; fTriedRelative = FALSE; TranslationTryAgain: // // Open and read the template file (automatically zero terminated) // hr = GetFileData( ( fTriedRelative ? str.QueryStr() : _strTranslationFile.QueryStr()), ( BYTE ** )&pchTranslationFile, &cbRead, _nCharset, TRUE ); if ( FAILED( hr ) ) { // // 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) || FAILED( str.Copy( _strQueryFile ) ) ) { 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 HRESULT_FROM_WIN32( dwE ); } goto ErrorExit; } pchLastSlash = (PCHAR)_mbsrchr( ( PUCHAR )str.QueryStr(), '\\' ); if ( !pchLastSlash ) { return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); } str.SetLen( DIFF(pchLastSlash - str.QueryStr()) + 1 ); hr = str.Append( _strTranslationFile ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error appending translation file name, hr = 0x%x.\n", hr )); return hr; } fTriedRelative = TRUE; goto TranslationTryAgain; } else { // // Update our template file path if it changed // if ( fTriedRelative ) { hr = _strTranslationFile.Copy( str ); if ( FAILED( hr ) ) { return hr; } } } hr = _plTransList.ParsePairs( pchTranslationFile, FALSE, TRUE, FALSE ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error in ParsePairs(), hr = 0x%x.\n", hr )); return hr; } // // Build the character map // memset( CharacterMap, 0, sizeof( CharacterMap ) ); while ( pvCookie = _plTransList.NextPair( pvCookie, &pchField, (LPSTR *)&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 ). // hr = _podbcstmt->QueryColNames( &_pstrCols, &_cCols, _cchMaxFieldSize, &fHaveResultSet ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error in QueryColNames, hr = 0x%x.\n", hr )); return hr; } if ( !fHaveResultSet ) { // // Check to see if there are anymore result sets for this query // hr = _podbcstmt->MoreResults( &fMoreResults ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error in MoreResults, hr = 0x%x.\n", hr )); return hr; } 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 // hr = _podbcstmt->ExecDirect( _strQueries[iQuery].QueryStr(), _strQueries[iQuery].QueryCCH() ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error in ExecDirect(), hr = 0x%x.\n", hr )); return hr; } iQuery++; goto NextResultSet; } } // // Get the first row of values // hr = NextRow( &fLastRow ); if ( fHaveResultSet && FAILED( hr ) ) { // // 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 hr; } } // Send reply header if (!pfnSendHeader( pvContext, "200 OK", pstrHeaders->QueryStr() )) { hr = HRESULT_FROM_WIN32(GetLastError()); DBGPRINTF(( DBG_CONTEXT, "Error in SendHeader(), hr = 0x%x.\n", hr )); return hr; } // // 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; if( pchBOF == NULL ) { return E_FAIL; } pchIn = pchBOF; pchEOF = pchBOF + BytesRead; while ( pchIn < pchEOF ) { // // Look for the start of a "