1589 lines
41 KiB
C++
1589 lines
41 KiB
C++
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
ssicgi.cxx
|
|
|
|
Abstract:
|
|
|
|
This is CGI code "ripped" from \w3\server\cgi.cxx
|
|
|
|
Author:
|
|
|
|
Bilal Alam (t-bilala) 5-June-1996
|
|
|
|
Revision History:
|
|
|
|
See iis\svcs\w3\server\cgi.cxx for prior log
|
|
|
|
--*/
|
|
|
|
#include "ssinc.hxx"
|
|
#include "ssicgi.hxx"
|
|
|
|
typedef struct CgiEnvTableEntry_ {
|
|
TCHAR* m_pszName;
|
|
BOOL m_fIsProcessEnv;
|
|
UINT m_cchNameLen;
|
|
UINT m_cchToCopy; // will be non zero for var to copy from
|
|
// process environment. In this case m_pszName
|
|
// points to the environment entry to copy
|
|
// ( name + '=' + value + '\0' )
|
|
// otherwise this entry is to be accessed
|
|
// using GetInfo()
|
|
} CgiEnvTableEntry;
|
|
|
|
|
|
//
|
|
// Environment variable block used for CGI
|
|
//
|
|
// best if in alphabetical order ( the env list is easier to read )
|
|
// but not mandatory.
|
|
// Note that the "" ( accessed as HTTP_ALL ) will be expanded to a list
|
|
// of unsorted entries, but this list as a whole will be considered to be
|
|
// named "HTTP_ALL" for sorting order.
|
|
//
|
|
|
|
CgiEnvTableEntry CGIEnvTable[] =
|
|
{
|
|
{TEXT("AUTH_TYPE"),FALSE},
|
|
{TEXT("ComSpec"),TRUE},
|
|
{TEXT("CONTENT_LENGTH"),FALSE},
|
|
{TEXT("CONTENT_TYPE"),FALSE},
|
|
{TEXT("GATEWAY_INTERFACE"),FALSE},
|
|
{TEXT(""),FALSE}, // Means insert all HTTP_ headers here
|
|
{TEXT("LOGON_USER"),FALSE},
|
|
{TEXT("PATH"),TRUE},
|
|
{TEXT("PATH_INFO"),FALSE},
|
|
{TEXT("PATH_TRANSLATED"),FALSE},
|
|
{TEXT("QUERY_STRING"),FALSE},
|
|
{TEXT("REMOTE_ADDR"),FALSE},
|
|
{TEXT("REMOTE_HOST"),FALSE},
|
|
{TEXT("REMOTE_USER"),FALSE},
|
|
{TEXT("REQUEST_METHOD"),FALSE},
|
|
{TEXT("SCRIPT_NAME"),FALSE},
|
|
{TEXT("SERVER_NAME"),FALSE},
|
|
{TEXT("SERVER_PORT"),FALSE},
|
|
{TEXT("SERVER_PORT_SECURE"),FALSE},
|
|
{TEXT("SERVER_PROTOCOL"),FALSE},
|
|
{TEXT("SERVER_SOFTWARE"),FALSE},
|
|
{TEXT("SystemRoot"),TRUE},
|
|
{TEXT("UNMAPPED_REMOTE_USER"),FALSE},
|
|
{TEXT("windir"),TRUE},
|
|
{NULL,FALSE}
|
|
};
|
|
|
|
//
|
|
// Globals
|
|
//
|
|
|
|
BOOL fAllowSpecialCharsInShell = FALSE;
|
|
BOOL fCreateProcessAsUser = TRUE;
|
|
BOOL fCreateProcessWithNewConsole = FALSE;
|
|
BOOL fForwardServerEnvironmentBlock = TRUE;
|
|
LPSTR g_pszIisEnv = NULL;
|
|
CgiEnvTableEntry *g_pEnvEntries = NULL;
|
|
DWORD dwTimeOut = 0;
|
|
BOOL fInitialized = FALSE;
|
|
|
|
class CGI_INFO
|
|
{
|
|
public:
|
|
CGI_INFO( SSI_REQUEST * pRequest )
|
|
: _pRequest ( pRequest ),
|
|
_cbData ( 0 ),
|
|
_hStdOut ( INVALID_HANDLE_VALUE ),
|
|
_hStdIn ( INVALID_HANDLE_VALUE ),
|
|
_hProcess ( INVALID_HANDLE_VALUE ),
|
|
_fShutdown ( FALSE )
|
|
{
|
|
_hResponseEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
|
|
}
|
|
|
|
~CGI_INFO( VOID )
|
|
{
|
|
if ( _hStdOut != INVALID_HANDLE_VALUE )
|
|
{
|
|
if ( !::CloseHandle( _hStdOut ))
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[~CGI_INFO] CloseHandle failed on StdIn, %d\n",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
|
|
if ( _hStdIn != INVALID_HANDLE_VALUE )
|
|
{
|
|
if ( !::CloseHandle( _hStdIn ))
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[~CGI_INFO] CloseHandle failed on StdIn, %d\n",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
|
|
if ( _hProcess != INVALID_HANDLE_VALUE )
|
|
{
|
|
if ( !::CloseHandle( _hProcess ))
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[~CGI_INFO] CloseHandle failed on Process, %d\n",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
if ( _hResponseEvent != NULL )
|
|
{
|
|
if ( !::CloseHandle( _hResponseEvent ))
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[~CGI_INFO] CloseHandle failed on Process, %d\n",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
}
|
|
|
|
SSI_REQUEST * _pRequest;
|
|
|
|
//
|
|
// Child process
|
|
//
|
|
|
|
HANDLE _hProcess;
|
|
|
|
//
|
|
// Parent's input and output handles and child's process handle
|
|
//
|
|
|
|
HANDLE _hStdOut;
|
|
HANDLE _hStdIn;
|
|
|
|
//
|
|
// Handles input from CGI (headers and additional data)
|
|
//
|
|
|
|
BUFFER _Buff;
|
|
UINT _cbData;
|
|
|
|
//
|
|
// Event to check for hanging processes
|
|
// and thread shutdown flag
|
|
//
|
|
|
|
HANDLE _hResponseEvent;
|
|
BOOL _fShutdown;
|
|
};
|
|
|
|
//
|
|
// Prototypes
|
|
//
|
|
|
|
BOOL
|
|
ProcessCGI(
|
|
SSI_REQUEST * pRequest,
|
|
const STR * pstrPath,
|
|
const STR * pstrURLParams,
|
|
const STR * pstrWorkingDir,
|
|
STR * pstrCmdLine
|
|
);
|
|
|
|
BOOL SetupChildEnv( SSI_REQUEST * pRequest,
|
|
BUFFER * pBuff );
|
|
|
|
BOOL SetupChildPipes( STARTUPINFO * pstartupinfo,
|
|
HANDLE * phParentIn,
|
|
HANDLE * phParentOut );
|
|
|
|
BOOL SetupCmdLine( STR * pstrCmdLine,
|
|
const STR & strParams );
|
|
|
|
DWORD CGIThread( PVOID Param );
|
|
|
|
BOOL ProcessCGIInput( CGI_INFO * pCGIInfo,
|
|
BYTE * buff,
|
|
DWORD cbRead,
|
|
BOOL * pfReadHeaders );
|
|
|
|
BOOL CheckForTermination( BOOL * pfTerminated,
|
|
BUFFER * pbuff,
|
|
UINT cbData,
|
|
BYTE * * ppbExtraData,
|
|
DWORD * pcbExtraData,
|
|
UINT cbReallocSize );
|
|
BOOL
|
|
FastScanForTerminator(
|
|
CHAR * pch,
|
|
UINT cbData
|
|
);
|
|
|
|
DWORD
|
|
InitializeCGI(
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
TerminateCGI(
|
|
VOID
|
|
);
|
|
|
|
//
|
|
// End of prototypes
|
|
//
|
|
|
|
DWORD
|
|
ReadRegistryDword(
|
|
IN HKEY hkey,
|
|
IN LPSTR pszValueName,
|
|
IN DWORD dwDefaultValue
|
|
)
|
|
{
|
|
DWORD err;
|
|
DWORD dwBuffer;
|
|
|
|
DWORD cbBuffer = sizeof(dwBuffer);
|
|
DWORD dwType;
|
|
|
|
#ifndef CHICAGO
|
|
if ( hkey != NULL )
|
|
{
|
|
err = RegQueryValueExA( hkey,
|
|
pszValueName,
|
|
NULL,
|
|
&dwType,
|
|
(LPBYTE)&dwBuffer,
|
|
&cbBuffer );
|
|
|
|
if ( ( err == NO_ERROR ) && ( dwType == REG_DWORD ) )
|
|
{
|
|
dwDefaultValue = dwBuffer;
|
|
}
|
|
}
|
|
#else
|
|
if ( hkey != NULL )
|
|
{
|
|
|
|
err = RegQueryValueEx( hkey,
|
|
pszValueName,
|
|
NULL,
|
|
&dwType,
|
|
(LPBYTE)&dwBuffer,
|
|
&cbBuffer );
|
|
|
|
if ( ( err == NO_ERROR ) )
|
|
{
|
|
dwDefaultValue = dwBuffer;
|
|
}
|
|
}
|
|
#endif
|
|
return dwDefaultValue;
|
|
} // ReadRegistryDword()
|
|
|
|
extern "C" int __cdecl
|
|
QsortEnvCmp(
|
|
const void *pA,
|
|
const void *pB )
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Compare CgiEnvTableEntry using their name entry
|
|
|
|
Arguments:
|
|
|
|
pA - pointer to 1st entry
|
|
pB - pointer to 2nd entry
|
|
|
|
Returns:
|
|
|
|
-1 if 1st entry comes first in sort order,
|
|
0 if identical
|
|
1 if 2nd entry comes first
|
|
|
|
--*/
|
|
{
|
|
LPSTR p1 = ((CgiEnvTableEntry*)pA)->m_pszName;
|
|
LPSTR p2 = ((CgiEnvTableEntry*)pB)->m_pszName;
|
|
|
|
if ( ! p1[0] )
|
|
{
|
|
p1 = "HTTP_ALL";
|
|
}
|
|
|
|
if ( ! p2[0] )
|
|
{
|
|
p2 = "HTTP_ALL";
|
|
}
|
|
|
|
return _stricmp( p1, p2 );
|
|
}
|
|
|
|
DWORD
|
|
InitializeCGI(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize CGI
|
|
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
LPVOID pvEnv;
|
|
UINT cchStr;
|
|
UINT cchIisEnv;
|
|
UINT cEnv;
|
|
INT chScanEndOfName;
|
|
CgiEnvTableEntry * pCgiEnv;
|
|
HKEY hkeyParam;
|
|
|
|
|
|
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|
W3_PARAMETERS_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&hkeyParam ) == NO_ERROR )
|
|
{
|
|
fAllowSpecialCharsInShell = !!ReadRegistryDword( hkeyParam,
|
|
"AllowSpecialCharsInShell",
|
|
FALSE );
|
|
|
|
fForwardServerEnvironmentBlock = !!ReadRegistryDword(
|
|
hkeyParam,
|
|
"ForwardServerEnvironmentBlock",
|
|
TRUE );
|
|
|
|
fCreateProcessAsUser = !!ReadRegistryDword( hkeyParam,
|
|
"CreateProcessAsUser",
|
|
TRUE );
|
|
|
|
fCreateProcessWithNewConsole = !!ReadRegistryDword( hkeyParam,
|
|
"CreateProcessWithNewConsole",
|
|
FALSE );
|
|
|
|
dwTimeOut = ReadRegistryDword( hkeyParam,
|
|
"ScriptTimeOut",
|
|
SSI_CGI_DEF_TIMEOUT );
|
|
if ( dwTimeOut >= ((DWORD)-1)/1000 )
|
|
{
|
|
dwTimeOut = (DWORD)-1;
|
|
}
|
|
else
|
|
{
|
|
dwTimeOut *= 1000;
|
|
}
|
|
|
|
RegCloseKey( hkeyParam );
|
|
}
|
|
|
|
if ( fForwardServerEnvironmentBlock
|
|
&& (pvEnv = GetEnvironmentStrings()) )
|
|
{
|
|
//
|
|
// Compute length of environment block and # of variables
|
|
// ( excluding block delimiter )
|
|
//
|
|
|
|
cchIisEnv = 0;
|
|
cEnv = 0;
|
|
|
|
while ( cchStr = strlen( ((PSTR)pvEnv) + cchIisEnv ) )
|
|
{
|
|
cchIisEnv += cchStr + 1;
|
|
++cEnv;
|
|
}
|
|
|
|
//
|
|
// store it
|
|
//
|
|
|
|
if ( (g_pszIisEnv = (LPSTR)LocalAlloc(
|
|
LMEM_FIXED, cchIisEnv * sizeof(TCHAR))) == NULL )
|
|
{
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
memcpy( g_pszIisEnv, pvEnv,
|
|
cchIisEnv * sizeof(TCHAR) );
|
|
|
|
FreeEnvironmentStrings( (LPTSTR)pvEnv );
|
|
|
|
pvEnv = (PVOID)g_pszIisEnv;
|
|
|
|
if ( g_pEnvEntries = new CgiEnvTableEntry [
|
|
cEnv + sizeof(CGIEnvTable)/sizeof(CgiEnvTableEntry) ] )
|
|
{
|
|
cchIisEnv = 0;
|
|
cEnv = 0;
|
|
|
|
//
|
|
// add process environment to table
|
|
//
|
|
|
|
while ( cchStr = strlen( ((PSTR)pvEnv) + cchIisEnv ) )
|
|
{
|
|
g_pEnvEntries[ cEnv ].m_pszName = ((PSTR)pvEnv) + cchIisEnv;
|
|
g_pEnvEntries[ cEnv ].m_fIsProcessEnv = TRUE;
|
|
|
|
// compute length of name : up to '=' char
|
|
|
|
for ( g_pEnvEntries[ cEnv ].m_cchNameLen = 0 ;
|
|
( chScanEndOfName = g_pEnvEntries[ cEnv ].m_pszName
|
|
[ g_pEnvEntries[ cEnv ].m_cchNameLen ] )
|
|
&& chScanEndOfName != '=' ; )
|
|
{
|
|
++g_pEnvEntries[ cEnv ].m_cchNameLen;
|
|
}
|
|
|
|
g_pEnvEntries[ cEnv ].m_cchToCopy = cchStr + 1;
|
|
|
|
cchIisEnv += cchStr + 1;
|
|
++cEnv;
|
|
}
|
|
|
|
//
|
|
// add CGI environment variables to table
|
|
//
|
|
|
|
for ( pCgiEnv = CGIEnvTable ; pCgiEnv->m_pszName ; ++pCgiEnv )
|
|
{
|
|
if ( !pCgiEnv->m_fIsProcessEnv )
|
|
{
|
|
memcpy( g_pEnvEntries + cEnv, pCgiEnv,
|
|
sizeof(CgiEnvTableEntry) );
|
|
g_pEnvEntries[ cEnv ].m_cchNameLen
|
|
= strlen( pCgiEnv->m_pszName );
|
|
g_pEnvEntries[ cEnv ].m_cchToCopy = 0;
|
|
++cEnv;
|
|
}
|
|
}
|
|
|
|
//
|
|
// add delimiter entry
|
|
//
|
|
|
|
g_pEnvEntries[ cEnv ].m_pszName = NULL;
|
|
|
|
qsort( g_pEnvEntries,
|
|
cEnv,
|
|
sizeof(CgiEnvTableEntry),
|
|
QsortEnvCmp );
|
|
}
|
|
else
|
|
{
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_pEnvEntries = CGIEnvTable;
|
|
}
|
|
|
|
fInitialized = TRUE;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
VOID
|
|
TerminateCGI(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Terminate CGI
|
|
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
if (g_pszIisEnv != NULL )
|
|
{
|
|
LocalFree( g_pszIisEnv );
|
|
g_pszIisEnv = NULL;
|
|
}
|
|
|
|
if ( g_pEnvEntries && g_pEnvEntries != CGIEnvTable )
|
|
{
|
|
delete [] g_pEnvEntries;
|
|
g_pEnvEntries = NULL;
|
|
}
|
|
fInitialized = FALSE;
|
|
}
|
|
|
|
BOOL
|
|
ProcessCGI(
|
|
SSI_REQUEST * pRequest,
|
|
const STR * pstrPath,
|
|
const STR * pstrURLParams,
|
|
const STR * pstrWorkingDir,
|
|
STR * pstrCmdLine
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Processes a CGI client request
|
|
|
|
Arguments:
|
|
|
|
pRequest - SSI_REQUEST struct used to access ISAPI utilities
|
|
pstrPath - Fully qualified path to executable (or NULL if the module
|
|
is contained in pstrCmdLine)
|
|
pstrURLParams - Parameter list for command (or NULL if the parms
|
|
are specified in pstrCmdLine)
|
|
strWorkingDir - Working directory for spawned process
|
|
(generally the web root). Can be NULL -> then CWD of command is used
|
|
pstrCmdLine - Optional command line to use instead of the default
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
STARTUPINFO startupinfo;
|
|
PROCESS_INFORMATION processinfo;
|
|
BUFFER buffEnv;
|
|
BOOL fRet = FALSE;
|
|
CGI_INFO * pCGIInfo = NULL;
|
|
DWORD dwThreadId;
|
|
HANDLE hThread;
|
|
STR strCmdLine;
|
|
DWORD dwFlags = DETACHED_PROCESS | CREATE_SEPARATE_WOW_VDM;
|
|
DWORD dwThreadCode;
|
|
|
|
TCP_ASSERT( fInitialized );
|
|
|
|
if ( !SetupChildEnv( pRequest,
|
|
&buffEnv ) )
|
|
{
|
|
LPCTSTR apszParms[ 1 ];
|
|
CHAR pszNumBuf[ SSI_MAX_NUMBER_STRING ];
|
|
_ultoa( GetLastError(), pszNumBuf, 10 );
|
|
apszParms[ 0 ] = pszNumBuf;
|
|
|
|
pRequest->SSISendError( SSINCMSG_CANT_SETUP_CHILD_ENV,
|
|
apszParms );
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if ( pstrCmdLine == NULL )
|
|
{
|
|
if ( !strCmdLine.Resize( SSI_MAX_PATH + 1 ) ||
|
|
!strCmdLine.Copy( "\"" ) ||
|
|
!strCmdLine.Append( pstrPath ? pstrPath->QueryStr() :
|
|
NULL ) ||
|
|
!strCmdLine.Append( "\" " ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if ( !SetupCmdLine( &strCmdLine,
|
|
*pstrURLParams ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
pstrCmdLine = &strCmdLine;
|
|
}
|
|
|
|
TCP_ASSERT( pstrCmdLine != NULL );
|
|
|
|
//
|
|
// Check to see if we're spawning cmd.exe, if so, refuse the request if
|
|
// there are any special shell characters. Note we do the check here
|
|
// so that the command line has been fully unescaped
|
|
//
|
|
|
|
if ( !fAllowSpecialCharsInShell )
|
|
{
|
|
DWORD i;
|
|
|
|
if ( strstr( pstrCmdLine->QueryStr(), "cmd.exe" ))
|
|
{
|
|
//
|
|
// We'll either match one of the characters or the '\0'
|
|
//
|
|
|
|
i = strcspn( pstrCmdLine->QueryStr(), "&|(,;%" );
|
|
|
|
if ( pstrCmdLine->QueryStr()[i] )
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[ProcessCGI] Refusing request for command shell due "
|
|
" to special characters\n" ));
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Setup the pipes information
|
|
//
|
|
|
|
pCGIInfo = new CGI_INFO( pRequest );
|
|
|
|
if ( !pCGIInfo )
|
|
{
|
|
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
|
|
return FALSE;
|
|
}
|
|
|
|
memset( &startupinfo, 0, sizeof(startupinfo) );
|
|
startupinfo.cb = sizeof(startupinfo);
|
|
|
|
//
|
|
// We specify an unnamed desktop so a new windowstation will be created
|
|
// in the context of the calling user
|
|
//
|
|
|
|
startupinfo.lpDesktop = "";
|
|
|
|
if ( !SetupChildPipes( &startupinfo,
|
|
&pCGIInfo->_hStdIn,
|
|
&pCGIInfo->_hStdOut ) )
|
|
{
|
|
LPCTSTR apszParms[ 1 ];
|
|
CHAR pszNumBuf[ SSI_MAX_NUMBER_STRING ];
|
|
_ultoa( GetLastError(), pszNumBuf, 10 );
|
|
apszParms[ 0 ] = pszNumBuf;
|
|
|
|
pRequest->SSISendError( SSINCMSG_CANT_SETUP_CHILD_PIPES,
|
|
apszParms );
|
|
goto Exit;
|
|
}
|
|
|
|
if ( fCreateProcessWithNewConsole )
|
|
{
|
|
dwFlags = CREATE_NEW_CONSOLE | CREATE_SEPARATE_WOW_VDM;
|
|
}
|
|
|
|
//
|
|
// Spawn the process and close the handles since we don't need them
|
|
//
|
|
|
|
TryAgain:
|
|
|
|
fRet = CreateProcess( pstrPath ? pstrPath->QueryStr() : NULL,
|
|
pstrCmdLine->QueryStr(),
|
|
NULL, // Process security
|
|
NULL, // Thread security
|
|
TRUE, // Inherit handles
|
|
dwFlags,
|
|
buffEnv.QueryPtr(),
|
|
pstrWorkingDir != NULL ?
|
|
pstrWorkingDir->QueryStr() : NULL,
|
|
&startupinfo,
|
|
&processinfo );
|
|
|
|
//
|
|
// If we get access denied this may be a 16 bit app, so try again w/o
|
|
// the process detached flag
|
|
//
|
|
|
|
if ( !fRet &&
|
|
(GetLastError() == ERROR_ACCESS_DENIED ||
|
|
GetLastError() == ERROR_INVALID_HANDLE ) &&
|
|
(dwFlags & DETACHED_PROCESS) )
|
|
{
|
|
dwFlags &= ~DETACHED_PROCESS;
|
|
goto TryAgain;
|
|
}
|
|
|
|
TCP_REQUIRE( CloseHandle( startupinfo.hStdOutput ) );
|
|
TCP_REQUIRE( CloseHandle( startupinfo.hStdInput ) );
|
|
|
|
if ( !fRet )
|
|
{
|
|
LPCTSTR apszParms[ 2 ];
|
|
CHAR pszNumBuf[ SSI_MAX_NUMBER_STRING ];
|
|
DWORD dwError = GetLastError();
|
|
_ultoa( dwError, pszNumBuf, 10 );
|
|
apszParms[ 0 ] = pstrCmdLine->QueryStr(),
|
|
apszParms[ 1 ] = pszNumBuf;
|
|
|
|
pRequest->SSISendError( SSINCMSG_CANT_CREATE_PROCESS,
|
|
apszParms );
|
|
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[ProcessCGI] Create process failed, error %d, exe = %s, cmd line = %s\n",
|
|
GetLastError(),
|
|
(pstrPath ? pstrPath->QueryStr() : "null"),
|
|
(pstrCmdLine ? pstrCmdLine->QueryStr() : "null") ));
|
|
goto Exit;
|
|
}
|
|
|
|
TCP_REQUIRE( CloseHandle( processinfo.hThread ) );
|
|
TCP_ASSERT( startupinfo.hStdError == startupinfo.hStdOutput);
|
|
|
|
//
|
|
// Save the process handle in case we need to terminate it later on
|
|
//
|
|
|
|
pCGIInfo->_hProcess = processinfo.hProcess;
|
|
|
|
//
|
|
// Create a thread to handle IO with the child process
|
|
//
|
|
|
|
if ( !(hThread = CreateThread( NULL,
|
|
0,
|
|
(LPTHREAD_START_ROUTINE) CGIThread,
|
|
pCGIInfo,
|
|
0,
|
|
&dwThreadId )))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Here is a crude method of finding runaway/hanging processes
|
|
//
|
|
|
|
DWORD dwWaitRes;
|
|
while ( !pCGIInfo->_fShutdown )
|
|
{
|
|
dwWaitRes = WaitForSingleObject( pCGIInfo->_hResponseEvent,
|
|
dwTimeOut );
|
|
if ( dwWaitRes == WAIT_OBJECT_0 )
|
|
{
|
|
// got signal in time
|
|
continue;
|
|
}
|
|
else if ( dwWaitRes == WAIT_TIMEOUT )
|
|
{
|
|
// process dead? Terminating should will cause thread to quit
|
|
TerminateProcess( processinfo.hProcess,
|
|
SSI_KILLED_PROCESS );
|
|
|
|
LPCTSTR apszParms[ 1 ];
|
|
apszParms[ 0 ] = pstrCmdLine->QueryStr();
|
|
|
|
pRequest->SSISendError( SSINCMSG_PROCESS_TIMEOUT,
|
|
apszParms );
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
WaitForSingleObject( hThread, INFINITE );
|
|
|
|
if ( !GetExitCodeThread( hThread,
|
|
&dwThreadCode ) ||
|
|
!dwThreadCode )
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
|
|
TCP_REQUIRE( CloseHandle( hThread ) );
|
|
|
|
Exit:
|
|
delete pCGIInfo;
|
|
|
|
return fRet;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: SetupChildPipes
|
|
|
|
SYNOPSIS: Creates/duplicates pipes for redirecting stdin and
|
|
stdout to a child process
|
|
|
|
ENTRY: pstartupinfo - pointer to startup info structure, receives
|
|
child stdin and stdout handles
|
|
phParentIn - Pipe to use for parent reading
|
|
phParenOut - Pipe to use for parent writing
|
|
|
|
RETURNS: TRUE if successful, FALSE on failure
|
|
|
|
HISTORY:
|
|
Johnl 22-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL SetupChildPipes( STARTUPINFO * pstartupinfo,
|
|
HANDLE * phParentIn,
|
|
HANDLE * phParentOut )
|
|
|
|
{
|
|
SECURITY_ATTRIBUTES sa;
|
|
|
|
*phParentIn = NULL;
|
|
*phParentOut = NULL;
|
|
|
|
sa.nLength = sizeof(sa);
|
|
sa.lpSecurityDescriptor = NULL;
|
|
sa.bInheritHandle = TRUE;
|
|
|
|
pstartupinfo->dwFlags = STARTF_USESTDHANDLES;
|
|
|
|
//
|
|
// Create the pipes then mark them as not inheritted in the
|
|
// DuplicateHandle to prevent handle leaks
|
|
//
|
|
|
|
if ( !CreatePipe( phParentIn,
|
|
&pstartupinfo->hStdOutput,
|
|
&sa,
|
|
0 ) ||
|
|
!DuplicateHandle( GetCurrentProcess(),
|
|
*phParentIn,
|
|
GetCurrentProcess(),
|
|
phParentIn,
|
|
0,
|
|
FALSE,
|
|
DUPLICATE_SAME_ACCESS |
|
|
DUPLICATE_CLOSE_SOURCE) ||
|
|
!CreatePipe( &pstartupinfo->hStdInput,
|
|
phParentOut,
|
|
&sa,
|
|
0 ) ||
|
|
!DuplicateHandle( GetCurrentProcess(),
|
|
*phParentOut,
|
|
GetCurrentProcess(),
|
|
phParentOut,
|
|
0,
|
|
FALSE,
|
|
DUPLICATE_SAME_ACCESS |
|
|
DUPLICATE_CLOSE_SOURCE ))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Stdout and Stderror will use the same pipe. If clients tend
|
|
// to close stderr, then we'll have to duplicate the handle
|
|
//
|
|
|
|
pstartupinfo->hStdError = pstartupinfo->hStdOutput;
|
|
|
|
return TRUE;
|
|
|
|
ErrorExit:
|
|
if ( *phParentIn )
|
|
TCP_REQUIRE( CloseHandle( *phParentIn ));
|
|
|
|
if ( *phParentOut )
|
|
TCP_REQUIRE( CloseHandle( *phParentOut ));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: SetupChildEnv
|
|
|
|
SYNOPSIS: Based on the passed pRequest, builds a CGI environment block
|
|
|
|
ENTRY: pRequest - HTTP request object
|
|
pBuff - Buffer to receive environment block
|
|
|
|
RETURNS: TRUE if successful, FALSE on failure
|
|
|
|
HISTORY:
|
|
Johnl 22-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL SetupChildEnv( SSI_REQUEST * pRequest,
|
|
BUFFER * pBuff )
|
|
{
|
|
TCHAR * pch, *pchtmp;
|
|
STR strVal;
|
|
UINT cchCurrentPos = 0; // Points to '\0' in buffer
|
|
UINT cchName, cchValue;
|
|
UINT cbNeeded;
|
|
int i = 0;
|
|
|
|
if ( !pBuff->Resize( 1500 * sizeof(TCHAR) ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Build the environment block for CGI
|
|
//
|
|
|
|
while ( g_pEnvEntries[i].m_pszName )
|
|
{
|
|
//
|
|
// Check if this is a copy entry from process environment
|
|
//
|
|
|
|
if ( g_pEnvEntries[i].m_cchToCopy )
|
|
{
|
|
if ( !pBuff->Resize( (cchCurrentPos + g_pEnvEntries[i].m_cchToCopy)
|
|
* sizeof(TCHAR) ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pch = (TCHAR *) pBuff->QueryPtr();
|
|
|
|
memcpy( pch + cchCurrentPos,
|
|
g_pEnvEntries[i].m_pszName,
|
|
g_pEnvEntries[i].m_cchToCopy );
|
|
|
|
cchCurrentPos += g_pEnvEntries[i].m_cchToCopy;
|
|
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// The NULL string means we're adding all of
|
|
// the HTTP header fields which requires a little
|
|
// bit of special processing
|
|
//
|
|
|
|
if ( !*g_pEnvEntries[i].m_pszName )
|
|
{
|
|
pch = "ALL_HTTP";
|
|
}
|
|
else
|
|
{
|
|
pch = g_pEnvEntries[i].m_pszName;
|
|
}
|
|
|
|
if ( !pRequest->GetVariable( pch,
|
|
&strVal ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
cchName = _tcslen( g_pEnvEntries[i].m_pszName );
|
|
cchValue = strVal.QueryCCH();
|
|
|
|
//
|
|
// We need space for the terminating '\0' and the '='
|
|
//
|
|
|
|
cbNeeded = ( cchName + cchValue + 1 + 1) * sizeof(TCHAR);
|
|
|
|
if ( !pBuff->Resize( cchCurrentPos * sizeof(TCHAR) + cbNeeded,
|
|
512 ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Replace the '\n' with a '\0' as needed
|
|
// for the HTTP headers
|
|
//
|
|
|
|
if ( !*g_pEnvEntries[i].m_pszName )
|
|
{
|
|
pchtmp = strVal.QueryStr();
|
|
|
|
//
|
|
// Convert the first ':' of each header to to an '=' for the
|
|
// environment table
|
|
//
|
|
|
|
while ( pchtmp = strchr( pchtmp, ':' ))
|
|
{
|
|
*pchtmp = '=';
|
|
|
|
if ( !(pchtmp = strchr( pchtmp, '\n' )))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
pchtmp = strVal.QueryStr();
|
|
|
|
while ( pchtmp = strchr( pchtmp+1, '\n' ))
|
|
{
|
|
*pchtmp = '\0';
|
|
}
|
|
}
|
|
|
|
pch = (TCHAR *) pBuff->QueryPtr();
|
|
|
|
if ( *g_pEnvEntries[i].m_pszName )
|
|
{
|
|
if ( strVal.QueryStr()[0] )
|
|
{
|
|
memcpy( pch + cchCurrentPos,
|
|
g_pEnvEntries[i].m_pszName,
|
|
cchName * sizeof(TCHAR));
|
|
|
|
*(pch + cchCurrentPos + cchName) = '=';
|
|
|
|
memcpy( pch + cchCurrentPos + cchName + 1,
|
|
strVal.QueryStr(),
|
|
(cchValue + 1) * sizeof(TCHAR));
|
|
|
|
cchCurrentPos += cchName + cchValue + 1 + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memcpy( pch + cchCurrentPos + cchName,
|
|
strVal.QueryStr(),
|
|
(cchValue + 1) * sizeof(TCHAR));
|
|
|
|
cchCurrentPos += cchName + cchValue;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
//
|
|
// Add a '\0' terminator to the environment list
|
|
//
|
|
|
|
if ( !pBuff->Resize( (cchCurrentPos + 1) * sizeof(TCHAR)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*((TCHAR *) pBuff->QueryPtr() + cchCurrentPos) = TEXT('\0');
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: SetupCmdLine
|
|
|
|
SYNOPSIS: Sets up a CGI command line
|
|
|
|
ENTRY: pstrCmdLine - Receives command line
|
|
strParams - Parameters following "?" in URL
|
|
|
|
HISTORY:
|
|
Johnl 04-Oct-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL SetupCmdLine( STR * pstrCmdLine,
|
|
const STR & strParams )
|
|
{
|
|
TCHAR * pch;
|
|
|
|
//
|
|
// If an unencoded "=" is found, don't use the command line
|
|
// (some weird CGI rule)
|
|
//
|
|
|
|
if ( _tcschr( strParams.QueryStr(),
|
|
TEXT('=') ))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Replace "+" with spaces and decode any hex escapes
|
|
//
|
|
|
|
if ( !pstrCmdLine->Append( strParams ) )
|
|
return FALSE;
|
|
|
|
while ( pch = _tcschr( pstrCmdLine->QueryStr(),
|
|
TEXT('+') ))
|
|
{
|
|
*pch = TEXT(' ');
|
|
pch++;
|
|
}
|
|
|
|
return pstrCmdLine->Unescape();
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: CGIThread
|
|
|
|
SYNOPSIS: Sends any gateway data to the scripts stdin and forwards
|
|
the script's stdout to the client through ISAPI
|
|
|
|
ENTRY: Param - Pointer to CGI_INFO structure
|
|
|
|
HISTORY:
|
|
Johnl 22-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
DWORD CGIThread( PVOID Param )
|
|
{
|
|
CGI_INFO * pCGIInfo = (CGI_INFO *) Param;
|
|
SSI_REQUEST * pRequest = pCGIInfo->_pRequest;
|
|
BYTE buff[2048];
|
|
DWORD cbRead;
|
|
DWORD dwExitCode;
|
|
DWORD err;
|
|
BOOL fReadHeaders = FALSE;
|
|
BOOL fRet = TRUE;
|
|
BOOL fSuccess = TRUE;
|
|
|
|
//
|
|
// Now wait for any data the child sends to its stdout or for the
|
|
// process to exit
|
|
//
|
|
|
|
//
|
|
// Handle input from child
|
|
//
|
|
|
|
while (TRUE)
|
|
{
|
|
fRet = ::ReadFile( pCGIInfo->_hStdIn,
|
|
buff,
|
|
sizeof(buff),
|
|
&cbRead,
|
|
NULL );
|
|
|
|
//
|
|
// Let the calling thread know that the process
|
|
// is still responding
|
|
//
|
|
|
|
SetEvent( pCGIInfo->_hResponseEvent );
|
|
|
|
if ( !fRet )
|
|
{
|
|
err = GetLastError();
|
|
if ( err == ERROR_BROKEN_PIPE )
|
|
{
|
|
break;
|
|
}
|
|
fSuccess = FALSE;
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[CGI_THREAD] ReadFile from child stdout failed, error %d, _hStdIn = %x\n",
|
|
GetLastError(),
|
|
pCGIInfo->_hStdIn));
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If no bytes were read, assume the file has been closed so
|
|
// get out
|
|
//
|
|
|
|
if ( !cbRead )
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// The CGI script can specify headers to include in the
|
|
// response. Wait till we receive all of the headers.
|
|
//
|
|
|
|
if ( !fReadHeaders )
|
|
{
|
|
if ( !ProcessCGIInput( pCGIInfo,
|
|
buff,
|
|
cbRead,
|
|
&fReadHeaders ) )
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[CGIThread] ProcessCGIInput failed with error %d\n",
|
|
GetLastError()));
|
|
fSuccess = FALSE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Either we are waiting for the rest of the header or
|
|
// we've sent the header and any residual data so wait
|
|
// for more data
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
if ( !pRequest->WriteToClient( buff,
|
|
cbRead,
|
|
&cbRead ) )
|
|
{
|
|
fSuccess = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we had to kill the process, log an error message
|
|
//
|
|
|
|
if ( GetExitCodeProcess( pCGIInfo->_hProcess,
|
|
&dwExitCode ) &&
|
|
dwExitCode == SSI_KILLED_PROCESS )
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[CGI_THREAD] - Spawned process hung\n" ));
|
|
fSuccess = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if ( !fReadHeaders )
|
|
{
|
|
DWORD cbSent;
|
|
|
|
//
|
|
// If we never read any headers, thats OK since were just
|
|
// including the CGI output into HTML stream.
|
|
//
|
|
|
|
if( !pRequest->WriteToClient( pCGIInfo->_Buff.QueryPtr(),
|
|
pCGIInfo->_cbData,
|
|
&cbSent ) )
|
|
{
|
|
fSuccess = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
pCGIInfo->_fShutdown = TRUE;
|
|
SetEvent( pCGIInfo->_hResponseEvent );
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: ProcessCGIInput
|
|
|
|
SYNOPSIS: Handles headers the CGI program hands back to the server
|
|
|
|
ENTRY: pCGIInfo - Pointer to CGI structure
|
|
buff - Pointer to data just read
|
|
cbRead - Number of bytes read into buff
|
|
pfReadHeaders - Set to TRUE after we've finished processing
|
|
all of the HTTP headers the CGI script gave us
|
|
|
|
HISTORY:
|
|
Johnl 22-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
|
|
BOOL ProcessCGIInput( CGI_INFO * pCGIInfo,
|
|
BYTE * buff,
|
|
DWORD cbRead,
|
|
BOOL * pfReadHeaders )
|
|
{
|
|
CHAR * pchValue;
|
|
CHAR * pchField;
|
|
BYTE * pbData;
|
|
DWORD cbData;
|
|
DWORD cbSent;
|
|
BOOL fFoundStatus = FALSE;
|
|
SSI_REQUEST * pRequest = pCGIInfo->_pRequest;
|
|
|
|
TCP_ASSERT( cbRead > 0 );
|
|
|
|
if ( !pCGIInfo->_Buff.Resize( pCGIInfo->_cbData + cbRead,
|
|
256 ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
memcpy( (BYTE *)pCGIInfo->_Buff.QueryPtr() + pCGIInfo->_cbData,
|
|
buff,
|
|
cbRead );
|
|
|
|
pCGIInfo->_cbData += cbRead;
|
|
|
|
//
|
|
// The end of CGI headers are marked by a blank line, check to see if
|
|
// we've hit that line
|
|
//
|
|
|
|
if ( !CheckForTermination( pfReadHeaders,
|
|
&pCGIInfo->_Buff,
|
|
pCGIInfo->_cbData,
|
|
&pbData,
|
|
&cbData,
|
|
256 ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !*pfReadHeaders )
|
|
return TRUE;
|
|
|
|
//
|
|
// We've found the end of the headers, process them and look for
|
|
// Location: xxxx
|
|
// URI: xxxx
|
|
//
|
|
// In both cases, send a redirect message to HTML stream
|
|
// For all other headers, simply ignore them (i.e. dont send them)
|
|
//
|
|
|
|
INET_PARSER Parser( (CHAR *) pCGIInfo->_Buff.QueryPtr() );
|
|
while ( *(pchField = Parser.QueryToken()) )
|
|
{
|
|
Parser.SkipTo( ':' );
|
|
Parser += 1;
|
|
pchValue = Parser.QueryToken();
|
|
|
|
if ( !::_strnicmp( "Location", pchField, 8 ) ||
|
|
!::_strnicmp( "URI", pchField, 3 ))
|
|
{
|
|
//
|
|
// The CGI script is redirecting us to another URL.
|
|
// Just insert into HTML stream a message indicating this
|
|
// redirection
|
|
//
|
|
|
|
STR strURL( pchValue );
|
|
STR strString;
|
|
STR strResp;
|
|
int iLen;
|
|
|
|
if ( !strString.LoadString( SSINCMSG_CGI_REDIRECT_RESPONSE,
|
|
SSI_DLL_NAME ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !strResp.Resize( SSI_MAX_ERROR_MESSAGE + 1 ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
iLen = _snprintf( strResp.QueryStr(),
|
|
SSI_MAX_ERROR_MESSAGE + 1,
|
|
strString.QueryStr(),
|
|
strURL.QueryStr() );
|
|
|
|
if ( !pRequest->WriteToClient( strResp.QueryStr(),
|
|
iLen,
|
|
(DWORD*) &iLen ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
Parser.NextLine();
|
|
}
|
|
|
|
//
|
|
// If there was additional data in the buffer, send that out now
|
|
//
|
|
|
|
if ( cbData )
|
|
{
|
|
if ( !pRequest->WriteToClient( pbData,
|
|
cbData,
|
|
&cbSent ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: ::CheckForTermination
|
|
|
|
SYNOPSIS: Looks in the passed buffer for a line followed by a blank
|
|
line. If not found, the buffer is resized.
|
|
|
|
ENTRY: pfTerminted - Set to TRUE if this block is terminated
|
|
pbuff - Pointer to buffer data
|
|
cbData - Size of pbuff
|
|
ppbExtraData - Receives a pointer to the first byte
|
|
of extra data following the header
|
|
pcbExtraData - Number of bytes in data following the header
|
|
cbReallocSize - Increase buffer by this number of bytes
|
|
if the terminate isn't found
|
|
|
|
RETURNS: TRUE if successful, FALSE otherwise
|
|
|
|
HISTORY:
|
|
Johnl 28-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL CheckForTermination( BOOL * pfTerminated,
|
|
BUFFER * pbuff,
|
|
UINT cbData,
|
|
BYTE * * ppbExtraData,
|
|
DWORD * pcbExtraData,
|
|
UINT cbReallocSize )
|
|
{
|
|
//
|
|
// Terminate the string but make sure it will fit in the
|
|
// buffer
|
|
//
|
|
|
|
if ( !pbuff->Resize(cbData + 1, cbReallocSize ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
CHAR * pchReq = (CHAR *) pbuff->QueryPtr();
|
|
*(pchReq + cbData) = '\0';
|
|
|
|
//
|
|
// Scan for double end of line marker
|
|
//
|
|
|
|
//
|
|
// if do not care for ptr info, can use fast method
|
|
//
|
|
|
|
if ( ppbExtraData == NULL )
|
|
{
|
|
if ( FastScanForTerminator( pchReq, cbData )
|
|
|| ScanForTerminator( pchReq ) )
|
|
{
|
|
*pfTerminated = TRUE;
|
|
return TRUE;
|
|
}
|
|
goto not_term;
|
|
}
|
|
|
|
*ppbExtraData = ScanForTerminator( pchReq );
|
|
|
|
if ( *ppbExtraData )
|
|
{
|
|
*pcbExtraData = cbData - (*ppbExtraData - (BYTE *) pchReq);
|
|
*pfTerminated = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
not_term:
|
|
|
|
*pfTerminated = FALSE;
|
|
|
|
//
|
|
// We didn't find the end so increase our buffer size
|
|
// in anticipation of more data
|
|
//
|
|
|
|
return pbuff->Resize( cbData + cbReallocSize );
|
|
}
|
|
|
|
BOOL
|
|
FastScanForTerminator(
|
|
CHAR * pch,
|
|
UINT cbData
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check if buffer contains a full HTTP header.
|
|
Can return false negatives.
|
|
|
|
Arguments:
|
|
|
|
pch - request buffer
|
|
cbData - # of bytes in pch, excluding trailing '\0'
|
|
|
|
Return Value:
|
|
|
|
TRUE if buffer contains a full HTTP header
|
|
FALSE if could not insure this, does not mean there is
|
|
no full HTTP header.
|
|
|
|
--*/
|
|
{
|
|
if ( cbData > 4 )
|
|
{
|
|
if ( !memcmp(pch+cbData-sizeof("\r\n\r\n")+1, "\r\n\r\n", sizeof("\r\n\r\n")-1 )
|
|
|| !memcmp(pch+cbData-sizeof("\n\n")+1, "\n\n", sizeof("\n\n")-1 ) )
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: ::SkipWhite
|
|
|
|
SYNOPSIS: Skips white space starting at the passed point in the string
|
|
and returns the next non-white space character.
|
|
|
|
HISTORY:
|
|
Johnl 23-Aug-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
CHAR * SkipWhite( CHAR * pch )
|
|
{
|
|
while ( ISWHITEA( *pch ) )
|
|
{
|
|
pch++;
|
|
}
|
|
|
|
return pch;
|
|
}
|
|
|
|
|
|
BYTE *
|
|
ScanForTerminator(
|
|
TCHAR * pch
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the first byte of data after the header
|
|
|
|
Arguments:
|
|
|
|
pch - Zero terminated buffer
|
|
|
|
Return Value:
|
|
|
|
Pointer to first byte of data after the header or NULL if the
|
|
header isn't terminated
|
|
|
|
--*/
|
|
{
|
|
while ( *pch )
|
|
{
|
|
if ( !(pch = strchr( pch, '\n' )))
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If we find an EOL, check if the next character is an EOL character
|
|
//
|
|
|
|
if ( *(pch = SkipWhite( pch + 1 )) == W3_EOL )
|
|
{
|
|
return (BYTE *) pch + 1;
|
|
}
|
|
else if ( *pch )
|
|
{
|
|
pch++;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|