/*++ Copyright (c) 2000 Microsoft Corporation Module Name: ssinc.cxx Abstract: This module contains the server side include processing code. We aim for support as specified by iis\spec\ssi.doc. The code is based on existing SSI support done in iis\svcs\w3\server\ssinc.cxx. Author: Ming Lu (MingLu) 10-Apr-2000 --*/ #include "precomp.hxx" // // Globals // UINT g_MonthToDayCount[] = { 0, 31, 31 + 28, 31 + 28 + 31, 31 + 28 + 31 + 30, 31 + 28 + 31 + 30 + 31, 31 + 28 + 31 + 30 + 31 + 30, 31 + 28 + 31 + 30 + 31 + 30 + 31, 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, } ; // // Prototypes // extern "C" { BOOL WINAPI DLLEntry( HINSTANCE hDll, DWORD dwReason, LPVOID lpvReserved ); } class SSI_ELEMENT_LIST; VOID InitializeSSIGlobals( VOID ); // // Global Data // DECLARE_DEBUG_PRINTS_OBJECT() DECLARE_DEBUG_VARIABLE(); // // This is the list of supported commands // struct _SSI_CMD_MAP { CHAR * pszCommand; DWORD cchCommand; SSI_COMMANDS ssiCmd; } SSICmdMap[] = { "#include ", 9, SSI_CMD_INCLUDE, "#echo ", 6, SSI_CMD_ECHO, "#fsize ", 7, SSI_CMD_FSIZE, "#flastmod ",10, SSI_CMD_FLASTMOD, "#config ", 8, SSI_CMD_CONFIG, "#exec ", 6, SSI_CMD_EXEC, NULL, 0, SSI_CMD_UNKNOWN }; // // This is the list of supported tags // struct _SSI_TAG_MAP { CHAR * pszTag; DWORD cchTag; SSI_TAGS ssiTag; } SSITagMap[] = { "var", 3, SSI_TAG_VAR, "file", 4, SSI_TAG_FILE, "virtual", 7, SSI_TAG_VIRTUAL, "errmsg", 6, SSI_TAG_ERRMSG, "timefmt", 7, SSI_TAG_TIMEFMT, "sizefmt", 7, SSI_TAG_SIZEFMT, "cmd", 3, SSI_TAG_CMD, "cgi", 3, SSI_TAG_CGI, "isa", 3, SSI_TAG_ISA, NULL, 0, SSI_TAG_UNKNOWN }; // // This is a list of #ECHO variables not supported by ISAPI // struct _SSI_VAR_MAP { CHAR * pszMap; DWORD cchMap; SSI_VARS ssiMap; } SSIVarMap[] = { "DOCUMENT_NAME", 13, SSI_VAR_DOCUMENT_NAME, "DOCUMENT_URI", 12, SSI_VAR_DOCUMENT_URI, "QUERY_STRING_UNESCAPED", 22, SSI_VAR_QUERY_STRING_UNESCAPED, "DATE_LOCAL", 10, SSI_VAR_DATE_LOCAL, "DATE_GMT", 8, SSI_VAR_DATE_GMT, "LAST_MODIFIED", 13, SSI_VAR_LAST_MODIFIED, NULL, 0, SSI_VAR_UNKNOWN }; // // #EXEC CMD is BAD. Disable it by default // BOOL fEnableCmdDirective = FALSE; // // Pointer to file cache // W3_FILE_INFO_CACHE * g_pFileCache = NULL; // // Class Definitions // // class SSI_FILE // // File structure. All high level functions should use this // structure instead of dealing with handle specifics themselves. class SSI_FILE { private: STRU _strFilename; W3_FILE_INFO * _hHandle; HANDLE _hMapHandle; PVOID _pvMappedBase; BOOL _fValid; BOOL _fCloseOnDestroy; // // Track the current number of open handles for this file. // DWORD _cRefCount; CRITICAL_SECTION _csRef; public: SSI_FILE( IN STRU * pstrFilename, IN W3_FILE_INFO * pOpenFile ) : _hHandle ( pOpenFile ), _hMapHandle ( NULL ), _pvMappedBase ( NULL ), _fValid ( FALSE ), _cRefCount ( 0), _fCloseOnDestroy( FALSE ) { InitializeCriticalSection( &_csRef ); if ( FAILED( _strFilename.Copy( pstrFilename->QueryStr() ) ) ) { return; } _fValid = TRUE; } SSI_FILE( IN STRU * pstrFilename, IN HANDLE hUser ) : _hHandle ( NULL ), _hMapHandle ( NULL ), _pvMappedBase ( NULL ), _fValid ( FALSE ), _cRefCount ( 0), _fCloseOnDestroy( TRUE ) { FILE_CACHE_USER fileUser; HRESULT hr; InitializeCriticalSection( &_csRef ); if ( FAILED( _strFilename.Copy( pstrFilename->QueryStr() ) ) ) { return; } fileUser._hToken = hUser; hr = g_pFileCache->GetFileInfo( _strFilename, NULL, &fileUser, TRUE, &_hHandle ); if ( FAILED( hr ) ) { return; } _fValid = TRUE; } ~SSI_FILE() { if ( _fCloseOnDestroy ) { if ( _hHandle ) { _hHandle->DereferenceCacheEntry(); } } DeleteCriticalSection( &_csRef ); } VOID Lock( VOID ) { EnterCriticalSection( &_csRef ); } VOID UnLock( VOID ) { LeaveCriticalSection( &_csRef ); } BOOL IsValid( VOID ) const { return _fValid; } PSECURITY_DESCRIPTOR GetSecDesc( VOID ) const { return _hHandle->QuerySecDesc(); } BOOL SSICreateFileMapping( VOID ) /*++ Creates a mapping to a file --*/ { HANDLE hHandle; if ( _hHandle->QueryFileBuffer() ) { return TRUE; } hHandle = _hHandle->QueryFileHandle(); if ( _hMapHandle != NULL ) { if ( !SSICloseMapHandle() ) { return FALSE; } } _hMapHandle = ::CreateFileMapping( hHandle, NULL, PAGE_READONLY, 0, 0, NULL ); if ( _hMapHandle == NULL ) { DBGPRINTF(( DBG_CONTEXT, "CreateFileMapping failed with %d\n", GetLastError() )); } return _hMapHandle != NULL; } BOOL SSICloseMapHandle( VOID ) /*++ Closes mapping to a file --*/ { if ( _hMapHandle != NULL ) { ::CloseHandle( _hMapHandle ); _hMapHandle = NULL; } return TRUE; } BOOL SSIMapViewOfFile( VOID ) /*++ Maps file to address --*/ { if ( _hHandle->QueryFileBuffer() ) { _pvMappedBase = _hHandle->QueryFileBuffer(); return TRUE; } if ( _pvMappedBase != NULL ) { if ( !SSIUnmapViewOfFile() ) { return FALSE; } } _pvMappedBase = ::MapViewOfFile( _hMapHandle, FILE_MAP_READ, 0, 0, 0 ); if ( _pvMappedBase == NULL ) { DBGPRINTF(( DBG_CONTEXT, "MapViewOfFile() failed with %d\n", GetLastError() )); } return _pvMappedBase != NULL; } BOOL SSIUnmapViewOfFile( VOID ) /*++ Unmaps file --*/ { if ( !_hHandle->QueryFileBuffer() && _pvMappedBase != NULL ) { ::UnmapViewOfFile( _pvMappedBase ); _pvMappedBase = NULL; } return TRUE; } DWORD SSIGetFileAttributes( VOID ) /*++ Gets the attributes of a file --*/ { return _hHandle->QueryAttributes(); } BOOL SSIGetFileSize( OUT DWORD * pdwLowWord, OUT DWORD * pdwHighWord ) /*++ Gets the size of the file. --*/ { LARGE_INTEGER liSize; _hHandle->QuerySize( &liSize ); *pdwLowWord = liSize.LowPart; *pdwHighWord = liSize.HighPart; return TRUE; } BOOL SSIGetLastModTime( OUT FILETIME * ftTime ) /*++ Gets the Last modification time of a file. --*/ { _hHandle->QueryLastWriteTime( ftTime ); return TRUE; } PVOID GetMappedBase( VOID ) { return _pvMappedBase; } STRU & GetFilename( VOID ) { return _strFilename; } }; // Class SSI_ELEMENT_ITEM // // Represents a SSI command or block of static text in the document class SSI_ELEMENT_ITEM { private: DWORD _Signature; SSI_COMMANDS _ssiCmd; SSI_TAGS _ssiTag; STRA * _pstrTagValue; DWORD _cbBegin; // Only used for Byte range command DWORD _cbLength; // Only used for Byte range command public: LIST_ENTRY _ListEntry; SSI_ELEMENT_ITEM( VOID ) : _ssiCmd ( SSI_CMD_UNKNOWN ), _ssiTag ( SSI_TAG_UNKNOWN ), _Signature( SIGNATURE_SEI ), _pstrTagValue( NULL ) { _ListEntry.Flink = NULL; } ~SSI_ELEMENT_ITEM() { if ( _pstrTagValue != NULL ) { delete _pstrTagValue; } DBG_ASSERT( _ListEntry.Flink == NULL ); _Signature = SIGNATURE_SEI_FREE; } VOID SetByteRange( IN DWORD cbBegin, IN DWORD cbLength ) { _ssiCmd = SSI_CMD_BYTERANGE; _cbBegin = cbBegin; _cbLength = cbLength; } BOOL SetCommand( IN SSI_COMMANDS ssiCmd, IN SSI_TAGS ssiTag, IN CHAR * achTag ) { _ssiCmd = ssiCmd; _ssiTag = ssiTag; _pstrTagValue = new STRA(); if( _pstrTagValue == NULL ) { return FALSE; } if( FAILED( _pstrTagValue->Copy( achTag ) ) ) { return FALSE; } return TRUE; } SSI_COMMANDS QueryCommand( VOID ) const { return _ssiCmd; } SSI_TAGS QueryTag( VOID ) const { return _ssiTag; } STRA * QueryTagValue( VOID ) const { return _pstrTagValue; } BOOL CheckSignature( VOID ) const { return _Signature == SIGNATURE_SEI; } DWORD QueryBegin( VOID ) const { return _cbBegin; } DWORD QueryLength( VOID ) const { return _cbLength; } }; // Class SSI_ELEMENT_LIST // // This object sits as a cache blob under a file to be processed as a // server side include. It represents an interpreted list of data // elements that make up the file itself. // class SSI_ELEMENT_LIST : public ASSOCIATED_FILE_OBJECT { private: DWORD _Signature; LIST_ENTRY _ListHead; // // These are for tracking the memory mapped file // DWORD _cRefCount; CRITICAL_SECTION _csRef; // // Provides the utilities needed to open/manipulate files // SSI_FILE * _pssiFile; // // Name of URL. Used to resolve FILE="xxx" filenames // STRU _strURL; public: SSI_ELEMENT_LIST() : _Signature ( SIGNATURE_SEL ), _pssiFile( NULL ), _cRefCount( 0 ) { InitializeListHead( &_ListHead ); INITIALIZE_CRITICAL_SECTION( &_csRef ); } ~SSI_ELEMENT_LIST() { SSI_ELEMENT_ITEM * pSEI; while ( !IsListEmpty( &_ListHead )) { pSEI = CONTAINING_RECORD( _ListHead.Flink, SSI_ELEMENT_ITEM, _ListEntry ); RemoveEntryList( &pSEI->_ListEntry ); pSEI->_ListEntry.Flink = NULL; delete pSEI; } UnMap(); if( _pssiFile != NULL ) { delete _pssiFile; } DeleteCriticalSection( &_csRef ); _Signature = SIGNATURE_SEL_FREE; } VOID Cleanup( VOID ) { delete this; } LIST_ENTRY * QueryListHead( VOID ) { return &_ListHead; }; BOOL AppendByteRange( IN DWORD cbStart, IN DWORD cbLength ) { SSI_ELEMENT_ITEM * pSEI; pSEI = new SSI_ELEMENT_ITEM; if ( !pSEI ) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return FALSE; } pSEI->SetByteRange( cbStart, cbLength ); AppendItem( pSEI ); return TRUE; } BOOL AppendCommand( IN SSI_COMMANDS ssiCmd, IN SSI_TAGS ssiTag, IN CHAR * pszTag ) { SSI_ELEMENT_ITEM * pSEI; pSEI = new SSI_ELEMENT_ITEM; if ( !pSEI ) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return FALSE; } if ( !pSEI->SetCommand( ssiCmd, ssiTag, pszTag )) { return FALSE; } AppendItem( pSEI ); return TRUE; } VOID AppendItem( IN SSI_ELEMENT_ITEM * pSEI ) { InsertTailList( &_ListHead, &pSEI->_ListEntry ); } CHAR * QueryData( VOID ) const { return ( CHAR * ) _pssiFile->GetMappedBase(); } PSECURITY_DESCRIPTOR QuerySecDesc( VOID ) { return _pssiFile->GetSecDesc(); } BOOL CheckSignature( VOID ) const { return _Signature == SIGNATURE_SEL; } VOID Lock( VOID ) { EnterCriticalSection( &_csRef ); } VOID UnLock( VOID ) { LeaveCriticalSection( &_csRef ); } BOOL UnMap( VOID ) { Lock(); if ( _cRefCount && !--_cRefCount ) { DBG_REQUIRE( _pssiFile->SSIUnmapViewOfFile() ); DBG_REQUIRE( _pssiFile->SSICloseMapHandle() ); } UnLock(); return TRUE; } BOOL Map( VOID ) { Lock(); if ( _cRefCount++ == 0 ) { if ( !_pssiFile->SSICreateFileMapping() ) { UnLock(); return FALSE; } if ( !_pssiFile->SSIMapViewOfFile() ) { UnMap(); UnLock(); return FALSE; } } UnLock(); return TRUE; } VOID SetFile( IN SSI_FILE * pssiFile ) { _pssiFile = pssiFile; } SSI_FILE * GetFile( VOID ) { return _pssiFile; } HRESULT SetURL( IN STRU * pstrURL ) { return _strURL.Copy( pstrURL->QueryStr() ); } static BOOL SSIFreeContextRoutine( VOID * pvContext ) { DBG_ASSERT( ( ( SSI_ELEMENT_LIST * ) pvContext) -> CheckSignature() ); delete ( SSI_ELEMENT_LIST * ) pvContext; return TRUE; } }; // // SSI_REQUEST methods implementation // //static ALLOC_CACHE_HANDLER * SSI_REQUEST::sm_pachSSIRequests = NULL; SSI_REQUEST::SSI_REQUEST( EXTENSION_CONTROL_BLOCK * pECB ) : _pECB( pECB ), _fBaseFile( TRUE ), _fValid( FALSE ), _pchErrorBuff ( NULL ) /*++ Routine Description: Constructor --*/ { DBG_ASSERT( _pECB != NULL ); STACK_BUFFER (buffTemp, 512); DWORD cbSize = buffTemp.QuerySize(); if (!_pECB->GetServerVariable(_pECB->ConnID, "UNICODE_URL", buffTemp.QueryPtr(), &cbSize)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || !buffTemp.Resize(cbSize)) { return; } // // Now, we should have enough buffer, try again // if (!_pECB->GetServerVariable(_pECB->ConnID, "UNICODE_URL", buffTemp.QueryPtr(), &cbSize)) { return; } } if (FAILED(_strURL.Copy( (LPWSTR)buffTemp.QueryPtr() ))) { return; } cbSize = buffTemp.QuerySize(); if (!_pECB->GetServerVariable(_pECB->ConnID, "UNICODE_SCRIPT_TRANSLATED", buffTemp.QueryPtr(), &cbSize)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || !buffTemp.Resize(cbSize)) { return; } // // Now, we should have enough buffer, try again // if (!_pECB->GetServerVariable(_pECB->ConnID, "UNICODE_SCRIPT_TRANSLATED", buffTemp.QueryPtr(), &cbSize)) { return; } } if (FAILED(_strFilename.Copy( (LPWSTR)buffTemp.QueryPtr() ))) { return; } if ( !_pECB->ServerSupportFunction( _pECB->ConnID, HSE_REQ_GET_IMPERSONATION_TOKEN, &_hUser, NULL, NULL ) ) { return; } _pSIF = new SSI_INCLUDE_FILE ( this, _strFilename, _strURL, NULL ); if ( _pSIF == NULL || !_pSIF->IsValid() ) { return; } if( !_IOBuffer.Resize(MAX_PATH) ) { return; } if( FAILED( _IOString.Resize(MAX_PATH) ) ) { return; } _fValid = TRUE; } SSI_REQUEST::~SSI_REQUEST() /*++ Routine Description: Destructor --*/ { if ( _pSIF != NULL ) { // // There should be no nested stm files // State Machine was supposed to complete and cleanup child SSI_INCLUDE_FILES // (also in the case of error) // DBG_ASSERT( _pSIF->GetParent() == NULL ); delete _pSIF; } if ( _pchErrorBuff != NULL ) { ::LocalFree( ( VOID * )_pchErrorBuff ); } } HRESULT SSI_REQUEST::SSISendError( IN DWORD dwMessageID, IN LPSTR apszParms[] ) /*++ Routine Description: Send an SSI error Arguments: dwMessageId - Message ID apszParms - Array of parameters Return Value: HRESULT (if couldn't find a custom error, this will fail) --*/ { DWORD cbSent; if ( *( ( CHAR * )_strUserMessage.QueryStr() ) != '\0' ) { // // user specified message with #CONFIG ERRMSG= // return WriteToClient( _strUserMessage.QueryStr(), _strUserMessage.QueryCCH(), &cbSent ); } else { DWORD cch; HRESULT hr = E_FAIL; CHAR chTemp1 = '\0'; CHAR chTemp2 = '\0'; if( _pchErrorBuff ) { ::LocalFree( ( VOID * )_pchErrorBuff ); _pchErrorBuff = NULL; } // // Lame. I need to validate the parameters for being <1024 // otherwise FormatMessage uses SEH to determine when to resize and // this will cause debuggers to break on the 1st change exception // if ( apszParms[ 0 ] != NULL && strlen( apszParms[ 0 ] ) > SSI_MAX_FORMAT_LEN ) { chTemp1 = apszParms[ 0 ][ SSI_MAX_FORMAT_LEN ]; apszParms[ 0 ][ SSI_MAX_FORMAT_LEN ] = '\0'; } if ( apszParms[ 1 ] != NULL && strlen( apszParms[ 1 ] ) > SSI_MAX_FORMAT_LEN ) { chTemp2 = apszParms[ 1 ][ SSI_MAX_FORMAT_LEN ]; apszParms[ 1 ][ SSI_MAX_FORMAT_LEN ] = '\0'; } cch = ::FormatMessageA( FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandle( SSI_DLL_NAME ), dwMessageID, 0, ( LPSTR ) &_pchErrorBuff, 0, ( va_list *) apszParms ); if ( chTemp1 != '\0' ) { apszParms[ 0 ][ SSI_MAX_FORMAT_LEN ] = chTemp1; } if ( chTemp2 != '\0' ) { apszParms[ 1 ][ SSI_MAX_FORMAT_LEN ] = chTemp2; } if( cch != 0 ) { // // WriteToClient will execute asynchronously so do not // free _pchErrorBuffer before I/O completion // hr = WriteToClient( _pchErrorBuff, cch, &cbSent ); return hr; } else { return HRESULT_FROM_WIN32(GetLastError()); } } } HRESULT SSI_REQUEST::SendCustomError( HSE_CUSTOM_ERROR_INFO * pCustomErrorInfo ) /*++ Routine Description: Try to have IIS send custom error on our behalf Arguments: pCustomErrorInfo - Describes custom error Return Value: HRESULT (if couldn't find a custom error, this will fail) --*/ { BOOL fRet; fRet = _pECB->ServerSupportFunction( _pECB->ConnID, HSE_REQ_SEND_CUSTOM_ERROR, pCustomErrorInfo, NULL, NULL ); if ( !fRet ) { return HRESULT_FROM_WIN32( GetLastError() ); } else { return NO_ERROR; } } HRESULT SSI_REQUEST::DoFLastMod( IN STRU * pstrFilename, IN STRA * pstrTimeFmt, IN SSI_ELEMENT_LIST * pList ) /*++ Routine Description: Send the LastModTime of file to HTML stream Arguments: pstrFilename - Filename pstrTimeFmt - Format of time -> follows strftime() convention Return Value: HRESULT --*/ { FILETIME ftTime; FILETIME ftLocalTime; SYSTEMTIME sysLocal; if ( ( NULL == pList ) || wcscmp( pstrFilename->QueryStr(), ( pList->GetFile()->GetFilename().QueryStr() ) ) ) { SSI_FILE ssiFile( pstrFilename, GetUserToken() ); if ( !ssiFile.IsValid() || ( !ssiFile.SSIGetLastModTime( &ftTime )) ) { return E_FAIL; } } else { pList->GetFile()->SSIGetLastModTime( &ftTime ); } if ( ( !FileTimeToLocalFileTime( &ftTime, &ftLocalTime ) ) || ( !FileTimeToSystemTime( &ftLocalTime, &sysLocal ) ) ) { return E_FAIL; } return SendDate( &sysLocal, pstrTimeFmt ); } HRESULT SSI_REQUEST::SendDate( IN SYSTEMTIME * psysTime, IN STRA * pstrTimeFmt ) /*++ Routine Description: Sends a SYSTEMTIME in appropriate format to HTML stream Arguments: psysTime - SYSTEMTIME containing time to send pstrTimeFmt - Format of time (follows strftime() convention) fCalcDays - TRUE if days since the beginning of the year should be calculated Return Value: HRESULT --*/ { struct tm tm; // Convert SYSTEMTIME to 'struct tm' tm.tm_sec = psysTime->wSecond; tm.tm_min = psysTime->wMinute; tm.tm_hour = psysTime->wHour; tm.tm_mday = psysTime->wDay; tm.tm_mon = psysTime->wMonth - 1; tm.tm_year = psysTime->wYear - 1900; tm.tm_wday = psysTime->wDayOfWeek; tm.tm_yday = g_MonthToDayCount[ tm.tm_mon ] + tm.tm_mday - 1; // // Adjust for leap year - note that we do not handle 2100 // if ( ( tm.tm_mon ) > 1 && !( psysTime->wYear & 3 ) ) { ++tm.tm_yday; } tm.tm_isdst = -1; // Daylight savings time flag - have crt compute _cbTimeBufferLen = strftime( _achTimeBuffer, SSI_MAX_TIME_SIZE + 1, ( CHAR * )pstrTimeFmt->QueryStr(), &tm ); if ( _cbTimeBufferLen == 0 ) { return E_FAIL; } return WriteToClient( _achTimeBuffer, _cbTimeBufferLen, &_cbTimeBufferLen ); } HRESULT SSI_REQUEST::LookupVirtualRoot( IN WCHAR * pszVirtual, OUT STRU * pstrPhysical, IN DWORD dwAccess ) /*++ Routine Description: Lookup the given virtual path. Optionally ensure that its access flags are valid for the require request. Arguments: pszVirtual = Virtual path to lookup pstrPhysical = Contains the physical path dwAccess = Access flags required for a valid request Return Value: HRESULT --*/ { HSE_URL_MAPEX_INFO URLMap; DWORD dwMask; STACK_STRA (strURL, 128); HRESULT hr = E_FAIL; // // ServerSupportFunction doesn't accept unicode strings. Convert // if ( FAILED(hr = strURL.CopyW(pszVirtual))) { return hr; } if ( !_pECB->ServerSupportFunction( _pECB->ConnID, HSE_REQ_MAP_URL_TO_PATH_EX, strURL.QueryStr(), NULL, (PDWORD) &URLMap ) ) { return HRESULT_FROM_WIN32(GetLastError()); } dwMask = URLMap.dwFlags; if ( dwAccess & HSE_URL_FLAGS_READ ) { // // BUGBUG-MING: Add IsSecurePort() // /* if ( !( dwMask & HSE_URL_FLAGS_READ ) || ( ( dwMask & HSE_URL_FLAGS_SSL ) && !_pReq->IsSecurePort() ) ) */ if ( !( dwMask & HSE_URL_FLAGS_READ ) || ( dwMask & HSE_URL_FLAGS_SSL ) ) { return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); } } if( FAILED( pstrPhysical->CopyA( URLMap.lpszPath ) ) ) { return HRESULT_FROM_WIN32(GetLastError()); } return NO_ERROR; } HRESULT SSI_REQUEST::DoEchoISAPIVariable( IN STRA * pstrVariable ) /*++ Routine Description: Get ISAPI variable and if successful, send it to HTML stream Arguments: pstrVariable - Variable Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; if ( FAILED( hr = GetVariable( ( CHAR * )pstrVariable->QueryStr(), &_IOBuffer ) ) ) { return hr; } return WriteToClient( _IOBuffer.QueryPtr(), strlen( ( CHAR * )_IOBuffer.QueryPtr() ), &_cbIOLen ); } HRESULT SSI_REQUEST::DoEchoDateLocal( IN STRA * pstrTimeFmt ) /*++ Routine Description: Sends local time (#ECHO VAR="DATE_LOCAL") Arguments: pstrTimefmt - Format of time (follows strftime() convention) Return Value: HRESULT --*/ { SYSTEMTIME sysTime; ::GetLocalTime( &sysTime ); return SendDate( &sysTime, pstrTimeFmt ); } HRESULT SSI_REQUEST::DoEchoDateGMT( IN STRA * pstrTimeFmt ) /*++ Routine Description: Sends GMT time (#ECHO VAR="DATE_GMT") Arguments: pstrTimefmt - Format of time (follows strftime() convention) Return Value: HRESULT --*/ { SYSTEMTIME sysTime; ::GetSystemTime( &sysTime ); return SendDate( &sysTime, pstrTimeFmt ); } HRESULT SSI_REQUEST::DoEchoDocumentName( IN STRU * pstrFilename ) /*++ Routine Description: Sends filename of current SSI document (#ECHO VAR="DOCUMENT_NAME") Arguments: pstrFilename - filename to print Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; if ( FAILED( hr = (_IOString.CopyW(pstrFilename->QueryStr() ) ) ) ) { return hr; } return WriteToClient( _IOString.QueryStr(), _IOString.QueryCCH(), &_cbIOLen); } HRESULT SSI_REQUEST::DoEchoDocumentURI( IN STRU * pstrURL ) /*++ Routine Description: Sends URL of current SSI document (#ECHO VAR="DOCUMENT_URI") Arguments: pstrURL - URL to print Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; if ( FAILED( hr = _IOString.CopyW(pstrURL->QueryStr() ) ) ) { return hr; } return WriteToClient( _IOString.QueryStr(), _IOString.QueryCCH(), &_cbIOLen ); } HRESULT SSI_REQUEST::DoEchoQueryStringUnescaped( VOID ) /*++ Routine Description: Sends unescaped querystring to HTML stream (#ECHO VAR="QUERY_STRING_UNESCAPED") Arguments: none Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; if ( FAILED( hr = _IOString.Copy( _pECB->lpszQueryString ) ) ) { return hr; } if ( FAILED( hr = _IOString.Unescape()) ) { return hr; } return WriteToClient( _IOString.QueryStr(), _IOString.QueryCCH(), &_cbIOLen ); } HRESULT SSI_REQUEST::DoEchoLastModified( IN STRU * pstrFilename, IN STRA * pstrTimeFmt, IN SSI_ELEMENT_LIST * pList ) /*++ Routine Description: Sends LastModTime of current document (#ECHO VAR="LAST_MODIFIED") Arguments: pstrFilename - Filename of current SSI document pstrTimeFmt - Time format (follows strftime() convention) Return Value: HRESULT --*/ { return DoFLastMod( pstrFilename, pstrTimeFmt, pList); } HRESULT SSI_REQUEST::DoFSize( IN STRU * pstrFilename, IN BOOL bSizeFmtBytes, IN SSI_ELEMENT_LIST * pList ) /*++ Routine Description: Sends file size of file to HTML stream Arguments: pstrfilename - Filename bSizeFmtBytes - TRUE if count is in Bytes, FALSE if in KBytes Return Value: HRESULT --*/ { BOOL bRet; DWORD cbSizeLow; DWORD cbSizeHigh; WCHAR achInputNumber[ SSI_MAX_NUMBER_STRING + 1 ]; WCHAR achOutputNumber[ SSI_MAX_NUMBER_STRING + 1 ]; NUMBERFMT nfNumberFormat; int iOutputSize; DWORD dwActualLen; HRESULT hr = E_FAIL; if ( ( NULL == pList ) || wcscmp( pstrFilename->QueryStr(), ( pList->GetFile()->GetFilename().QueryStr() ) ) ) { SSI_FILE ssiFile( pstrFilename, GetUserToken() ); if ( !ssiFile.IsValid() || ( !ssiFile.SSIGetFileSize( &cbSizeLow, &cbSizeHigh ) ) ) { return E_FAIL; } } else { if (!pList->GetFile()->SSIGetFileSize( &cbSizeLow, &cbSizeHigh ) ) { return E_FAIL; } } if ( cbSizeHigh ) { //BUGBUG-jaro: do we ignore extra large files intentionaly? return E_FAIL; } if ( !bSizeFmtBytes ) { // express in terms of KB cbSizeLow /= 1000; } nfNumberFormat.NumDigits = 0; nfNumberFormat.LeadingZero = 0; nfNumberFormat.Grouping = 3; nfNumberFormat.lpThousandSep = L","; nfNumberFormat.lpDecimalSep = L"."; nfNumberFormat.NegativeOrder = 2; _snwprintf( achInputNumber, SSI_MAX_NUMBER_STRING + 1, L"%ld", cbSizeLow ); iOutputSize = GetNumberFormat( LOCALE_SYSTEM_DEFAULT, 0, achInputNumber, &nfNumberFormat, achOutputNumber, SSI_MAX_NUMBER_STRING + 1 ); if ( !iOutputSize ) { return HRESULT_FROM_WIN32(GetLastError()); } // // Do not count trailing '\0' // iOutputSize--; // // Convert from Unicode before sending to client // if ( FAILED( hr = _IOString.CopyW(achOutputNumber) ) ) { return hr; } return WriteToClient( _IOString.QueryStr(), iOutputSize, &_cbIOLen ); } HRESULT SSI_REQUEST::PrepareSSI( VOID ) /*++ Routine Description: Prepare esential data structures Arguments: none Return Value: HRESULT --*/ { return _pSIF->Prepare(); } HRESULT SSI_REQUEST::DoWork( DWORD dwError ) /*++ Routine Description: This is the top level routine for retrieving a server side include file. Arguments: dwError - error of the last asynchronous operation (the one received by completion routine) Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; DBG_ASSERT ( _pSIF != NULL ); while( _pSIF != NULL ) { // // In the case that dwError != NO_ERROR // _pSIF->DoWork() may be called multiple times to unwind state machine // We will pass the same dwError in the case of multiple calls // since that error is the primary reason why processing of this request // must finish // hr = _pSIF->DoWork(dwError); if ( hr == HRESULT_FROM_WIN32(ERROR_IO_PENDING) ) { // // If there is pending IO return to caller // return hr; } else { // // Either SSI_INCLUDE_FILE processing completed // or there is nested include // // In the case of error this block is used to unwind state machine // if ( _pSIF->IsCompleted() ) { // // SSI_INCLUDE_FILE processing completed // Cleanup and if there is parent SSI_INCLUDE_FILE continue with that one // SSI_INCLUDE_FILE * pParent = _pSIF->GetParent(); delete _pSIF; _pSIF = pParent; } else { // // Current SSI_INCLUDE_FILE _pSIF hasn't been completed yet. Continue // } } } return hr; } //static VOID WINAPI SSI_REQUEST::HseIoCompletion( IN EXTENSION_CONTROL_BLOCK * pECB, IN PVOID pContext, IN DWORD cbIO, IN DWORD dwError ) /*++ Routine Description: This is the callback function for handling completions of asynchronous IO. This function performs necessary cleanup and resubmits additional IO (if required). Arguments: pecb pointer to ECB containing parameters related to the request. pContext context information supplied with the asynchronous IO call. cbIO count of bytes of IO in the last call. dwError Error if any, for the last IO operation. Return Value: None. --*/ { SSI_REQUEST * pRequest = (SSI_REQUEST *) pContext; HRESULT hr = E_FAIL; // // Continue processing SSI file // hr = pRequest->DoWork(dwError); if ( hr == HRESULT_FROM_WIN32(ERROR_IO_PENDING) ) { // // pending IO operation // return; } // // Processing of current SSI request completed // Do Cleanup // delete pRequest; // // Notify IIS that we are done with processing // pECB->ServerSupportFunction( pECB->ConnID, HSE_REQ_DONE_WITH_SESSION, NULL, NULL, NULL); return; } // // SSI_INCLUDE_FILE methods implementation // //static ALLOC_CACHE_HANDLER * SSI_INCLUDE_FILE::sm_pachSSI_IncludeFiles = NULL; SSI_INCLUDE_FILE::~SSI_INCLUDE_FILE( VOID ) /*++ Routine Description: Destructor --*/ { if ( !_fSELCached && _pSEL ) { delete _pSEL; _pSEL = NULL; } if ( _pOpenFile ) { _pOpenFile->DereferenceCacheEntry(); _pOpenFile = NULL; } } HRESULT SSI_INCLUDE_FILE::Prepare( VOID ) /*++ Routine Description: This method builds the Server Side Include Element List the first time a .stm file is sent. Subsequently, the element list is checked out from the associated cache blob. Note: The HTTP headers have already been sent at this point so for any subsequent non-catastrophic errors, we have to insert them into the output stream. Arguments: Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; DWORD dwError; LPSTR apszParms[ 2 ] = { NULL, NULL }; CHAR pszNumBuf[ SSI_MAX_NUMBER_STRING ]; FILE_CACHE_USER fileUser; HSE_CUSTOM_ERROR_INFO customErrorInfo; DBG_ASSERT( _State == SIF_STATE_INITIALIZED ); DBG_ASSERT( _pRequest != NULL ); fileUser._hToken = _pRequest->GetUserToken(); hr = g_pFileCache->GetFileInfo( _strFilename, NULL, &fileUser, TRUE, &_pOpenFile ); if ( FAILED( hr ) ) { goto failed; } // // The source file is in the cache. Check whether we have // associated a SSI_ELEMENT_LIST with it. // _fFileCached = _pOpenFile->QueryFileBuffer() != NULL; _pSEL = ( SSI_ELEMENT_LIST * )_pOpenFile->QueryAssociatedObject(); if ( _pSEL ) { _fSELCached = TRUE; } else { // // build SSI_ELEMENT_LIST _pSEL // hr = BuildSEL(); if ( FAILED( hr ) ) { goto failed; } } // // Only bother to cache SEL if the file is cached // if ( !_fSELCached && _fFileCached ) { if ( !_pOpenFile->SetAssociatedObject( _pSEL ) ) { delete _pSEL; _pSEL = (SSI_ELEMENT_LIST*) _pOpenFile->QueryAssociatedObject(); if ( !_pSEL ) { DBG_ASSERT( FALSE ); hr = E_FAIL; goto failed; } else { _fSELCached = TRUE; } } else { _fSELCached = TRUE; } } // // adjust State // SetState( SIF_STATE_READY ); // // If we got this far and this is the base file, we can send the // 200 OK // if ( IsBaseFile() ) { return _pRequest->SendResponseHeader( NULL, SSI_HEADER ); } else { return NO_ERROR; } failed: dwError = WIN32_FROM_HRESULT(hr); if ( IsBaseFile() ) { // // First try to have IIS send custom error // switch( dwError ) { case ERROR_ACCESS_DENIED: customErrorInfo.pszStatus = "401 Access Denied"; customErrorInfo.uHttpSubError = MD_ERROR_SUB401_LOGON_ACL; customErrorInfo.fAsync = FALSE; hr = _pRequest->SendCustomError( &customErrorInfo ); break; case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: customErrorInfo.pszStatus = "404 Object Not Found"; customErrorInfo.uHttpSubError = 0; customErrorInfo.fAsync = FALSE; hr = _pRequest->SendCustomError( &customErrorInfo ); break; default: hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); } // // If IIS could not send custom error, then send our own legacy // error response // if ( hr == HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ) ) { switch( dwError ) { case ERROR_ACCESS_DENIED: _pRequest->SendResponseHeader( SSI_ACCESS_DENIED, SSI_HEADER "