2025-04-27 07:49:33 -04:00

1083 lines
28 KiB
C++

/*++
Copyright (c) 1994 Microsoft Corporation
Module Name:
main.cxx
Abstract:
This is the HTTP ODBC gateway
Author:
John Ludeman (johnl) 20-Feb-1995
Revision History:
--*/
#include <iis64.h>
#include <w3p.hxx>
#include <tsunami.hxx>
#include <odbcconn.hxx>
#include <festrcnv.h>
# include "dbgutil.h"
DECLARE_DEBUG_PRINTS_OBJECT();
#ifndef _NO_TRACING_
#include <initguid.h>
DEFINE_GUID(IisOdbcGuid,
0x784d8936, 0xaa8c, 0x11d2, 0x92, 0x5e, 0x00, 0xc0, 0x4f, 0x72, 0xd9, 0x0e);
#else
DECLARE_DEBUG_VARIABLE();
#endif
extern "C" {
#include <httpext.h>
BOOL
WINAPI
DLLEntry(
HINSTANCE hDll,
DWORD dwReason,
LPVOID lpvReserved
);
}
#include <qrycache.hxx>
#include <odbcmsg.h>
#include <odbcreq.hxx>
//
// Globals
//
BOOL g_fIsSystemDBCS; // Is this system DBCS?
//
// Prototypes
//
BOOL
DoQuery(
EXTENSION_CONTROL_BLOCK * pecb,
PW3_SERVER_INSTANCE pInstance,
const CHAR * pszQueryFile,
const CHAR * pszParamList,
HTTP_REQUEST * pReq,
STR * pstrError,
int nCharset,
BOOL * pfAccessDenied,
BOOL * pfFileNotFound
);
DWORD
OdbcExtensionOutput(
EXTENSION_CONTROL_BLOCK * pecb,
const CHAR * pchOutput,
DWORD cbOutput
);
BOOL
OdbcExtensionHeader(
EXTENSION_CONTROL_BLOCK * pecb,
const CHAR * pchStatus,
const CHAR * pchHeaders
);
BOOL LookupHttpSymbols(
HTTP_REQUEST * pRequest,
const CHAR * pszSymbolName,
STR * pstrSymbolValue
);
BOOL
GetIDCCharset(
CONST CHAR * pszPath,
int * pnCharset,
STR * pstrError
);
BOOL
ConvertUrlEncodedStringToSJIS(
int nCharset,
STR * pstrParams
);
BOOL
IsSystemDBCS(
VOID
);
DWORD
HttpExtensionProc(
EXTENSION_CONTROL_BLOCK * pecb
)
{
STACK_STR( strPath, MAX_PATH);
STACK_STR( strParams, MAX_PATH);
STR strError;
CHAR * pch;
DWORD cch;
HTTP_REQUEST * pReq;
PW3_SERVER_INSTANCE pInstance;
int nCharset;
TCP_REQUIRE(( pecb->ServerSupportFunction( pecb->ConnID,
HSE_PRIV_REQ_HTTP_REQUEST,
&pReq,
NULL,
NULL )));
//
// Make sure ODBC is loaded
//
if ( !LoadODBC() )
{
STR str;
str.FormatString( ODBCMSG_CANT_LOAD_ODBC,
NULL,
HTTP_ODBC_DLL );
pecb->ServerSupportFunction( pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
"500 Unable to load ODBC",
NULL,
(LPDWORD) str.QueryStr() );
pecb->dwHttpStatusCode = 500;
return HSE_STATUS_ERROR;
}
//
// We currently only support the GET and POST methods
//
if ( !strcmp( pecb->lpszMethod, "POST" ))
{
if ( _stricmp( pecb->lpszContentType,
"application/x-www-form-urlencoded" ))
{
goto BadRequest;
}
//
// The query params are in the extra data, add a few bytes in case
// we need to double "'"
//
if ( !strParams.Resize( pecb->cbAvailable + sizeof(TCHAR) + 3))
return HSE_STATUS_ERROR;
strParams.Copy( (const char * ) pecb->lpbData, pecb->cbAvailable);
}
else if ( !strcmp( pecb->lpszMethod, "GET" ))
{
if ( !strParams.Copy( pecb->lpszQueryString ))
return HSE_STATUS_ERROR;
}
else
{
BadRequest:
STR str;
str.FormatString( ODBCMSG_UNSUPPORTED_METHOD,
NULL,
HTTP_ODBC_DLL );
pecb->ServerSupportFunction(
pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
"400 Unsupported method",
NULL,
(LPDWORD) str.QueryStr() );
pecb->dwHttpStatusCode = 400;
return HSE_STATUS_ERROR;
}
//
// "Charset" field is enabled for CP932 (Japanese) only in this version.
//
if ( 932 != GetACP() )
{
nCharset = CODE_ONLY_SBCS;
}
else
{
//
// Get the charset from .idc file
//
if ( !GetIDCCharset( pecb->lpszPathTranslated, &nCharset, &strError ) )
{
if (strError.IsEmpty())
{
// WinSE 27595 DoS when requesting non-existant .idc file on JPN version
pecb->ServerSupportFunction( pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPDWORD) "404 File Not Found",
NULL,
NULL );
pecb->dwHttpStatusCode = 404;
return HSE_STATUS_ERROR;
}
STR str;
LPCSTR apsz[1];
apsz[0] = strError.QueryStr();
str.FormatString( ODBCMSG_ERROR_PERFORMING_QUERY,
apsz,
HTTP_ODBC_DLL,
1024 + strError.QueryCB() );
pecb->ServerSupportFunction( pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPDWORD) "500 Error performing query",
NULL,
(LPDWORD) str.QueryStr() );
pecb->dwHttpStatusCode = 500;
return HSE_STATUS_ERROR;
}
if ( strParams.QueryCB() )
{
if ( CODE_ONLY_SBCS != nCharset )
{
//
// Convert the charset of Parameters to SJIS
//
if ( !ConvertUrlEncodedStringToSJIS( nCharset, &strParams ) )
{
STR strError;
STR str;
strError.LoadString( GetLastError(), (LPCTSTR) NULL );
str.FormatString( ODBCMSG_ERROR_PERFORMING_QUERY,
NULL,
HTTP_ODBC_DLL );
pecb->ServerSupportFunction( pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPDWORD) "500 Error performing Query",
NULL,
(LPDWORD) str.QueryStr() );
pecb->dwHttpStatusCode = 500;
return HSE_STATUS_ERROR;
}
}
}
}
//
// Walk the parameter string to do three things:
//
// 1) Double all single quotes to prevent SQL quoting problem
// 2) Remove escaped '\n's so we don't break parameter parsing later on
// 3) Replace all '&' parameter delimiters with real '\n' so escaped
// '&'s won't get confused later on
//
pch = strParams.QueryStr();
cch = strParams.QueryCCH();
while ( *pch )
{
switch ( *pch )
{
case '%':
if ( pch[1] == '0' && toupper(pch[2]) == 'A' )
{
pch[1] = '2';
pch[2] = '0';
pch += 3;
}
else if ( pch[1] == '2' && pch[2] == '7' )
{
//
// This is an escaped single quote
//
if ( strParams.QuerySize() < (cch + 4) ) // Include null
{
DWORD Pos = DIFF(pch - strParams.QueryStr());
if ( !strParams.Resize( cch + 4 ) )
return HSE_STATUS_ERROR;
//
// Adjust for possible pointer shift
//
pch = strParams.QueryStr() + Pos;
}
//
// Note the memory copy just doubles the existing quote
//
memmove( pch+3,
pch,
(cch + 1) - DIFF(pch - strParams.QueryStr()) );
cch += 3; // Adjust for the additional '%27'
pch += 6; // skip old and new %27
}
else
{
pch +=1;
}
break;
case '\'':
if ( strParams.QuerySize() < (cch + 2) )
{
DWORD Pos = DIFF(pch - strParams.QueryStr());
if ( !strParams.Resize( cch + 2 ) )
return HSE_STATUS_ERROR;
//
// Adjust for possible pointer shift
//
pch = strParams.QueryStr() + Pos;
}
//
// Note the memory copy just doubles the existing quote
//
memmove( pch+1,
pch,
(cch + 1) - DIFF(pch - strParams.QueryStr()) );
pch += 2;
cch++; // Adjust for the additional '''
break;
case '&':
*pch = '\n';
pch++;
break;
default:
pch++;
}
}
//
// Get the TSVC_INFO object from the HTTP server
//
#if 0
TCP_REQUIRE(( pecb->ServerSupportFunction( pecb->ConnID,
HSE_PRIV_REQ_TSVCINFO,
&pInetSvc,
NULL,
NULL )));
#else
pInstance = pReq->QueryClientConn()->QueryW3Instance();
#endif
//
// The path info contains the location of the query file
//
if ( !strPath.Copy( pecb->lpszPathTranslated ))
return HSE_STATUS_ERROR;
FlipSlashes( strPath.QueryStr() );
//
// Attempt the query
//
BOOL fFileNotFound = FALSE;
BOOL fAccessDenied = FALSE;
if ( !DoQuery( pecb,
pInstance,
strPath.QueryStr(),
strParams.QueryStr(),
pReq,
&strError,
nCharset,
&fAccessDenied,
&fFileNotFound ))
{
if ( fAccessDenied )
{
pecb->ServerSupportFunction( pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPDWORD) "401 Authentication Required",
NULL,
NULL );
pecb->dwHttpStatusCode = 401;
return HSE_STATUS_ERROR;
}
else if (fFileNotFound)
{
pecb->ServerSupportFunction( pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPDWORD) "404 File Not Found",
NULL,
NULL );
pecb->dwHttpStatusCode = 404;
return HSE_STATUS_ERROR;
}
else
{
STR str;
LPCSTR apsz[1];
apsz[0] = strError.QueryStr();
//
// Note we terminate the error message (ODBC sometimes generates
// 22k errors) *and* we double the buffer size we pass to FormatString()
// because the win32 API FormatMessage() has a bug that doesn't
// account for Unicode conversion
//
if ( strlen( apsz[0] ) > 1024 ) {
((LPSTR)apsz[0])[1024] = '\0';
}
str.FormatString( ODBCMSG_ERROR_PERFORMING_QUERY,
apsz,
HTTP_ODBC_DLL,
1024 + strError.QueryCB() );
pecb->ServerSupportFunction( pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPDWORD) "500 Error performing query",
NULL,
(LPDWORD) str.QueryStr() );
pecb->dwHttpStatusCode = 500;
return HSE_STATUS_ERROR;
}
}
return HSE_STATUS_SUCCESS;
}
BOOL
DoQuery(
EXTENSION_CONTROL_BLOCK * pecb,
PW3_SERVER_INSTANCE pInstance,
const CHAR * pszQueryFile,
const CHAR * pszParamList,
HTTP_REQUEST * pReq,
STR * pstrError,
int nCharset,
BOOL * pfAccessDenied,
BOOL * pfFileNotFound
)
/*++
Routine Description:
Performs the actual query or retrieves the same query from the query
cache
Arguments:
pecb - Extension context
pTsvcInfo - Server info class
pszQueryFile - .wdg file to use for query
pszParamList - Client supplied param list
pReq - Http request this query is for
pstrError - Error text to return errors in
pfAccessDenied - Set to TRUE if the user was denied access
Return Value:
TRUE if successful, FALSE on error
--*/
{
ODBC_REQ * podbcreq;
ODBC_REQ * podbcreqCached;
VOID * pvCacheCookie = NULL;
BOOL fRet = TRUE;
STACK_STR( strHeaders, MAX_PATH);
CHAR achPragmas[250];
DWORD cbPragmas = sizeof(achPragmas);
BOOL fRetrieveFromCache = TRUE;
HANDLE hToken;
PSECURITY_DESCRIPTOR pSecDesc = NULL;
if ( !pecb->ServerSupportFunction( pecb->ConnID,
HSE_REQ_GET_IMPERSONATION_TOKEN,
&hToken,
NULL,
NULL ) )
{
return FALSE;;
}
//
// Create an odbc request as we will either use it for the query or
// as the key for the cache lookup
//
podbcreq = new ODBC_REQ( &pInstance->GetTsvcCache(),
pszQueryFile,
pecb->lpszPathInfo,
pszParamList,
pReq->IsAnonymous(),
hToken,
pReq->QueryMetaData()->QueryPoolIDCTimeout(),
(ODBC_REQ_FIND_SYMBOL) LookupHttpSymbols,
(VOID *) pReq,
nCharset );
if ( !podbcreq ||
!podbcreq->IsValid() )
{
pstrError->LoadString( GetLastError() );
delete podbcreq;
return FALSE;
}
//
// Check to see if user already authentified
//
CHAR achLoggedOnUser[UNLEN + 1];
DWORD dwLoggedOnUser = sizeof( achLoggedOnUser );
BOOL fIsAuth = pecb->GetServerVariable( (HCONN)pecb->ConnID,
"LOGON_USER",
achLoggedOnUser,
&dwLoggedOnUser ) &&
achLoggedOnUser[0] != '\0';
//
// Check to see if the client specified "Pragma: no-cache"
//
if ( pecb->GetServerVariable( pecb->ConnID,
"HTTP_PRAGMA",
achPragmas,
&cbPragmas ))
{
CHAR * pch;
//
// Look for "no-cache"
//
pch = _strupr( achPragmas );
while ( pch = strchr( pch, 'N' ))
{
if ( !memcmp( pch, "NO-CACHE", 8 ))
{
fRetrieveFromCache = FALSE;
goto Found;
}
pch++;
}
}
Found:
//
// Is this query in the cache?
//
if ( !fRetrieveFromCache ||
!CheckOutQuery( podbcreq,
&pvCacheCookie,
&podbcreqCached ))
{
if ( fRetrieveFromCache && GetLastError() == ERROR_ACCESS_DENIED )
{
*pfAccessDenied = TRUE;
fRet = FALSE;
goto Exit;
}
DWORD CurChangeCounter;
//
// This query isn't cached, do the query
//
//
// Get the current change counter. If it's different after doing
// the query then an .htx or .idc file has changed so don't cache it
//
CurChangeCounter = GetChangeCounter();
//
// Open the query file and do the query
//
if ( !podbcreq->OpenQueryFile( pfAccessDenied, pfFileNotFound ) ||
!podbcreq->ParseAndQuery( achLoggedOnUser ) ||
!podbcreq->AppendHeaders( &strHeaders ) ||
!podbcreq->OutputResults( (ODBC_REQ_CALLBACK) OdbcExtensionOutput,
pecb, &strHeaders,
(ODBC_REQ_HEADER) OdbcExtensionHeader,
fIsAuth,
pfAccessDenied,
pfFileNotFound) )
{
fRet = FALSE;
goto Exit;
}
//
// If the query can't be cached (no "Expires:" field for example) then
// delete it now and get out
//
if ( !podbcreq->IsCacheable() )
{
delete podbcreq;
goto Exit;
}
else
{
//
// Close the odbc handles now that we have all of the data
//
podbcreq->Close();
}
//
// Now attempt to add the query to the cache.
//
if ( !AddQuery( podbcreq,
CurChangeCounter ))
{
if ( GetLastError() == ERROR_ACCESS_DENIED )
{
*pfAccessDenied = TRUE;
}
delete podbcreq;
goto Exit;
}
}
else
{
//
// We already have an equivalent query in our cache, use it instead
//
delete podbcreq;
podbcreq = podbcreqCached;
if ( !podbcreq->AppendHeaders( &strHeaders ) ||
!pecb->ServerSupportFunction( pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPDWORD) "200 OK",
NULL,
(LPDWORD) strHeaders.QueryStr()) ||
!podbcreq->OutputCachedResults( (ODBC_REQ_CALLBACK) OdbcExtensionOutput,
pecb ) )
{
fRet = FALSE;
goto Exit;
}
}
Exit:
if ( !fRet )
{
podbcreq->GetLastErrorText( pstrError );
//
// If we didn't cache this request, then delete it
//
if ( !pvCacheCookie )
{
delete podbcreq;
}
}
if ( pvCacheCookie )
{
TCP_REQUIRE( CheckInQuery( pvCacheCookie ) );
}
if ( pSecDesc )
{
LocalFree( pSecDesc );
}
return fRet;
}
DWORD OdbcExtensionOutput( EXTENSION_CONTROL_BLOCK * pecb,
const CHAR * pchOutput,
DWORD cbOutput )
{
if ( !pecb->WriteClient( pecb->ConnID,
(VOID *) pchOutput,
&cbOutput,
0 ))
{
return GetLastError();
}
return NO_ERROR;
}
BOOL OdbcExtensionHeader( EXTENSION_CONTROL_BLOCK * pecb,
const CHAR * pchStatus,
const CHAR * pchHeaders )
{
return pecb->ServerSupportFunction(
pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPDWORD) "200 OK",
NULL,
(LPDWORD) pchHeaders );
}
BOOL LookupHttpSymbols(
HTTP_REQUEST * pRequest,
const CHAR * pszSymbolName,
STR * pstrSymbolValue
)
{
//
// Terminate the string if necessary
//
if ( !pstrSymbolValue->IsEmpty() )
{
*pstrSymbolValue->QueryStr() = '\0';
}
//
// Get any defined variables from this request
//
return pRequest->GetInfo( pszSymbolName,
pstrSymbolValue );
}
BOOL
WINAPI
DLLEntry(
HINSTANCE hDll,
DWORD dwReason,
LPVOID lpvReserved
)
{
DWORD err;
switch ( dwReason )
{
case DLL_PROCESS_ATTACH:
#ifdef _NO_TRACING_
CREATE_DEBUG_PRINT_OBJECT( "httpodbc.dll");
SET_DEBUG_FLAGS( 0);
#else
CREATE_DEBUG_PRINT_OBJECT( "httpodbc.dll", IisOdbcGuid);
#endif
if ( !InitializeQueryCache() ||
!InitializeOdbcPool() )
{
return FALSE;
}
DisableThreadLibraryCalls( hDll );
g_fIsSystemDBCS = IsSystemDBCS();
break;
case DLL_PROCESS_DETACH:
TerminateQueryCache();
TerminateOdbcPool();
DELETE_DEBUG_PRINT_OBJECT();
break;
default:
break;
}
return TRUE;
}
BOOL
GetExtensionVersion(
HSE_VERSION_INFO * pver
)
{
pver->dwExtensionVersion = MAKELONG( 0, 2 );
strcpy( pver->lpszExtensionDesc,
"Microsoft HTTP ODBC Gateway, v2.0" );
return TRUE;
}
CHAR * skipwhite( CHAR * pch )
{
CHAR ch;
while ( 0 != (ch = *pch) )
{
if ( ' ' != ch && '\t' != ch )
break;
++pch;
}
return pch;
}
CHAR * nextline( CHAR * pch )
{
CHAR ch;
while ( 0 != (ch = *pch) )
{
++pch;
if ( '\n' == ch )
{
break;
}
}
return pch;
}
BOOL
GetIDCCharset(
CONST CHAR * pszPath,
int * pnCharset,
STR * pstrError
)
{
BUFFER buff;
HANDLE hFile;
DWORD dwSize;
CACHE_FILE_INFO CacheFileInfo;
#define QUERYFILE_READSIZE 4096
if ( !pnCharset )
return FALSE;
*pnCharset = CODE_ONLY_SBCS;
if ( !buff.Resize( QUERYFILE_READSIZE + sizeof(TCHAR) ) )
{
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
pstrError->LoadString( GetLastError() );
return FALSE;
}
hFile = CreateFile(
pszPath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if ( INVALID_HANDLE_VALUE == hFile )
{
pstrError->Reset(); //WinSE 27595
return FALSE;
}
if ( !ReadFile( hFile, buff.QueryPtr(), QUERYFILE_READSIZE, &dwSize, NULL ) )
{
pstrError->LoadString( GetLastError() );
CloseHandle( hFile );
return FALSE;
}
CloseHandle( hFile );
*((CHAR *) buff.QueryPtr() + dwSize) = '\0';
CHAR * pch = (CHAR *)buff.QueryPtr(); // we know that this is null terminated!
while ( *pch )
{
pch = skipwhite( pch );
if ( 'C' == toupper( *pch ) && !_strnicmp( IDC_FIELDNAME_CHARSET, pch, sizeof(IDC_FIELDNAME_CHARSET)-1 ) )
{
pch += sizeof(IDC_FIELDNAME_CHARSET) - 1;
pch = skipwhite( pch );
if ( 932 == GetACP() )
{
if ( !_strnicmp( IDC_CHARSET_JIS1, pch, sizeof(IDC_CHARSET_JIS1)-1 ) ||
!_strnicmp( IDC_CHARSET_JIS2, pch, sizeof(IDC_CHARSET_JIS2)-1 ) )
{
*pnCharset = CODE_JPN_JIS;
break;
}
else if ( !_strnicmp( IDC_CHARSET_EUCJP, pch, sizeof(IDC_CHARSET_EUCJP)-1 ))
{
*pnCharset = CODE_JPN_EUC;
break;
}
else if ( !_strnicmp( IDC_CHARSET_SJIS, pch, sizeof(IDC_CHARSET_SJIS)-1 ))
{
*pnCharset = CODE_ONLY_SBCS;
break;
}
else
{
LPCSTR apsz[1];
//
// illegal value for Charset: field
//
apsz[0] = pszPath;
pstrError->FormatString( ODBCMSG_UNREC_FIELD,
apsz,
HTTP_ODBC_DLL );
return FALSE;
}
}
//
// please add code here to support other FE character encoding(FEFEFE)
//
// else if ( 949 == GetACP() )
// ...
}
pch = nextline( pch );
}
return TRUE;
}
BOOL
ConvertUrlEncodedStringToSJIS(
int nCharset,
STR * pstrParams
)
{
STACK_STR( strTemp, MAX_PATH);
int cbSJISSize;
int nResult;
//
// Pre-process the URL encoded parameters
//
for ( char * pch = pstrParams->QueryStr(); *pch; ++pch )
{
if ( *pch == '&' )
*pch = '\n';
else if ( *pch == '+' )
*pch = ' ';
}
//
// URL decoding (decode %nn only)
//
pstrParams->Unescape();
//
// charset conversion
//
// if ( !pstrParams->Clone( &strTemp ) )
if ( !strTemp.Copy( pstrParams->QueryStr() ) )
return FALSE;
cbSJISSize = UNIX_to_PC(
GetACP(),
nCharset,
(UCHAR *)strTemp.QueryStr(),
strTemp.QueryCB(),
NULL,
0 );
if ( !pstrParams->Resize( cbSJISSize + sizeof(TCHAR) ) )
return FALSE;
nResult = UNIX_to_PC(
GetACP(),
nCharset,
(UCHAR *)strTemp.QueryStr(),
strTemp.QueryCB(),
(UCHAR *)pstrParams->QueryStr(),
cbSJISSize );
if ( -1 == nResult || nResult != cbSJISSize )
return FALSE;
DBG_REQUIRE ( pstrParams->SetLen( cbSJISSize));
//
// URL encoding
//
if ( !pstrParams->Escape() )
return FALSE;
return TRUE;
}
BOOL
IsSystemDBCS(
VOID )
{
WORD wPrimaryLangID = PRIMARYLANGID( GetSystemDefaultLangID() );
return ( wPrimaryLangID == LANG_JAPANESE ||
wPrimaryLangID == LANG_CHINESE ||
wPrimaryLangID == LANG_KOREAN );
}