10712 lines
351 KiB
C++
10712 lines
351 KiB
C++
//depot/private/jasbr/inetsrv/iis/svcs/cmp/asp/template.cpp#19 - edit change 3548 (text)
|
|
/*==============================================================================
|
|
Microsoft Denali
|
|
|
|
Microsoft Confidential.
|
|
Copyright 1996 Microsoft Corporation. All Rights Reserved.
|
|
|
|
File: template.cpp
|
|
Maintained by: DaveK
|
|
Component: source file for Denali Compiled Template object
|
|
==============================================================================*/
|
|
#include "denpre.h"
|
|
|
|
#pragma hdrstop
|
|
|
|
const int SNIPPET_SIZE = 20; // # of characters in the code snippets
|
|
|
|
#pragma warning( disable : 4509 ) // suppress SEH/destructor warnings
|
|
#pragma warning( disable : 4355 ) // ignore: "'this' used in base member init
|
|
|
|
#include "debugger.h"
|
|
#include "dbgutil.h"
|
|
#include "tlbcache.h"
|
|
#include "ie449.h"
|
|
|
|
#include "memchk.h"
|
|
#include "vecimpl.h" // Include after memchk to insure that vector uses our mem manager.
|
|
|
|
#include "Accctrl.h"
|
|
#include "aclapi.h"
|
|
|
|
// Init class statics
|
|
CTemplate::CTokenList *CTemplate::gm_pTokenList = NULL;
|
|
PTRACE_LOG CTemplate::gm_pTraceLog = NULL;
|
|
HANDLE CTemplate::sm_hSmallHeap = NULL;
|
|
HANDLE CTemplate::sm_hLargeHeap = NULL;
|
|
|
|
// Max # of opener tokens to look for
|
|
#define TOKEN_OPENERS_MAX 8
|
|
|
|
/*===================================================================
|
|
Private non-class support functions
|
|
===================================================================*/
|
|
static void ByteRangeFromPb(BYTE* pbSource, CByteRange& brTarget);
|
|
static BOOLB FByteRangesAreEqual(CByteRange& br1, CByteRange& br2);
|
|
static unsigned CharAdvDBCS(WORD wCodePage, char *pchStart, char *pchEnd, unsigned cCharAdv, char **ppchEnd, BOOL fForceDBCS = FALSE);
|
|
static void LineFromByteRangeAdv(CByteRange& br, CByteRange& brLine);
|
|
static void LTrimWhiteSpace(CByteRange& br);
|
|
static void RTrimWhiteSpace(CByteRange& br);
|
|
static CByteRange BrNewLine(CByteRange br);
|
|
static BOOLB FWhiteSpace(char ch, BOOLB fSpaceIsWhiteSpace = TRUE);
|
|
static BOOLB FByteRangeIsWhiteSpace(CByteRange br);
|
|
static BOOLB FTagName(BYTE* pb, UINT cb);
|
|
static void ByteAlignOffset(UINT* pcbOffset, UINT cbAlignment);
|
|
static void GetSzFromPatternInserts(char* pszPattern, UINT cInserts, char** ppszInserts, char* szReturned);
|
|
static UINT CchPathFromFilespec(LPCTSTR szFile);
|
|
static void GetPathFromParentAndFilespec(LPCTSTR szParentPath, LPCTSTR szFileSpec, LPTSTR* pszPath);
|
|
static void HandleAccessFailure(CHitObj* pHitObj, TCHAR* szFile);
|
|
static void SendToLog(DWORD dwMask, CHAR *szFileName, CHAR *szLineNum, CHAR *szShortDes, CHAR *szLongDes, CHAR *szEngine, CHitObj *pHitObj);
|
|
static HRESULT GetProgLangId(CByteRange& brEngine, PROGLANG_ID* pProgLangId);
|
|
|
|
inline
|
|
void __cdecl DebugPrintf(LPCSTR fmt, ...)
|
|
{
|
|
#if DBG
|
|
char msg[512];
|
|
va_list marker;
|
|
va_start(marker, fmt);
|
|
vsprintf(msg, fmt, marker);
|
|
va_end(marker);
|
|
OutputDebugStringA(msg);
|
|
#endif
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
ByteRangeFromPb
|
|
Gets a byte range from a contiguous block of memory
|
|
|
|
Returns:
|
|
Nothing.
|
|
|
|
Side effects:
|
|
None.
|
|
*/
|
|
void
|
|
ByteRangeFromPb
|
|
(
|
|
BYTE* pbSource,
|
|
CByteRange& brTarget
|
|
)
|
|
{
|
|
Assert(pbSource != NULL);
|
|
brTarget.m_cb = *(ULONG*)pbSource;
|
|
brTarget.m_pb = pbSource + sizeof(ULONG);
|
|
}
|
|
|
|
/* ============================================================================
|
|
FByteRangesAreEqual
|
|
|
|
Compares two byte ranges
|
|
|
|
Returns:
|
|
BOOLB. True if byte ranges are equal, else false.
|
|
|
|
Side effects:
|
|
None.
|
|
*/
|
|
BOOLB
|
|
FByteRangesAreEqual
|
|
(
|
|
CByteRange& br1,
|
|
CByteRange& br2
|
|
)
|
|
{
|
|
if(br1.m_cb != br2.m_cb)
|
|
return FALSE;
|
|
return (!_strnicmp((LPCSTR)br1.m_pb, (LPCSTR)br2.m_pb, br1.m_cb));
|
|
}
|
|
|
|
/* ============================================================================
|
|
CharAdvDBCS
|
|
|
|
Advance "cchCharAdv" characters in a buffer
|
|
SBCS: Degenerates to simple pointer arithmatic
|
|
|
|
Arguments:
|
|
wCodePage - code page
|
|
pchStart - pointer to beginning of segment
|
|
pchEnd - pointer to just past end of segment
|
|
cCharAdv - # of characters to advance
|
|
ppchEnd - [output], contains pointer "cCharAdv" chars past pchStart
|
|
fForceDBCS - if TRUE, always use double byte algorithm.
|
|
(for verifying correct behavior of func in debug mode)
|
|
|
|
Returns:
|
|
(int) # of characters that we actually advanced
|
|
|
|
Notes:
|
|
By passing INFINITE for "cCharAdv", you can use this function to count characters
|
|
in a block
|
|
|
|
Side effects:
|
|
None.
|
|
*/
|
|
unsigned
|
|
CharAdvDBCS
|
|
(
|
|
WORD wCodePage,
|
|
char *pchStart,
|
|
char *pchEnd,
|
|
unsigned cCharAdv,
|
|
char **ppchEnd,
|
|
BOOL fForceDBCS
|
|
)
|
|
{
|
|
CPINFO CpInfo;
|
|
GetCPInfo(wCodePage, &CpInfo);
|
|
if (!fForceDBCS && CpInfo.MaxCharSize == 1)
|
|
{
|
|
char *pchT = pchStart + min(cCharAdv, unsigned(pchEnd - pchStart));
|
|
|
|
if (ppchEnd)
|
|
*ppchEnd = pchT;
|
|
|
|
#if DBG
|
|
// Verify DBCS algorithm (not often tested otherwise)
|
|
char *pchTest;
|
|
unsigned cchTest = CharAdvDBCS(wCodePage, pchStart, pchEnd, cCharAdv, &pchTest, TRUE);
|
|
Assert (cchTest == unsigned(pchT - pchStart) && pchTest == pchT);
|
|
#endif
|
|
|
|
return DIFF(pchT - pchStart);
|
|
}
|
|
else
|
|
{
|
|
int cch = 0;
|
|
char *pchNext = pchStart;
|
|
|
|
// Count DBCS characters. We have to stop before pchEnd because
|
|
// pchEnd may point past file map and CharNextExA AVs when advancing
|
|
// past allocated memory
|
|
|
|
while (cCharAdv > 0 && pchNext < pchEnd-2)
|
|
{
|
|
pchNext = *pchNext? AspCharNextA(wCodePage, pchNext) : pchNext + 1;
|
|
--cCharAdv;
|
|
++cch;
|
|
}
|
|
|
|
// We could stop on the last or the before last character
|
|
// depending on the DBCS char sequence
|
|
if (cCharAdv > 0 && pchNext == pchEnd-1)
|
|
{
|
|
// Only one byte - has to be one single byte character
|
|
++pchNext;
|
|
++cch;
|
|
}
|
|
|
|
else if (cCharAdv > 0 && pchNext == pchEnd-2)
|
|
{
|
|
// 2 bytes left - either 1 2-byte char or 2 1-byte chars
|
|
if (IsDBCSLeadByteEx(wCodePage, *pchNext))
|
|
{
|
|
++cch;
|
|
pchNext += 2;
|
|
}
|
|
else
|
|
{
|
|
// Two characters left. If cCharAdv > 1, this means that user wants to
|
|
// advance at least two more chars. Otherwise, cCharAdv == 1, and
|
|
// we advance one char
|
|
//
|
|
if (cCharAdv > 1)
|
|
{
|
|
cch += 2;
|
|
pchNext += 2;
|
|
}
|
|
else
|
|
{
|
|
Assert (cCharAdv == 1);
|
|
++cch;
|
|
++pchNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ppchEnd)
|
|
*ppchEnd = pchNext;
|
|
|
|
return cch;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
LineFromByteRangeAdv
|
|
Gets the first line in a byte range.
|
|
|
|
Returns:
|
|
Nothing
|
|
|
|
Side effects:
|
|
Advances source byte range to just beyond its first non-white-space line,
|
|
if one is found.
|
|
|
|
*/
|
|
void
|
|
LineFromByteRangeAdv
|
|
(
|
|
CByteRange& brSource,
|
|
CByteRange& brLine
|
|
)
|
|
{
|
|
CByteRange brTemp;
|
|
|
|
if(brSource.IsNull())
|
|
{
|
|
brLine.Nullify();
|
|
return;
|
|
}
|
|
|
|
brLine.m_pb = brSource.m_pb;
|
|
|
|
brTemp = BrNewLine(brSource);
|
|
if(brTemp.IsNull())
|
|
{
|
|
// We found no newline in a non-empty byte range:
|
|
// set line range to entire source byte range and empty source byte range
|
|
brLine.m_cb = brSource.m_cb;
|
|
brSource.Nullify();
|
|
}
|
|
else
|
|
{
|
|
// We found a newline in a non-empty byte range:
|
|
// set line range to portion of source byte range before new line;
|
|
// set source range to portion of source range after new line
|
|
brLine.m_cb = DIFF(brTemp.m_pb - brSource.m_pb);
|
|
brSource.m_pb = brTemp.m_pb + brTemp.m_cb;
|
|
brSource.m_cb -= (brLine.m_cb + brTemp.m_cb);
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
LTrimWhiteSpace
|
|
|
|
Left-trim white space from byte-range
|
|
|
|
Returns:
|
|
Nothing
|
|
|
|
Side effects:
|
|
Advances byte range to just beyond its first non-white-space character.
|
|
|
|
*/
|
|
void
|
|
LTrimWhiteSpace
|
|
(
|
|
CByteRange& br
|
|
)
|
|
{
|
|
if(br.IsNull())
|
|
return;
|
|
while(FWhiteSpace(*br.m_pb))
|
|
{
|
|
br.m_pb++;
|
|
if(--br.m_cb == 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
RTrimWhiteSpace
|
|
Right-trim white space from byte-range
|
|
*/
|
|
void
|
|
RTrimWhiteSpace(CByteRange& br)
|
|
{
|
|
if(br.IsNull())
|
|
return;
|
|
while(FWhiteSpace(*(br.m_pb + br.m_cb - 1)))
|
|
{
|
|
if(--br.m_cb == 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
BrNewLine
|
|
Returns ptr to the first newline in a byte range
|
|
NOTE does not change byte range (since it is passed by value)
|
|
*/
|
|
CByteRange
|
|
BrNewLine(CByteRange br)
|
|
{
|
|
while(!br.IsNull())
|
|
{
|
|
if(*br.m_pb == '\r')
|
|
return CByteRange(br.m_pb, (br.m_cb > 1 && br.m_pb[1] == '\n')? 2 : 1);
|
|
|
|
else if (*br.m_pb == '\n')
|
|
return CByteRange(br.m_pb, 1);
|
|
|
|
++br.m_pb;
|
|
--br.m_cb;
|
|
}
|
|
return CByteRange();
|
|
}
|
|
|
|
/* ============================================================================
|
|
FWhiteSpace
|
|
Returns:
|
|
TRUE if ch is a white-space character, else returns FALSE
|
|
Certain character(s) (e.g. space) may be treated as
|
|
non-white-space; to do this, caller passes FALSE for
|
|
fSpaceIsWhiteSpace flag.
|
|
*/
|
|
BOOLB
|
|
FWhiteSpace(char ch, BOOLB fSpaceIsWhiteSpace)
|
|
{
|
|
switch (ch)
|
|
{
|
|
case ' ':
|
|
return fSpaceIsWhiteSpace;
|
|
case '\0':
|
|
return TRUE;
|
|
case '\a':
|
|
return TRUE;
|
|
case '\b':
|
|
return TRUE;
|
|
case '\f':
|
|
return TRUE;
|
|
case '\n':
|
|
return TRUE;
|
|
case '\r':
|
|
return TRUE;
|
|
case '\t':
|
|
return TRUE;
|
|
case '\v':
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
FByteRangeIsWhiteSpace
|
|
Is the entire input byte range white space?
|
|
NOTE input byte range is byval; caller's copy is not changed
|
|
*/
|
|
BOOLB
|
|
FByteRangeIsWhiteSpace(CByteRange br)
|
|
{
|
|
while(!br.IsNull())
|
|
{
|
|
if(!FWhiteSpace(*(br.m_pb)))
|
|
return FALSE;
|
|
br.Advance(1);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* ============================================================================
|
|
FTagName
|
|
Does pb point to a valid HTML tag name?
|
|
(i.e., is *pb a valid HTML tag name and not a substring?)
|
|
|
|
Returns
|
|
TRUE or FALSE
|
|
Side effects
|
|
None
|
|
*/
|
|
BOOLB
|
|
FTagName(BYTE* pb, UINT cb)
|
|
{
|
|
if((pb == NULL) || (cb == 0))
|
|
return FALSE;
|
|
|
|
// a valid HTML tag name must be preceded by white space ...
|
|
if( FWhiteSpace(*(pb - 1)) || *(pb - 1) == '@' )
|
|
{
|
|
// ... and followed either by white space or the tag separator
|
|
if(FWhiteSpace(*(pb + cb)))
|
|
return TRUE;
|
|
else if(*(pb + cb) == CH_ATTRIBUTE_SEPARATOR)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*===================================================================
|
|
ByteAlignOffset
|
|
Byte-aligns an offset value, based on size of source data
|
|
*/
|
|
void
|
|
ByteAlignOffset
|
|
(
|
|
UINT* pcbOffset, // ptr to offset value
|
|
UINT cbAlignment // Alignment boundary
|
|
)
|
|
{
|
|
// comment the below code out so that it works for 64 bit...
|
|
|
|
// only byte-align for 2-, or 4-byte data
|
|
// since our base pointer in only aligned to a 4 byte boundary
|
|
//if(cbAlignment == 2 || cbAlignment == 4)
|
|
//{
|
|
// if current offset does not fall on a byte-aligned location for current data type,
|
|
// advance offset to next byte-aligned location
|
|
Assert(cbAlignment > 0);
|
|
--cbAlignment;
|
|
if (*pcbOffset & cbAlignment)
|
|
*pcbOffset = (*pcbOffset + cbAlignment + 1) & ~cbAlignment;
|
|
}
|
|
|
|
/* ============================================================================
|
|
GetSzFromPatternInserts
|
|
Returns a 'resolved' version of a pattern string, i.e. a new string in which
|
|
| characters have been replaced by caller-specified insert strings.
|
|
NOTE this function allocates, but caller must free
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
allocates memory
|
|
*/
|
|
void
|
|
GetSzFromPatternInserts
|
|
(
|
|
char* pszPattern, // 'pattern' string
|
|
UINT cInserts, // count of insert strings
|
|
char** ppszInserts, // array of ptrs to insert strings
|
|
char* szReturned // returned string MUST be allocated by caller
|
|
)
|
|
{
|
|
UINT cchRet = strlen(pszPattern); // length of return string
|
|
char* pchStartCopy = pszPattern; // ptr to start of copy range in pattern
|
|
char* pchEndCopy = pszPattern; // ptr to end of copy range in pattern
|
|
UINT cActualInserts = 0; // count of actual insert strings
|
|
|
|
// init return string to empty so we can concatenate onto it
|
|
szReturned[0] = NULL;
|
|
|
|
// zero out length of return string - we now use it to count actual length as we build return string
|
|
cchRet = 0;
|
|
|
|
while(TRUE)
|
|
{
|
|
// advance end-of-copy ptr through pattern looking for insertion points or end of string
|
|
|
|
while ((*pchEndCopy != NULL) && (IsDBCSLeadByte(*pchEndCopy) || (*pchEndCopy != '|')))
|
|
pchEndCopy = CharNextA(pchEndCopy);
|
|
|
|
// cat from start-of-copy to end-of-copy onto return string
|
|
strncat(szReturned, pchStartCopy, DIFF(pchEndCopy - pchStartCopy));
|
|
|
|
// update return string length
|
|
cchRet += DIFF(pchEndCopy - pchStartCopy);
|
|
|
|
// if we are at end of pattern, exit
|
|
if(*pchEndCopy == NULL)
|
|
goto Exit;
|
|
|
|
if(cActualInserts < cInserts)
|
|
{
|
|
// if inserts remain, cat the next one onto return string
|
|
strcat(szReturned, ppszInserts[cActualInserts]);
|
|
// update return string length
|
|
cchRet += strlen(ppszInserts[cActualInserts]);
|
|
cActualInserts++;
|
|
}
|
|
|
|
// advance end-of-copy and start-of-copy beyond insertion point
|
|
pchEndCopy++;
|
|
pchStartCopy = pchEndCopy;
|
|
}
|
|
|
|
Exit:
|
|
// null-terminate return string
|
|
szReturned[cchRet] = NULL;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CchPathFromFilespec
|
|
Returns a filespec's path length (exclusive of filespec)
|
|
NOTE path string includes trailing '\' or '/'
|
|
|
|
Returns:
|
|
Length of path string
|
|
Side effects:
|
|
None
|
|
*/
|
|
UINT
|
|
CchPathFromFilespec
|
|
(
|
|
LPCTSTR szFileSpec // filespec
|
|
)
|
|
{
|
|
// BUG FIX 102010 DBCS fixes
|
|
//int ich = lstrlen(szFileSpec) - 1; // index of char to compare
|
|
//
|
|
//while(*(szFileSpec + ich) != '\\' && *(szFileSpec + ich) != '/')
|
|
// {
|
|
// if(--ich < 0)
|
|
// THROW(E_FAIL);
|
|
// }
|
|
//return (UINT) (ich + 1); // path length, including trailing '\' or '/', is char index + 1
|
|
|
|
TCHAR* p1 = _tcsrchr(szFileSpec, _T('\\'));
|
|
TCHAR* p2 = _tcsrchr(szFileSpec, _T('/')); // this wont be a DBCS trail byte.
|
|
|
|
if (p1 == NULL && p2 == NULL)
|
|
THROW(E_FAIL);
|
|
|
|
return (UINT) ((((LPTSTR)max(p1,p2) - szFileSpec)) + 1);
|
|
}
|
|
|
|
/* ============================================================================
|
|
GetPathFromParentAndFilespec
|
|
Returns an absolute path which is a 'parent' file's path concatenated with a filespec.
|
|
|
|
Returns:
|
|
absolute path (out-parameter)
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
GetPathFromParentAndFilespec
|
|
(
|
|
LPCTSTR szParentPath, // parent path
|
|
LPCTSTR szFileSpec, // filespec
|
|
LPTSTR* pszPath // resolved path (out-parameter)
|
|
)
|
|
{
|
|
UINT cchParentPath = CchPathFromFilespec(szParentPath);
|
|
|
|
if ((cchParentPath + _tcslen(szFileSpec)) > MAX_PATH)
|
|
THROW(E_FAIL);
|
|
|
|
_tcsncpy(*pszPath, szParentPath, cchParentPath);
|
|
_tcscpy(*pszPath + cchParentPath, szFileSpec);
|
|
}
|
|
|
|
/* ============================================================================
|
|
HandleAccessFailure
|
|
Handles an access-denied failure
|
|
|
|
Returns:
|
|
nothing
|
|
Side effects:
|
|
none
|
|
*/
|
|
void
|
|
HandleAccessFailure
|
|
(
|
|
CHitObj* pHitObj, // browser's hitobj
|
|
TCHAR * szFile // file path of main template
|
|
)
|
|
{
|
|
Assert(pHitObj);
|
|
|
|
// debugging diagnostic print
|
|
#if DBG
|
|
|
|
STACK_BUFFER( authUserBuff, 32 );
|
|
|
|
char *szAuthUser;
|
|
DWORD cbAuthUser;
|
|
|
|
if (SERVER_GET(pHitObj->PIReq(), "AUTH_USER", &authUserBuff, &cbAuthUser)) {
|
|
szAuthUser = (char*)authUserBuff.QueryPtr();
|
|
}
|
|
else {
|
|
szAuthUser = "anonymous";
|
|
}
|
|
|
|
#if UNICODE
|
|
DBGPRINTF((DBG_CONTEXT, "No permission to read file %S\n", szFile != NULL? szFile : pHitObj->PIReq()->QueryPszPathTranslated()));
|
|
#else
|
|
DBGPRINTF((DBG_CONTEXT, "No permission to read file %s\n", szFile != NULL? szFile : pHitObj->PIReq()->QueryPszPathTranslated()));
|
|
#endif
|
|
DBGPRINTF((DBG_CONTEXT, " The user account is \"%s\"\n", szAuthUser));
|
|
#endif
|
|
|
|
CResponse *pResponse = pHitObj->PResponse();
|
|
if (!pResponse)
|
|
return;
|
|
|
|
HandleSysError(401,3,IDE_401_3_ACCESS_DENIED,IDH_401_3_ACCESS_DENIED,pHitObj->PIReq(),pHitObj);
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
/* ============================================================================
|
|
SendToLog
|
|
Sends Error Info to Log
|
|
|
|
Returns:
|
|
Nothing
|
|
|
|
Side effects:
|
|
None.
|
|
*/
|
|
void
|
|
SendToLog
|
|
(
|
|
DWORD dwMask,
|
|
CHAR *szFileName,
|
|
CHAR *szLineNum,
|
|
CHAR *szEngine,
|
|
CHAR *szErrCode,
|
|
CHAR *szShortDes,
|
|
CHAR *szLongDes,
|
|
CHitObj *pHitObj // browser's hitobj
|
|
)
|
|
{
|
|
CHAR *szFileNameT;
|
|
CHAR *szLineNumT;
|
|
CHAR *szEngineT;
|
|
CHAR *szErrCodeT;
|
|
CHAR *szShortDesT;
|
|
CHAR *szLongDesT;
|
|
if(pHitObj) {
|
|
// NOTE - szFileName is assumed to be UTF8 when UNICODE is defined
|
|
szFileNameT = StringDupA(szFileName);
|
|
szLineNumT = StringDupA(szLineNum);
|
|
szEngineT = StringDupA(szEngine);
|
|
szErrCodeT = StringDupA(szErrCode);
|
|
szShortDesT = StringDupA(szShortDes);
|
|
szLongDesT = StringDupA(szLongDes);
|
|
|
|
HandleError(szShortDesT, szLongDesT, dwMask, szFileNameT, szLineNumT, szEngineT, szErrCodeT, NULL, pHitObj);
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
FreeNullify
|
|
Frees and nullifies a ptr to memory allocated with malloc.
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
static void
|
|
FreeNullify
|
|
(
|
|
void** pp
|
|
)
|
|
{
|
|
if(*pp != NULL)
|
|
{
|
|
free(*pp);
|
|
*pp = NULL;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
SmallTemplateFreeNullify
|
|
Frees and nullifies a ptr to memory allocated with CTemplate::SmallMalloc.
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
static void
|
|
SmallTemplateFreeNullify
|
|
(
|
|
void** pp
|
|
)
|
|
{
|
|
if(*pp != NULL)
|
|
{
|
|
CTemplate::SmallFree(*pp);
|
|
*pp = NULL;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
LargeTemplateFreeNullify
|
|
Frees and nullifies a ptr to memory allocated with CTemplate::LargeMalloc.
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
static void
|
|
LargeTemplateFreeNullify
|
|
(
|
|
void** pp
|
|
)
|
|
{
|
|
if(*pp != NULL)
|
|
{
|
|
CTemplate::LargeFree(*pp);
|
|
*pp = NULL;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
GetProgLangId
|
|
Gets the prog lang id for a script engine
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
throws on error
|
|
*/
|
|
HRESULT
|
|
GetProgLangId
|
|
(
|
|
CByteRange& brEngine, // engine name
|
|
PROGLANG_ID* pProgLangId // prog lang id (out-parameter)
|
|
)
|
|
{
|
|
|
|
STACK_BUFFER( tempEngine, 128 );
|
|
|
|
if (!tempEngine.Resize(brEngine.m_cb + 1)) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
LPSTR szProgLang = static_cast<LPSTR> (tempEngine.QueryPtr());
|
|
|
|
strncpy(szProgLang, (LPCSTR)brEngine.m_pb, brEngine.m_cb);
|
|
szProgLang[brEngine.m_cb] = '\0';
|
|
|
|
return g_ScriptManager.ProgLangIdOfLangName((LPCSTR) szProgLang, pProgLangId);
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
CByteRange member functions
|
|
*/
|
|
|
|
/* ========================================================
|
|
CByteRange::Advance
|
|
Advances a byte range.
|
|
*/
|
|
void
|
|
CByteRange::Advance(UINT i)
|
|
{
|
|
if(i >= m_cb)
|
|
{
|
|
Nullify();
|
|
}
|
|
else
|
|
{
|
|
m_pb += i;
|
|
m_cb -= i;
|
|
}
|
|
}
|
|
|
|
/* ========================================================
|
|
CByteRange::FMatchesSz
|
|
Compares a byte range with a string, case-insensitively
|
|
*/
|
|
BOOLB
|
|
CByteRange::FMatchesSz
|
|
(
|
|
LPCSTR psz
|
|
)
|
|
{
|
|
if(IsNull() || (psz == NULL))
|
|
return FALSE;
|
|
if((ULONG)strlen(psz) != m_cb)
|
|
return FALSE;
|
|
return !_strnicmp((const char*)m_pb, psz, m_cb);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CByteRange::PbString
|
|
Finds a case-insensitive string within a byte range
|
|
|
|
Returns:
|
|
Ptr to first case-insensitive occurrence of the string in this byte range;
|
|
NULL if none found.
|
|
Side effects:
|
|
None
|
|
*/
|
|
BYTE*
|
|
CByteRange::PbString
|
|
(
|
|
LPSTR psz,
|
|
LONG lCodePage
|
|
)
|
|
{
|
|
UINT cch = strlen(psz);
|
|
if(cch == 0)
|
|
return NULL;
|
|
|
|
BYTE *pbLocal = m_pb;
|
|
UINT cbLocal = m_cb;
|
|
char ch0 = psz[0];
|
|
BYTE *pbTemp = NULL;
|
|
UINT cbAdvanced = 0;
|
|
|
|
if (IsCharAlpha(ch0))
|
|
{
|
|
// cannot use strchr
|
|
while (cbLocal >= cch)
|
|
{
|
|
if (_strnicmp((const char *)pbLocal, psz, cch) == 0)
|
|
return pbLocal;
|
|
|
|
// The following code simply performs a DBCS-enabled ByteRange.Advance() action.
|
|
pbTemp = pbLocal;
|
|
pbLocal = *pbLocal? (BYTE *)AspCharNextA((WORD)lCodePage, (const char *)pbLocal) : pbLocal + 1;
|
|
cbAdvanced = DIFF(pbLocal - pbTemp);
|
|
if (cbAdvanced >= cbLocal)
|
|
{
|
|
cbLocal = 0;
|
|
pbLocal = NULL;
|
|
}
|
|
else
|
|
cbLocal -= cbAdvanced;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// can use strchr
|
|
while (cbLocal >= cch)
|
|
{
|
|
pbTemp = (BYTE *)memchr(pbLocal, ch0, cbLocal);
|
|
if (pbTemp == NULL)
|
|
break;
|
|
UINT cbOffset = DIFF(pbTemp - pbLocal);
|
|
if (cbOffset >= cbLocal)
|
|
break;
|
|
pbLocal = pbTemp;
|
|
cbLocal -= cbOffset;
|
|
if (cch <= cbLocal && _strnicmp((const char *)pbLocal, psz, cch) == 0)
|
|
return pbLocal;
|
|
// The following code simply performs a DBCS-enabled ByteRange.Advance() action.
|
|
pbTemp = pbLocal;
|
|
pbLocal = *pbLocal? (BYTE *)AspCharNextA((WORD)lCodePage, (const char *)pbLocal) : pbLocal + 1;
|
|
cbAdvanced = DIFF(pbLocal - pbTemp);
|
|
if (cbAdvanced >= cbLocal)
|
|
{
|
|
cbLocal = 0;
|
|
pbLocal = NULL;
|
|
}
|
|
else
|
|
cbLocal -= cbAdvanced;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CByteRange::PbOneOfAspOpenerStringTokens
|
|
Finds a case-insensitive string within a byte range
|
|
that matches one of the strings passed
|
|
|
|
!!! WILL ONLY WORK IF THE FOLLOWING IS TRUE:
|
|
1) All the tokens start with the same charater (for example '<')
|
|
2) This character is not alpha (so that strchr() would work)
|
|
!!! THE ABOVE ASSUMPTIONS MAKE THE CODE WORK FASTER
|
|
|
|
Returns:
|
|
Ptr to first case-insensitive occurrence of the string in this byte range;
|
|
NULL if none found.
|
|
*pcindex is set to the index of string found
|
|
Side effects:
|
|
None
|
|
*/
|
|
BYTE*
|
|
CByteRange::PbOneOfAspOpenerStringTokens
|
|
(
|
|
LPSTR rgszTokens[],
|
|
UINT rgcchTokens[],
|
|
UINT nTokens,
|
|
UINT *pidToken
|
|
)
|
|
{
|
|
if (nTokens == 0)
|
|
return NULL;
|
|
|
|
BYTE *pb = m_pb; // pointer to unsearched remainder of the range
|
|
UINT cbRemainder = m_cb; // remaining byte range length
|
|
char ch0 = rgszTokens[0][0]; // first char of every token
|
|
|
|
while (cbRemainder > 0) {
|
|
// BUG 82331: avoid strchr() because byte range is not null-terminated
|
|
while (cbRemainder > 0 && *pb != ch0)
|
|
{
|
|
++pb;
|
|
--cbRemainder;
|
|
}
|
|
|
|
if (cbRemainder == 0)
|
|
break;
|
|
|
|
for (UINT i = 0; i < nTokens; i++) {
|
|
|
|
if ((rgcchTokens[i] <= cbRemainder)
|
|
&& (rgszTokens[i] != NULL)
|
|
&& (_strnicmp((const char *)pb, rgszTokens[i], rgcchTokens[i]) == 0)) {
|
|
|
|
*pidToken = i;
|
|
return pb;
|
|
}
|
|
}
|
|
++pb;
|
|
--cbRemainder;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
CByteRange::FEarlierInSourceThan
|
|
Does this byte range occur earlier in source than parameter byte range?
|
|
|
|
Returns
|
|
TRUE or FALSE
|
|
Side effects
|
|
None
|
|
*/
|
|
BOOLB
|
|
CByteRange::FEarlierInSourceThan(CByteRange& br)
|
|
{
|
|
if(br.IsNull())
|
|
return TRUE;
|
|
return(m_idSequence < br.m_idSequence);
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
CTemplate member functions
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CTemplate::InitClass
|
|
Initilaizes CTemplate static members
|
|
|
|
Returns:
|
|
hresult
|
|
Side effects:
|
|
allocates memory for static members
|
|
*/
|
|
HRESULT
|
|
CTemplate::InitClass
|
|
(
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
TRY
|
|
// init heaps
|
|
sm_hSmallHeap = ::HeapCreate(0, 0, 0);
|
|
sm_hLargeHeap = ::HeapCreate(0, 0, 0);
|
|
|
|
// Init token list
|
|
gm_pTokenList = new CTokenList;
|
|
if (gm_pTokenList == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
gm_pTokenList->Init();
|
|
|
|
CATCH(hrException)
|
|
hr = hrException;
|
|
END_TRY
|
|
|
|
return hr;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::UnInitClass
|
|
Un-initilaizes CTemplate static members
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::UnInitClass()
|
|
{
|
|
delete gm_pTokenList;
|
|
gm_pTokenList = NULL;
|
|
|
|
::HeapDestroy(sm_hLargeHeap);
|
|
if (sm_hLargeHeap != sm_hSmallHeap)
|
|
::HeapDestroy(sm_hSmallHeap);
|
|
sm_hLargeHeap = sm_hSmallHeap = NULL;
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::Init
|
|
Inits template in preparation for calling Compile
|
|
Does the minimum needed
|
|
|
|
Returns:
|
|
Success or failure code
|
|
Side effects:
|
|
Allocates memory
|
|
*/
|
|
HRESULT
|
|
CTemplate::Init
|
|
(
|
|
CHitObj *pHitObj, // ptr to template's hit object
|
|
BOOL fGlobalAsa, // is this the global.asa file?
|
|
const CTemplateKey &rTemplateKey // hash table key
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Create debug critical section
|
|
ErrInitCriticalSection(&m_csDebuggerDetach, hr);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// note critical section creation success
|
|
m_fDebuggerDetachCSInited = TRUE;
|
|
|
|
// Create event: manual-reset, ready-for-use event; non-signaled
|
|
m_hEventReadyForUse = IIS_CREATE_EVENT(
|
|
"CTemplate::m_hEventReadyForUse",
|
|
this,
|
|
TRUE, // flag for manual-reset event
|
|
FALSE // flag for initial state
|
|
);
|
|
if (!m_hEventReadyForUse)
|
|
return E_OUTOFMEMORY;
|
|
|
|
// cache GlobalAsp flag
|
|
m_fGlobalAsa = BOOLB(fGlobalAsa);
|
|
|
|
// CIsapiReqInfo better be present
|
|
if (pHitObj->PIReq() == NULL)
|
|
return E_POINTER;
|
|
|
|
// Initialize the template's code page
|
|
|
|
m_wCodePage = pHitObj->PAppln()->QueryAppConfig()->uCodePage();
|
|
m_lLCID = pHitObj->PAppln()->QueryAppConfig()->uLCID();
|
|
|
|
STACK_BUFFER( serverNameBuff, 32 );
|
|
STACK_BUFFER( serverPortBuff, 10 );
|
|
STACK_BUFFER( portSecureBuff, 8 );
|
|
|
|
DWORD cbServerName;
|
|
DWORD cbServerPort;
|
|
DWORD cbServerPortSecure;
|
|
|
|
// Construct a URL for the application
|
|
|
|
// Get the server name and port
|
|
if (!SERVER_GET(pHitObj->PIReq(), "SERVER_NAME", &serverNameBuff, &cbServerName)
|
|
|| !SERVER_GET(pHitObj->PIReq(), "SERVER_PORT", &serverPortBuff, &cbServerPort)) {
|
|
|
|
if (GetLastError() == E_OUTOFMEMORY) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else {
|
|
hr = E_FAIL;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
char *szServerPort = (char *)serverPortBuff.QueryPtr();
|
|
char *szServerName = (char *)serverNameBuff.QueryPtr();
|
|
|
|
BOOL fServerPortSecure = FALSE;
|
|
|
|
// determine if server port is secure
|
|
if (SERVER_GET(pHitObj->PIReq(), "SERVER_PORT_SECURE", &portSecureBuff, &cbServerPortSecure)) {
|
|
char *szServerPortSecure = (char *)portSecureBuff.QueryPtr();
|
|
fServerPortSecure = (szServerPortSecure[0] == '1');
|
|
}
|
|
|
|
// Get the application virtual path
|
|
TCHAR szApplnVirtPath[256];
|
|
if (FAILED(hr = FindApplicationPath(pHitObj->PIReq(), szApplnVirtPath, sizeof szApplnVirtPath)))
|
|
return hr;
|
|
|
|
TCHAR *szServerNameT;
|
|
TCHAR *szServerPortT;
|
|
|
|
#if UNICODE
|
|
CMBCSToWChar convServer;
|
|
if (FAILED(hr = convServer.Init(szServerName))) {
|
|
return hr;
|
|
}
|
|
szServerNameT = convServer.GetString();
|
|
#else
|
|
szServerNameT = szServerName;
|
|
#endif
|
|
|
|
#if UNICODE
|
|
CMBCSToWChar convPort;
|
|
if (FAILED(hr = convPort.Init(szServerPort))) {
|
|
return hr;
|
|
}
|
|
szServerPortT = convPort.GetString();
|
|
#else
|
|
szServerPortT = szServerPort;
|
|
#endif
|
|
|
|
// Allocate space for and construct the application URL
|
|
m_szApplnURL = new TCHAR [(9 /* sizeof "https://:" */ + _tcslen(szServerNameT) + _tcslen(szServerPortT) + _tcslen(szApplnVirtPath) + 1)];
|
|
if (m_szApplnURL == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
TCHAR *pT;
|
|
|
|
// start with the protocol prefix...
|
|
|
|
pT = strcpyEx(m_szApplnURL, fServerPortSecure? _T("https://") : _T("http://"));
|
|
|
|
// next add the servername
|
|
|
|
pT = strcpyEx(pT, szServerNameT);
|
|
|
|
// next the colon between the servername and the serverport
|
|
|
|
pT = strcpyEx(pT, _T(":"));
|
|
|
|
// next the server port
|
|
|
|
pT = strcpyEx(pT, szServerPortT);
|
|
|
|
// now the applURL is built up to the appln path. The next step will be to
|
|
// add the virtpath.
|
|
|
|
m_szApplnVirtPath = pT;
|
|
|
|
_tcscpy(m_szApplnVirtPath, szApplnVirtPath);
|
|
|
|
m_LKHashKey.dwInstanceID = rTemplateKey.dwInstanceID;
|
|
if ((m_LKHashKey.szPathTranslated = StringDup((TCHAR *)rTemplateKey.szPathTranslated)) == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::Compile
|
|
Compiles the template from its source file and include files, if any,
|
|
by calling GetSegmentsFromFile (to populate WorkStore),
|
|
followed by WriteTemplate (to create the template from WorkStore).
|
|
|
|
Returns:
|
|
HRESULT indicating success or type of failure
|
|
Side effects:
|
|
Indirectly allocates memory (via WriteTemplate)
|
|
Indirectly frees memory on error (via FreeGoodTemplateMemory)
|
|
*/
|
|
HRESULT
|
|
CTemplate::Compile
|
|
(
|
|
CHitObj* pHitObj
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// The following code moved from Init() (to make Init() lighter)
|
|
|
|
Assert(pHitObj);
|
|
|
|
// Create and Init WorkStore
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// construct the workstore - bail on fail
|
|
if(NULL == (m_pWorkStore = new CWorkStore))
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = (m_pWorkStore->m_ScriptStore).Init(pHitObj->QueryAppConfig()->szScriptLanguage(),
|
|
pHitObj->QueryAppConfig()->pCLSIDDefaultEngine());
|
|
|
|
if (hr == TYPE_E_ELEMENTNOTFOUND)
|
|
{
|
|
// default script language in registry is bogus - send error msg to browser
|
|
HandleCTemplateError(
|
|
NULL, // source file map
|
|
NULL, // ptr to source location where error occurred
|
|
IDE_TEMPLATE_BAD_PROGLANG_IN_REGISTRY, // error message id
|
|
0, // count of insert strings for error msg
|
|
NULL, // array of ptrs to error msg insert strings
|
|
pHitObj // Browser Request
|
|
);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
delete m_pWorkStore;
|
|
m_pWorkStore = NULL;
|
|
}
|
|
}
|
|
|
|
// Try to init the workstore and map main file - this can fail with oom, etc or user lacks permissions
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TRY
|
|
m_pWorkStore->Init();
|
|
AppendMapFile(
|
|
NULL, // file spec for this file - NULL means get filespec from pHitObj
|
|
NULL, // ptr to filemap of parent file
|
|
FALSE, // don't care
|
|
pHitObj, // ptr to template's hit object
|
|
m_fGlobalAsa // is this the global.asa file?
|
|
);
|
|
|
|
CATCH(hrException)
|
|
delete m_pWorkStore;
|
|
m_pWorkStore = NULL;
|
|
|
|
hr = hrException;
|
|
|
|
if(hr == E_USER_LACKS_PERMISSIONS)
|
|
HandleAccessFailure(pHitObj,
|
|
(m_rgpFilemaps && m_rgpFilemaps[0])? m_rgpFilemaps[0]->m_szPathTranslated : NULL);
|
|
|
|
if (m_rgpFilemaps && m_rgpFilemaps[0])
|
|
{
|
|
// empty file will fail to map but will have a handle, so we check for it here
|
|
if (0 == GetFileSize(m_rgpFilemaps[0]->m_hFile, NULL))
|
|
hr = E_SOURCE_FILE_IS_EMPTY;
|
|
|
|
m_rgpFilemaps[0]->UnmapFile();
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
hr = E_FAIL; // make sure the error is set
|
|
END_TRY
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
Assert(m_rgpFilemaps[0]);
|
|
Assert(m_rgpFilemaps[0]->m_szPathTranslated);
|
|
Assert(FImplies(!m_fGlobalAsa, (0 == _tcscmp(m_rgpFilemaps[0]->m_szPathTranslated, pHitObj->PSzCurrTemplatePhysPath()))));
|
|
Assert(FImplies(m_fGlobalAsa, (0 == _tcscmp(m_rgpFilemaps[0]->m_szPathTranslated, pHitObj->GlobalAspPath()))));
|
|
Assert(0 < m_rgpFilemaps[0]->GetSize());
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
m_fDontCache = TRUE;
|
|
// OK, cache HR if m_fDontCache is true
|
|
// later, another thread might find this template from the cache even if the template
|
|
// has some error and marked as DontCache.
|
|
m_hrOnNoCache = hr;
|
|
m_fReadyForUse = TRUE;
|
|
SetEvent(m_hEventReadyForUse);
|
|
return hr;
|
|
}
|
|
|
|
// End of Code moved from Init()
|
|
|
|
|
|
// By default we are not in a transaction
|
|
m_ttTransacted = ttUndefined;
|
|
// By default session is required
|
|
m_fSession = TRUE;
|
|
// By default assume script exists
|
|
m_fScriptless = FALSE;
|
|
|
|
// we assert, in effect, that template is already init'ed
|
|
Assert(FImplies(!m_fGlobalAsa, (0 == _tcscmp(m_rgpFilemaps[0]->m_szPathTranslated, pHitObj->PSzCurrTemplatePhysPath()))));
|
|
Assert(FImplies(m_fGlobalAsa, (0 == _tcscmp(m_rgpFilemaps[0]->m_szPathTranslated, pHitObj->GlobalAspPath()))));
|
|
|
|
TRY
|
|
// Get source segments from source file
|
|
GetSegmentsFromFile(*(m_rgpFilemaps[0]), *m_pWorkStore, pHitObj);
|
|
|
|
/* get "language equivalents" for primary languagefrom registry
|
|
NOTE we do this here because the user can reset the primary language in the script file,
|
|
so we must wait until after GetSegmentsFromFile()
|
|
*/
|
|
GetLanguageEquivalents();
|
|
|
|
// Call WriteTemplate, which writes out template components to contiguous memory,
|
|
// resulting in a compiled template
|
|
WriteTemplate(*m_pWorkStore, pHitObj);
|
|
|
|
// Calculate the # of characters in a filemap before we unmap the file for all time.
|
|
for (unsigned i = 0; i < m_cFilemaps; ++i)
|
|
m_rgpFilemaps[i]->CountChars((WORD)m_wCodePage);
|
|
|
|
// Wrap typelibs into single IDispatch*
|
|
WrapTypeLibs(pHitObj);
|
|
|
|
m_fIsValid = TRUE;
|
|
|
|
CATCH(hrException)
|
|
// NOTE: we used to free template memory here. Now we do not because if the
|
|
// error was E_USER_LACKS_PERMISSIONS, and template is in cache, we don't want
|
|
// to sabotage future requests. There's no need to decache the template.
|
|
//
|
|
// The template destructor will free this memory anyway.
|
|
//
|
|
hr = hrException;
|
|
END_TRY
|
|
|
|
// check if scriptless
|
|
if (!m_fGlobalAsa)
|
|
{
|
|
// count various stuff to make the determination
|
|
DWORD cScriptEngines = m_pWorkStore->m_ScriptStore.CountPreliminaryEngines();
|
|
DWORD cPrimaryScriptSegments = (cScriptEngines > 0) ? m_pWorkStore->m_ScriptStore.m_ppbufSegments[0]->Count() : 0;
|
|
DWORD cObjectTags = m_pWorkStore->m_ObjectInfoStore.Count();
|
|
DWORD cHtmlSegments = m_pWorkStore->m_bufHTMLSegments.Count();
|
|
DWORD c449Cookies = m_rgp449.length();
|
|
BOOL fPageCommandsPresent = m_pWorkStore->m_fPageCommandsExecuted;
|
|
|
|
if (cScriptEngines <= 1 &&
|
|
cPrimaryScriptSegments == 0 &&
|
|
cObjectTags == 0 &&
|
|
cHtmlSegments == 1 &&
|
|
c449Cookies == 0 &&
|
|
!fPageCommandsPresent)
|
|
{
|
|
m_fScriptless = TRUE;
|
|
}
|
|
}
|
|
|
|
// free working storage - no longer needed
|
|
delete m_pWorkStore;
|
|
m_pWorkStore = NULL;
|
|
|
|
// un-map filemaps - NOTE filemaps stay around for possible post-compile errors (e.g., script failure)
|
|
UnmapFiles();
|
|
|
|
// Debugging: print data structure to debugger
|
|
IF_DEBUG(SCRIPT_DEBUGGER)
|
|
{
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT, "Script Compiled\n"));
|
|
|
|
for (UINT i = 0; i < m_cScriptEngines; ++i)
|
|
{
|
|
char *szEngineName;
|
|
PROGLANG_ID *pProgLangID;
|
|
const wchar_t *wszScriptText;
|
|
|
|
GetScriptBlock(i, &szEngineName, &pProgLangID, &wszScriptText);
|
|
DBGPRINTF((DBG_CONTEXT, "Engine %d, Language=\"%s\":\n", i, szEngineName));
|
|
#ifndef _NO_TRACING_
|
|
DBGINFO((DBG_CONTEXT, (char *) wszScriptText));
|
|
DBGINFO((DBG_CONTEXT, "\n"));
|
|
#else
|
|
OutputDebugStringW(wszScriptText);
|
|
OutputDebugStringA("\n");
|
|
#endif
|
|
}
|
|
#if 0
|
|
OutputDebugTables();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (hr == E_TEMPLATE_COMPILE_FAILED_DONT_CACHE)
|
|
{
|
|
m_fDontCache = TRUE;
|
|
m_hrOnNoCache = hr;
|
|
}
|
|
|
|
// Set ready-for-use flag true and event to signaled
|
|
// NOTE we do this whether success or failure, since even a failed-compile template
|
|
// will remain in the cache to allow template cache mgr to satisfy requests on it
|
|
m_fReadyForUse = TRUE;
|
|
SetEvent(m_hEventReadyForUse);
|
|
|
|
// Note whether the template currently is debuggable
|
|
// BUG BUG: Template is debuggable or not based on first app. If shared between a debug
|
|
// & non-debug app, the first application wins.
|
|
m_fDebuggable = (BOOLB)!!pHitObj->PAppln()->FDebuggable();
|
|
|
|
return hr;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::Deliver
|
|
Delivers template to caller once template is ready for use
|
|
NOTE 'compile failure' == template is 'ready for use' but did not compile successfully;
|
|
this allows cache mgr to keep a failed template in cache in case it gets requested again
|
|
|
|
Returns
|
|
success or failure
|
|
Side effects
|
|
none
|
|
*/
|
|
HRESULT
|
|
CTemplate::Deliver
|
|
(
|
|
CHitObj* pHitObj
|
|
)
|
|
{
|
|
// NOTE: There was a compiler bug where 'ps' would not be correctly aligned,
|
|
// EVEN if it was declared to be a DWORD array, if 'ps' was nested in
|
|
// a block. Thus declare it here.
|
|
//
|
|
BYTE ps[SIZE_PRIVILEGE_SET]; // privilege set
|
|
HRESULT hr = S_OK;
|
|
|
|
// if ready flag is not yet set block until template is ready for use
|
|
if(!m_fReadyForUse)
|
|
{
|
|
WaitForSingleObject(m_hEventReadyForUse, INFINITE);
|
|
Assert(m_fReadyForUse); // when event unblocks, flag will be set
|
|
}
|
|
|
|
if (m_pbStart == NULL)
|
|
{
|
|
if (m_fDontCache && m_dwLastErrorMask == 0)
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT, "template compile failed with %08x\n", m_hrOnNoCache));
|
|
DBG_ASSERT(FAILED(m_hrOnNoCache));
|
|
|
|
// Safety net: always fail, even if "m_hrOnNoCache" did not get set somehow.
|
|
hr = m_hrOnNoCache;
|
|
if (SUCCEEDED(m_hrOnNoCache))
|
|
hr = E_FAIL;
|
|
|
|
if(hr == E_USER_LACKS_PERMISSIONS)
|
|
HandleAccessFailure(pHitObj,
|
|
(m_rgpFilemaps && m_rgpFilemaps[0])? m_rgpFilemaps[0]->m_szPathTranslated : NULL);
|
|
return hr;
|
|
}
|
|
// template compile failed - NOTE null start-of-template ptr == template compile failed
|
|
// use cached error info
|
|
SendToLog( m_dwLastErrorMask,
|
|
m_pszLastErrorInfo[ILE_szFileName],
|
|
m_pszLastErrorInfo[ILE_szLineNum],
|
|
m_pszLastErrorInfo[ILE_szEngine],
|
|
m_pszLastErrorInfo[ILE_szErrorCode],
|
|
m_pszLastErrorInfo[ILE_szShortDes],
|
|
m_pszLastErrorInfo[ILE_szLongDes],
|
|
pHitObj);
|
|
hr = E_TEMPLATE_COMPILE_FAILED;
|
|
}
|
|
else if (!pHitObj->FIsBrowserRequest())
|
|
{
|
|
return hr;
|
|
}
|
|
else if (Glob(fWinNT))
|
|
// template compile succeeded - check user's file permissions
|
|
// ACLs: the following code should in future be shared with IIS (see creatfil.cxx in IIS project)
|
|
{
|
|
HANDLE hUserAccessToken = pHitObj->HImpersonate(); // current user's access token
|
|
DWORD dwPS = sizeof(ps); // privilege set size
|
|
DWORD dwGrantedAccess; // granted access mask
|
|
BOOL fAccessGranted; // access granted flag
|
|
GENERIC_MAPPING gm = { // generic mapping struct
|
|
FILE_GENERIC_READ,
|
|
FILE_GENERIC_WRITE,
|
|
FILE_GENERIC_EXECUTE,
|
|
FILE_ALL_ACCESS
|
|
};
|
|
|
|
((PRIVILEGE_SET*)&ps)->PrivilegeCount = 0; // set privilege count to 0
|
|
|
|
Assert(NULL != hUserAccessToken);
|
|
|
|
for(UINT i = 0; i < m_cFilemaps; i++)
|
|
{
|
|
|
|
if(NULL == m_rgpFilemaps[i]->m_pSecurityDescriptor)
|
|
continue;
|
|
|
|
if(!AccessCheck(
|
|
m_rgpFilemaps[i]->m_pSecurityDescriptor, // pointer to security descriptor
|
|
hUserAccessToken, // handle to client access token
|
|
FILE_GENERIC_READ, // access mask to request
|
|
&gm, // address of generic-mapping structure
|
|
(PRIVILEGE_SET*)ps, // address of privilege-set structure
|
|
&dwPS, // address of size of privilege-set structure
|
|
&dwGrantedAccess, // address of granted access mask
|
|
&fAccessGranted // address of flag indicating whether access granted
|
|
))
|
|
return E_FAIL;
|
|
|
|
if(!fAccessGranted)
|
|
{
|
|
// if access is denied on any file, handle the failure and return
|
|
HandleAccessFailure(pHitObj, m_rgpFilemaps[0]->m_szPathTranslated);
|
|
return E_USER_LACKS_PERMISSIONS;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset the Session.CodePage to the script compilation-time codepage
|
|
// only if a code page directive was found during compilation
|
|
if (m_fCodePageSet && (!pHitObj->FHasSession() || !pHitObj->PSession()->FCodePageSet()))
|
|
{
|
|
pHitObj->SetCodePage(m_wCodePage);
|
|
}
|
|
|
|
// Reset the Session.LCID to the script compilation-time LCID
|
|
// only if an LCID directive was found during compilation
|
|
if (m_fLCIDSet && (!pHitObj->FHasSession() || !pHitObj->PSession()->FLCIDSet()))
|
|
{
|
|
pHitObj->SetLCID(m_lLCID);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::CTemplate
|
|
Ctor
|
|
*/
|
|
CTemplate::CTemplate()
|
|
: m_pWorkStore(NULL),
|
|
m_fGlobalAsa(FALSE),
|
|
m_fReadyForUse(FALSE),
|
|
m_fDontAttach(FALSE),
|
|
m_hEventReadyForUse(NULL),
|
|
m_fDebuggerDetachCSInited(FALSE),
|
|
m_pbStart(NULL),
|
|
m_cbTemplate(0),
|
|
m_cRefs(1), // NOTE ctor AddRefs implicitly
|
|
m_pbErrorLocation(NULL),
|
|
m_idErrMsg(0),
|
|
m_cMsgInserts(0),
|
|
m_ppszMsgInserts(NULL),
|
|
m_cScriptEngines(0),
|
|
m_rgrgSourceInfos(NULL),
|
|
m_rgpDebugScripts(NULL),
|
|
m_rgpFilemaps(NULL),
|
|
m_cFilemaps(0),
|
|
m_rgpSegmentFilemaps(NULL),
|
|
m_cSegmentFilemapSlots(0),
|
|
m_wCodePage(CP_ACP),
|
|
m_lLCID(LOCALE_SYSTEM_DEFAULT),
|
|
m_ttTransacted(ttUndefined),
|
|
m_fSession(TRUE),
|
|
m_fScriptless(FALSE),
|
|
m_fDebuggable(FALSE),
|
|
m_fIsValid(FALSE),
|
|
m_fDontCache(FALSE),
|
|
m_fZombie(FALSE),
|
|
m_fCodePageSet(FALSE),
|
|
m_fLCIDSet(FALSE),
|
|
m_fIsPersisted(FALSE),
|
|
m_szPersistTempName(NULL),
|
|
m_szApplnVirtPath(NULL),
|
|
m_szApplnURL(NULL),
|
|
m_CPTextEvents(this, IID_IDebugDocumentTextEvents),
|
|
m_pdispTypeLibWrapper(NULL),
|
|
m_dwLastErrorMask(S_OK),
|
|
m_hrOnNoCache(S_OK),
|
|
m_cbTargetOffsetPrevT(0),
|
|
m_pHashTable(NULL),
|
|
m_pServicesConfig(NULL) {
|
|
|
|
m_wCodePage = GetACP();
|
|
|
|
for (UINT i = 0; i < ILE_MAX; i++)
|
|
{
|
|
m_pszLastErrorInfo[i] = NULL;
|
|
}
|
|
|
|
IF_DEBUG(TEMPLATE)
|
|
{
|
|
WriteRefTraceLog(gm_pTraceLog, m_cRefs, this);
|
|
}
|
|
#if PER_TEMPLATE_REFLOG
|
|
m_pTraceLog = CreateRefTraceLog (100,0);
|
|
WriteRefTraceLog (m_pTraceLog,m_cRefs, this);
|
|
#endif
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::~CTemplate
|
|
Destructor
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
CTemplate::~CTemplate()
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT, "Deleting template, m_cFilemaps = %d, m_rgpFilemaps %p\n", m_cFilemaps, m_rgpFilemaps));
|
|
|
|
// first, remove this template from its inc-files' template lists
|
|
// NOTE must do this before freeing template memory
|
|
RemoveFromIncFiles();
|
|
|
|
// Remove the template from the debugger's list of documents
|
|
Detach();
|
|
|
|
PersistCleanup();
|
|
|
|
if(m_rgpFilemaps)
|
|
{
|
|
for(UINT i = 0; i < m_cFilemaps; i++)
|
|
delete m_rgpFilemaps[i];
|
|
SmallTemplateFreeNullify((void**) &m_rgpFilemaps);
|
|
}
|
|
|
|
FreeGoodTemplateMemory();
|
|
|
|
if (m_pWorkStore)
|
|
delete m_pWorkStore;
|
|
|
|
//FileName, LineNum, Engine, ErrorCode, ShortDes, LongDes
|
|
for(UINT iErrInfo = 0; iErrInfo < ILE_MAX; iErrInfo++)
|
|
{
|
|
FreeNullify((void**) &m_pszLastErrorInfo[iErrInfo]);
|
|
}
|
|
|
|
if(m_hEventReadyForUse != NULL)
|
|
CloseHandle(m_hEventReadyForUse);
|
|
|
|
if (m_LKHashKey.szPathTranslated)
|
|
free((void *)m_LKHashKey.szPathTranslated);
|
|
|
|
if (m_szApplnURL)
|
|
delete [] m_szApplnURL;
|
|
|
|
if (m_fDebuggerDetachCSInited)
|
|
DeleteCriticalSection(&m_csDebuggerDetach);
|
|
|
|
if (m_pdispTypeLibWrapper)
|
|
m_pdispTypeLibWrapper->Release();
|
|
|
|
if (m_szPersistTempName)
|
|
CTemplate::LargeFree(m_szPersistTempName);
|
|
|
|
if (m_pServicesConfig)
|
|
m_pServicesConfig->Release();
|
|
|
|
m_pServicesConfig = NULL;
|
|
|
|
#if PER_TEMPLATE_REFLOG
|
|
DestroyRefTraceLog (m_pTraceLog);
|
|
#endif
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::QueryInterface
|
|
Provides QueryInterface implementation for CTemplate
|
|
|
|
NOTE: It is arbitrary which vtable we return for IDebugDocument & IDebugDocumentInfo.
|
|
*/
|
|
HRESULT
|
|
CTemplate::QueryInterface(const GUID &uidInterface, void **ppvObj)
|
|
{
|
|
if (uidInterface == IID_IUnknown || uidInterface == IID_IDebugDocumentProvider)
|
|
*ppvObj = static_cast<IDebugDocumentProvider *>(this);
|
|
|
|
else if (uidInterface == IID_IDebugDocument || uidInterface == IID_IDebugDocumentInfo || uidInterface == IID_IDebugDocumentText)
|
|
*ppvObj = static_cast<IDebugDocumentText *>(this);
|
|
|
|
else if (uidInterface == IID_IConnectionPointContainer)
|
|
*ppvObj = static_cast<IConnectionPointContainer *>(this);
|
|
|
|
else
|
|
*ppvObj = NULL;
|
|
|
|
if (*ppvObj)
|
|
{
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
else
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::AddRef
|
|
Adds a ref to this template, thread-safely
|
|
*/
|
|
ULONG
|
|
CTemplate::AddRef()
|
|
{
|
|
LONG cRefs = InterlockedIncrement(&m_cRefs);
|
|
|
|
Assert(FImplies(m_fIsValid,FImplies(cRefs > 1, m_pbStart != NULL)));
|
|
IF_DEBUG(TEMPLATE)
|
|
{
|
|
WriteRefTraceLog(gm_pTraceLog, cRefs, this);
|
|
}
|
|
|
|
#if PER_TEMPLATE_REFLOG
|
|
WriteRefTraceLog(m_pTraceLog, cRefs, this);
|
|
#endif
|
|
|
|
return cRefs;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::Release
|
|
Releases a ref to this template, thread-safely
|
|
*/
|
|
ULONG
|
|
CTemplate::Release()
|
|
{
|
|
LONG cRefs = InterlockedDecrement(&m_cRefs);
|
|
IF_DEBUG(TEMPLATE)
|
|
{
|
|
WriteRefTraceLog(gm_pTraceLog, cRefs, this);
|
|
}
|
|
|
|
#if PER_TEMPLATE_REFLOG
|
|
WriteRefTraceLog(m_pTraceLog, cRefs, this);
|
|
#endif
|
|
|
|
if (cRefs == 0)
|
|
delete this;
|
|
|
|
return cRefs;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::RemoveIncFile
|
|
Removes (by setting to null) an inc-file ptr from this template's inc-file list.
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::RemoveIncFile
|
|
(
|
|
CIncFile* pIncFile
|
|
)
|
|
{
|
|
|
|
// If the filemap count is non-zero the pointer to
|
|
// the array of filemaps has better not be null
|
|
DBGPRINTF(( DBG_CONTEXT, "m_cFilemaps = %d, m_rgpFilemaps %p\n", m_cFilemaps, m_rgpFilemaps));
|
|
Assert((m_cFilemaps <= 0) || (m_rgpFilemaps != NULL));
|
|
|
|
// find the inc-file in list
|
|
for(UINT i = 1; (i < m_cFilemaps) && (m_rgpFilemaps[i]->m_pIncFile != pIncFile); i++)
|
|
;
|
|
|
|
// assert that we found the inc-file in list
|
|
Assert((i < m_cFilemaps) && (m_rgpFilemaps[i]->m_pIncFile == pIncFile));
|
|
|
|
// set inc-file ptr null
|
|
m_rgpFilemaps[i]->m_pIncFile = NULL;
|
|
}
|
|
|
|
/*===================================================================
|
|
CTemplate::FTemplateObsolete
|
|
|
|
Test to see if the files this template depends on have changed since it
|
|
was compiled.
|
|
|
|
We use this in cases where we may have missed a change notification,
|
|
for example, when there were too many changes to record in our change
|
|
notification buffer. We check the last time the file was written too,
|
|
and the security descriptor, since changes to the security descriptor
|
|
aren't noted in the file last write time.
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
TRUE if the template is obsolete, else FALSE
|
|
*/
|
|
BOOL CTemplate::FTemplateObsolete(VOID)
|
|
{
|
|
BOOL fStatus = FALSE;
|
|
|
|
// On Windows 95 files should not be cached
|
|
// so assume the template has changed
|
|
if (!FIsWinNT())
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
for (UINT i = 0; i < m_cFilemaps; i++)
|
|
{
|
|
if (FFileChangedSinceCached(m_rgpFilemaps[i]->m_szPathTranslated, m_rgpFilemaps[i]->m_ftLastWriteTime))
|
|
{
|
|
// If the file write time has changed we know enough
|
|
// and can quit here
|
|
fStatus = TRUE;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// The file hasn't been writen to, but the security descriptor may
|
|
// have chagned
|
|
|
|
// Assert on non-valid security descriptor
|
|
|
|
if (NULL != m_rgpFilemaps[i]->m_pSecurityDescriptor)
|
|
{
|
|
|
|
PSECURITY_DESCRIPTOR pSecurityDescriptor = NULL;
|
|
DWORD dwSize = m_rgpFilemaps[i]->m_dwSecDescSize;
|
|
|
|
if( 0 == GetSecDescriptor(m_rgpFilemaps[i]->m_szPathTranslated, &pSecurityDescriptor, &dwSize))
|
|
{
|
|
if (pSecurityDescriptor)
|
|
{
|
|
// if the size is not the same then set fStatus to TRUE no need to compare memory blocks.
|
|
|
|
if(dwSize != GetSecurityDescriptorLength(m_rgpFilemaps[i]->m_pSecurityDescriptor))
|
|
{
|
|
fStatus = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// The size of the security descriptor hasn't changed
|
|
// but we have to compare the contents to make sure they haven't changed
|
|
fStatus = !(0 == memcmp(m_rgpFilemaps[i]->m_pSecurityDescriptor, pSecurityDescriptor, dwSize));
|
|
}
|
|
|
|
// We are done with the descriptor
|
|
free(pSecurityDescriptor);
|
|
|
|
}
|
|
else
|
|
{
|
|
// Since we failed to get a security descriptor
|
|
// assume the file has changed.
|
|
fStatus = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Quit as soon as we find a change
|
|
if (fStatus)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return fStatus;
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetSourceFileName
|
|
Returns name of source file on which this template is based
|
|
|
|
Returns
|
|
source file name
|
|
Side effects
|
|
none
|
|
*/
|
|
LPTSTR
|
|
CTemplate::GetSourceFileName(SOURCEPATHTYPE pathtype)
|
|
{
|
|
if (!m_rgpFilemaps)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
switch (pathtype)
|
|
{
|
|
case SOURCEPATHTYPE_PHYSICAL:
|
|
return((m_rgpFilemaps[0] ? m_rgpFilemaps[0]->m_szPathTranslated : NULL));
|
|
|
|
case SOURCEPATHTYPE_VIRTUAL:
|
|
return((m_rgpFilemaps[0] ? m_rgpFilemaps[0]->m_szPathInfo : NULL));
|
|
|
|
default:
|
|
return(NULL);
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::Count
|
|
Returns count of components of type tcomp contained in this template
|
|
|
|
Returns:
|
|
Count of components of type tcomp
|
|
Side effects:
|
|
None
|
|
*/
|
|
USHORT
|
|
CTemplate::Count
|
|
(
|
|
TEMPLATE_COMPONENT tcomp
|
|
)
|
|
{
|
|
Assert(NULL != m_pbStart);
|
|
|
|
// script engines and script blocks have the same count, stored in same slot
|
|
if(tcomp == tcompScriptEngine)
|
|
tcomp = tcompScriptBlock;
|
|
|
|
// counts are stored at start of template in sequential slots, starting with script blocks count
|
|
return * (USHORT*) ((USHORT*)m_pbStart + (tcomp - tcompScriptBlock));
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetScriptBlock
|
|
Gets ptrs to script engine name, prog lang id and script text of i-th script block.
|
|
|
|
Returns:
|
|
Out-parameters; see below
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::GetScriptBlock
|
|
(
|
|
UINT i, // script block id
|
|
LPSTR* pszScriptEngine, // ptr to script engine name (out-parameter)
|
|
PROGLANG_ID** ppProgLangId, // ptr to prog lang id (out-parameter)
|
|
LPCOLESTR* pwstrScriptText // ptr to wstr script text (out-parameter)
|
|
)
|
|
{
|
|
CByteRange brEngine; // engine name
|
|
CByteRange brScriptText; // script text
|
|
UINT cbAlignment; // count of bytes guid was shifted in WriteTemplate() to make it dword-aligned
|
|
BYTE* pbEngineInfo = GetAddress(tcompScriptEngine, (USHORT)i); // ptr to engine info
|
|
|
|
Assert(pbEngineInfo != NULL);
|
|
Assert(i < CountScriptEngines());
|
|
|
|
// Get engine name from start of engine info
|
|
ByteRangeFromPb(pbEngineInfo, brEngine);
|
|
|
|
ByteRangeFromPb(GetAddress(tcompScriptBlock, (USHORT)i), brScriptText);
|
|
|
|
Assert(!brEngine.IsNull());
|
|
Assert(!brScriptText.IsNull());
|
|
|
|
// Advance ptr past name to prog lang id
|
|
// length of prefix + length of name + NULL
|
|
pbEngineInfo += (sizeof(UINT) + (*pbEngineInfo) + 1);
|
|
|
|
// Get prog lang id - it will be on the next pointer sized boundary
|
|
cbAlignment = (UINT) (((DWORD_PTR) pbEngineInfo) % sizeof(DWORD));
|
|
if(cbAlignment > 0)
|
|
{pbEngineInfo += (sizeof(DWORD) - cbAlignment);}
|
|
|
|
*pszScriptEngine = (LPSTR)brEngine.m_pb;
|
|
*ppProgLangId = (PROGLANG_ID*)pbEngineInfo;
|
|
*pwstrScriptText = (LPCOLESTR)brScriptText.m_pb;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetObjectInfo
|
|
Returns i-th object-info in template as object name and
|
|
its clsid, scope, model
|
|
|
|
Returns:
|
|
HRESULT
|
|
Out-parameters; see below
|
|
Side effects:
|
|
*/
|
|
HRESULT
|
|
CTemplate::GetObjectInfo
|
|
(
|
|
UINT i, // object index
|
|
LPSTR* ppszObjectName, // address of object name ptr (out-parameter)
|
|
CLSID* pClsid, // address of object clsid
|
|
CompScope* pcsScope, // address of object scope
|
|
CompModel* pcmModel // address of object threading model
|
|
)
|
|
{
|
|
BYTE* pbObjectInfo = GetAddress(tcompObjectInfo, (USHORT)i); // ptr to current read location
|
|
CByteRange brName; // object name
|
|
UINT cbAlignment; // count of bytes guid was shifted in WriteTemplate() to make it dword-aligned
|
|
|
|
Assert(i < Count(tcompObjectInfo));
|
|
|
|
// Get name from start of object-info
|
|
ByteRangeFromPb(pbObjectInfo, brName);
|
|
Assert(!brName.IsNull());
|
|
|
|
// Advance ptr past name
|
|
// length of prefix + length of name + NULL
|
|
pbObjectInfo += (sizeof(UINT) + (*pbObjectInfo) + 1);
|
|
|
|
// Get clsid - it will be on the next DWORD boundary
|
|
cbAlignment = (UINT)(((DWORD_PTR) pbObjectInfo) % sizeof(DWORD));
|
|
if(cbAlignment > 0)
|
|
pbObjectInfo += (sizeof(DWORD) - cbAlignment);
|
|
|
|
*pClsid = *(CLSID*)pbObjectInfo;
|
|
pbObjectInfo += sizeof(CLSID);
|
|
|
|
// Get scope
|
|
*pcsScope = *(CompScope*)pbObjectInfo;
|
|
pbObjectInfo += sizeof(CompScope);
|
|
|
|
// Get model
|
|
*pcmModel = *(CompModel*)pbObjectInfo;
|
|
pbObjectInfo += sizeof(CompModel);
|
|
|
|
*ppszObjectName = (LPSTR)brName.m_pb;
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetHTMLBlock
|
|
Returns i-th HTML block
|
|
|
|
Parameters:
|
|
UINT i block number
|
|
LPSTR* pszHTML [out] html text
|
|
ULONG* pcbHTML [out] html text length
|
|
ULONG* pcbSrcOffs [out] offset in the source file
|
|
LPSTR* pszSrcIncFile [out] include source file name
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
HRESULT
|
|
CTemplate::GetHTMLBlock
|
|
(
|
|
UINT i,
|
|
LPSTR* pszHTML,
|
|
ULONG* pcbHTML,
|
|
ULONG* pcbSrcOffs,
|
|
LPSTR* pszSrcIncFile
|
|
)
|
|
{
|
|
Assert(i < Count(tcompHTMLBlock));
|
|
|
|
// this was added due to user attempt to access the method with an invalid array offset
|
|
//
|
|
if ( i >= Count(tcompHTMLBlock) )
|
|
return E_FAIL;
|
|
|
|
// get address of the block start in template memory
|
|
BYTE *pbBlock = GetAddress(tcompHTMLBlock, (USHORT)i);
|
|
Assert(pbBlock);
|
|
|
|
// retrieve the byte range of the html code
|
|
CByteRange brHTML;
|
|
ByteRangeFromPb(pbBlock, brHTML);
|
|
*pszHTML = (LPSTR)brHTML.m_pb;
|
|
*pcbHTML = brHTML.m_cb;
|
|
|
|
// advance to the source offset
|
|
pbBlock += sizeof(ULONG); // skip prefix
|
|
pbBlock += brHTML.m_cb+1; // skip html bytes (incl. '\0')
|
|
|
|
// Add byte aligment which is done in ByteAlignOffset()
|
|
if ((reinterpret_cast<ULONG_PTR>(pbBlock)) & 3)
|
|
pbBlock = reinterpret_cast<BYTE *>((reinterpret_cast<ULONG_PTR>(pbBlock) + 4) & ~3);
|
|
|
|
*pcbSrcOffs = *((ULONG*)pbBlock);
|
|
|
|
// advance to the source name length
|
|
pbBlock += sizeof(ULONG); // skip source offset prefix
|
|
ULONG cbSrcIncFile = *((ULONG *)pbBlock); // inc file name length
|
|
pbBlock += sizeof(ULONG); // skip inc file name length
|
|
*pszSrcIncFile = (cbSrcIncFile > 0) ? (LPSTR)pbBlock : NULL;
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetScriptSourceInfo
|
|
Returns line number and source file name a given target line in a given script engine.
|
|
|
|
Returns
|
|
line number and source file name (as out-parameters)
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::GetScriptSourceInfo
|
|
(
|
|
UINT idEngine, // script engine id
|
|
int iTargetLine, // target line number
|
|
LPTSTR* pszPathInfo, // ptr to source file virtual path (out-parameter)
|
|
LPTSTR* pszPathTranslated, // ptr to source file real path (out-parameter)
|
|
ULONG* piSourceLine, // ptr to source line number (out-parameter)
|
|
ULONG* pichSourceLine, // ptr to source file offset (out-parameter)
|
|
BOOLB* pfGuessedLine // ptr to flag: did we guess the source line?
|
|
)
|
|
{
|
|
// Initialize some out parameters
|
|
if (pszPathInfo)
|
|
*pszPathInfo = _T("?"); // In case we don't ever find the path
|
|
|
|
if (pszPathTranslated)
|
|
*pszPathTranslated = _T("?"); // In case we don't ever find the path
|
|
|
|
if (piSourceLine)
|
|
*piSourceLine = 0;
|
|
|
|
if (pichSourceLine)
|
|
*pichSourceLine = 0;
|
|
|
|
if (pfGuessedLine)
|
|
*pfGuessedLine = FALSE;
|
|
|
|
if (iTargetLine <=0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// CHANGE: The rgSourceInfo array is now ZERO based. Decrement target line
|
|
// to convert.
|
|
--iTargetLine;
|
|
|
|
// CONSIDER: Make these assertions?
|
|
if(!m_rgrgSourceInfos)
|
|
return;
|
|
if(idEngine > (m_cScriptEngines - 1)) // bug 375: check vs. array bound
|
|
return;
|
|
if(size_t(iTargetLine) >= m_rgrgSourceInfos[idEngine].length()) // bug 375: check vs. array bound
|
|
return;
|
|
|
|
vector<CSourceInfo> *prgSourceInfos = &m_rgrgSourceInfos[idEngine];
|
|
|
|
// bug 379: move backwards through target lines, starting with the caller's, until we find one whose
|
|
// fIsHTML flag is false. this handles the case where vbs flags a manufactured line as in error;
|
|
// we assume the actual error occurred at the most recent authored line
|
|
while (iTargetLine >= 0 && (*prgSourceInfos)[iTargetLine].m_fIsHTML)
|
|
{
|
|
--iTargetLine;
|
|
if (pfGuessedLine)
|
|
*pfGuessedLine = TRUE;
|
|
}
|
|
|
|
|
|
if (iTargetLine >= 0)
|
|
{
|
|
if (pszPathInfo && (*prgSourceInfos)[iTargetLine].m_pfilemap != NULL)
|
|
*pszPathInfo = (*prgSourceInfos)[iTargetLine].m_pfilemap->m_szPathInfo;
|
|
|
|
if (pszPathTranslated && (*prgSourceInfos)[iTargetLine].m_pfilemap != NULL)
|
|
*pszPathTranslated = (*prgSourceInfos)[iTargetLine].m_pfilemap->m_szPathTranslated;
|
|
|
|
if (piSourceLine)
|
|
*piSourceLine = (*prgSourceInfos)[iTargetLine].m_idLine;
|
|
|
|
if (pichSourceLine)
|
|
*pichSourceLine = (*prgSourceInfos)[iTargetLine].m_cchSourceOffset;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetPositionOfLine
|
|
Get the character offset of a line of source
|
|
(Debugger API Extended to specify a filemap)
|
|
*/
|
|
HRESULT
|
|
CTemplate::GetPositionOfLine
|
|
(
|
|
CFileMap *pFilemap,
|
|
ULONG cLineNumber,
|
|
ULONG *pcCharacterPosition
|
|
)
|
|
{
|
|
// NOTE:
|
|
// The table is not binary-searchable because include files
|
|
// will start a new line ordering
|
|
//
|
|
// Algorithm:
|
|
//
|
|
// Find the largest source line N across all engines, such that
|
|
// N <= cLineNumber and the line corresponds to an line
|
|
// in the appropriate file.
|
|
//
|
|
CSourceInfo *pSourceInfoLE = NULL;
|
|
++cLineNumber; // Convert zero-based line # to one-based
|
|
|
|
// Find the correct offset
|
|
for (unsigned idEngine = 0; idEngine < m_cScriptEngines; ++idEngine)
|
|
{
|
|
vector<CSourceInfo> *prgSourceInfos = &m_rgrgSourceInfos[idEngine];
|
|
|
|
// Loop through all lines EXCEPT the EOF line
|
|
for (unsigned j = 0; j < prgSourceInfos->length() - 1; ++j)
|
|
{
|
|
CSourceInfo *pSourceInfo = &(*prgSourceInfos)[j];
|
|
if (pFilemap == pSourceInfo->m_pfilemap &&
|
|
pSourceInfo->m_idLine <= cLineNumber &&
|
|
(pSourceInfoLE == NULL || pSourceInfo->m_idLine > pSourceInfoLE->m_idLine))
|
|
{
|
|
pSourceInfoLE = pSourceInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We had better be able to map all line numbers to offsets, unless they passed a bogus line
|
|
// (in which case we still find an offset)
|
|
//
|
|
Assert (pSourceInfoLE != NULL);
|
|
|
|
if (pSourceInfoLE == NULL) {
|
|
return E_FAIL;
|
|
}
|
|
*pcCharacterPosition = pSourceInfoLE->m_cchSourceOffset;
|
|
#if 0
|
|
IF_DEBUG(SCRIPT_DEBUGGER)
|
|
{
|
|
wchar_t wszSourceText[SNIPPET_SIZE + 1], wszTargetText[SNIPPET_SIZE + 1], wszDebugMessage[256];
|
|
GetScriptSnippets(
|
|
pSourceInfoLE->m_cchSourceOffset, pSourceInfoLE->m_pfilemap,
|
|
0, 0,
|
|
wszSourceText, NULL
|
|
);
|
|
|
|
DBGPRINTF((
|
|
DBG_CONTEXT,
|
|
"Source Line %d corresponds to source offset %d (Text: \"%S\")\n",
|
|
cLineNumber - 1, pSourceInfoLE->m_cchSourceOffset,
|
|
wszSourceText
|
|
));
|
|
}
|
|
#endif
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetLineOfPosition
|
|
Get the line # & offset in line of an arbitrary character offset in source
|
|
(Debugger API Extended to specify a filemap)
|
|
*/
|
|
HRESULT CTemplate::GetLineOfPosition
|
|
(
|
|
CFileMap *pFilemap,
|
|
ULONG cCharacterPosition,
|
|
ULONG *pcLineNumber,
|
|
ULONG *pcCharacterOffsetInLine
|
|
)
|
|
{
|
|
// FAIL if source offset totally off-base
|
|
if (cCharacterPosition >= pFilemap->m_cChars)
|
|
return E_FAIL;
|
|
|
|
// NOTE:
|
|
// The table is not binary-searchable because include files
|
|
// will start a new line ordering
|
|
//
|
|
// Algorithm:
|
|
//
|
|
// Find the largest source line N across all engines, such that
|
|
// N <= cLineNumber and the line corresponds to an line
|
|
// in the appropriate file.
|
|
//
|
|
CSourceInfo *pSourceInfoLE = NULL;
|
|
|
|
// Find the correct offset
|
|
for (unsigned idEngine = 0; idEngine < m_cScriptEngines; ++idEngine)
|
|
{
|
|
vector<CSourceInfo> *prgSourceInfos = &m_rgrgSourceInfos[idEngine];
|
|
|
|
// Loop through all lines EXCEPT the EOF line
|
|
for (unsigned j = 0; j < prgSourceInfos->length() - 1; ++j)
|
|
{
|
|
CSourceInfo *pSourceInfo = &(*prgSourceInfos)[j];
|
|
if (pFilemap == pSourceInfo->m_pfilemap &&
|
|
pSourceInfo->m_cchSourceOffset <= cCharacterPosition &&
|
|
(pSourceInfoLE == NULL || pSourceInfo->m_cchSourceOffset > pSourceInfoLE->m_cchSourceOffset))
|
|
{
|
|
pSourceInfoLE = pSourceInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We had better be able to map all offsets to line numbers, unless they passed a bogus offset
|
|
// (in which case we still find a line #, but may go out of range for the offset in line.
|
|
// That case is handled later)
|
|
//
|
|
Assert (pSourceInfoLE != NULL);
|
|
|
|
if (pSourceInfoLE == NULL) {
|
|
return E_FAIL;
|
|
}
|
|
|
|
*pcLineNumber = pSourceInfoLE->m_idLine - 1; // Convert to zero-based line #
|
|
*pcCharacterOffsetInLine = cCharacterPosition - pSourceInfoLE->m_cchSourceOffset;
|
|
#if 0
|
|
IF_DEBUG(SCRIPT_DEBUGGER)
|
|
{
|
|
wchar_t wszSourceText[SNIPPET_SIZE + 1], wszTargetText[SNIPPET_SIZE + 1], wszDebugMessage[256];
|
|
GetScriptSnippets(
|
|
pSourceInfoLE->m_cchSourceOffset, pSourceInfoLE->m_pfilemap,
|
|
0, 0,
|
|
wszSourceText, NULL
|
|
);
|
|
|
|
DBGPRINTF((
|
|
DBG_CONTEXT,
|
|
"Source offset %d corresponds to source line %d (Text: \"%S\")\n",
|
|
pSourceInfoLE->m_cchSourceOffset, *pcLineNumber,
|
|
wszSourceText
|
|
));
|
|
}
|
|
|
|
DBGPRINTF((
|
|
DBG_CONTEXT,
|
|
"Source offset %d corresponds to source line %d (Text: \"%S\")\n",
|
|
pSourceInfoLE->m_cchSourceOffset, *pcLineNumber,
|
|
wszSourceText
|
|
));
|
|
}
|
|
#endif
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetSourceOffset
|
|
Convert a character offset relative to the target script to the appropriate
|
|
offset in the source.
|
|
|
|
NOTE: offsets in the middle of a target line are converted to the
|
|
offset relative to the beginning of source line - NOT to the
|
|
precise source offset.
|
|
|
|
this is OK because debugger ultimately wants the offset of the
|
|
beginning of line. It is a lot of work to do the precise conversion
|
|
due to the translation of "=" to Response.Write & HTML to
|
|
Response.WriteBlock
|
|
|
|
Also, because of these translations, we return the length of the segment
|
|
calculated during compilation, and throw away the length the scripting
|
|
engine sent to us.
|
|
*/
|
|
void
|
|
CTemplate::GetSourceOffset
|
|
(
|
|
ULONG idEngine,
|
|
ULONG cchTargetOffset,
|
|
TCHAR **pszSourceFile,
|
|
ULONG *pcchSourceOffset,
|
|
ULONG *pcchSourceText
|
|
)
|
|
{
|
|
Assert (idEngine < m_cScriptEngines);
|
|
vector<CSourceInfo> *prgSourceInfos = &m_rgrgSourceInfos[idEngine];
|
|
|
|
// Find the closest offset in the source
|
|
// This is the largest target offset N, such that N <= cchTargetOffset
|
|
CSourceInfo *pSourceInfo;
|
|
GetBracketingPair(
|
|
cchTargetOffset, // value to search for
|
|
prgSourceInfos->begin(), prgSourceInfos->end(), // array to search
|
|
CTargetOffsetOrder(), // ordering predicate
|
|
&pSourceInfo, static_cast<CSourceInfo **>(NULL) // return values
|
|
);
|
|
|
|
// Since the first offset is zero, which is less than all other conceivable offsets,
|
|
// the offset must have been found or else there is a bug.
|
|
Assert (pSourceInfo != NULL);
|
|
Assert (cchTargetOffset >= pSourceInfo->m_cchTargetOffset);
|
|
#if 0
|
|
IF_DEBUG(SCRIPT_DEBUGGER)
|
|
{
|
|
wchar_t wszSourceText[SNIPPET_SIZE + 1], wszTargetText[SNIPPET_SIZE + 1], wszDebugMessage[256];
|
|
GetScriptSnippets(
|
|
pSourceInfo->m_cchSourceOffset, pSourceInfo->m_pfilemap,
|
|
cchTargetOffset, idEngine,
|
|
wszSourceText, wszTargetText
|
|
);
|
|
DBGPRINTF((
|
|
DBG_CONTEXT,
|
|
"Target offset %d (Text: \"%S\") corresponds to source offset %d (Text: \"%S\") (Length is %d)\n",
|
|
cchTargetOffset, wszTargetText,
|
|
pSourceInfo->m_cchSourceOffset, wszSourceText,
|
|
pSourceInfo->m_cchSourceText
|
|
));
|
|
}
|
|
#endif
|
|
*pszSourceFile = pSourceInfo->m_pfilemap->m_szPathTranslated;
|
|
*pcchSourceOffset = pSourceInfo->m_cchSourceOffset;
|
|
*pcchSourceText = pSourceInfo->m_cchSourceText;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetTargetOffset
|
|
Convert a character offset relative to the source script to the appropriate
|
|
offset in the target.
|
|
|
|
Returns:
|
|
TRUE - source offset corresponds to script
|
|
FALSE - source offset corresponds to HTML
|
|
|
|
NOTES:
|
|
1. This function is very slow. consider caching the value of this function
|
|
(The CTemplateDocumentContext class does this.)
|
|
|
|
2. This function returns the source offset in the master include file -
|
|
if the target offset corresponds to an offset in a header file, then
|
|
the offset to the #include line in the source is returned.
|
|
|
|
3. offsets in the middle of a target line are converted to the
|
|
offset relative to the beginning of source line - NOT to the
|
|
precise source offset.
|
|
|
|
this is OK because the debugger ultimately wants the offset of the
|
|
beginning of line. It is a lot of work to do the precise conversion
|
|
due to the translation of "=" to Response.Write & HTML to
|
|
Response.WriteBlock
|
|
|
|
CONSIDER:
|
|
Figure out a better way to do this
|
|
*/
|
|
BOOL CTemplate::GetTargetOffset
|
|
(
|
|
TCHAR *szSourceFile,
|
|
ULONG cchSourceOffset,
|
|
/* [out] */ ULONG *pidEngine,
|
|
/* [out] */ ULONG *pcchTargetOffset
|
|
)
|
|
{
|
|
// NOTE:
|
|
// The table is not binary-searchable because of two factors:
|
|
// 1. Include files will start a new line ordering
|
|
// 2. For engine 0, tagged scripts will be re-arranged in
|
|
// the target code to reside after all primary script in
|
|
// engine 0.
|
|
//
|
|
// Algorithm:
|
|
//
|
|
// Find the largest source offset N across all engines, such that
|
|
// N <= cchSourceOffset and the offset corresponds to an offset
|
|
// in the appropriate file.
|
|
//
|
|
CSourceInfo *pSourceInfoLE = NULL;
|
|
unsigned idEngineLE = 0;
|
|
|
|
// Find the correct offset
|
|
for (unsigned idEngine = 0; idEngine < m_cScriptEngines; ++idEngine)
|
|
{
|
|
vector<CSourceInfo> *prgSourceInfos = &m_rgrgSourceInfos[idEngine];
|
|
|
|
// Loop through all lines EXCEPT the EOF line
|
|
for (unsigned j = 0; j < prgSourceInfos->length() - 1; ++j)
|
|
{
|
|
CSourceInfo *pSourceInfo = &(*prgSourceInfos)[j];
|
|
if (_tcscmp(pSourceInfo->m_pfilemap->m_szPathTranslated, szSourceFile) == 0 &&
|
|
pSourceInfo->m_cchSourceOffset <= cchSourceOffset &&
|
|
(pSourceInfoLE == NULL || pSourceInfo->m_cchSourceOffset > pSourceInfoLE->m_cchSourceOffset))
|
|
{
|
|
pSourceInfoLE = pSourceInfo;
|
|
idEngineLE = idEngine;
|
|
}
|
|
}
|
|
}
|
|
|
|
// There won't be a valid offset in the case where there is no
|
|
// code corresponding to the first line in the file (this only
|
|
// occurs when the first line is whitespace, because there is no
|
|
// corresponding "Response.WriteBlock" call there)
|
|
//
|
|
// In that case, return FALSE, which will cause the caller to fail
|
|
//
|
|
if (pSourceInfoLE == NULL)
|
|
{
|
|
*pidEngine = 0;
|
|
*pcchTargetOffset = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
*pidEngine = idEngineLE;
|
|
*pcchTargetOffset = pSourceInfoLE->m_cchTargetOffset;
|
|
#if 0
|
|
IF_DEBUG(SCRIPT_DEBUGGER)
|
|
{
|
|
wchar_t wszSourceText[SNIPPET_SIZE + 1], wszTargetText[SNIPPET_SIZE + 1], wszDebugMessage[256];
|
|
GetScriptSnippets(
|
|
cchSourceOffset, pSourceInfoLE->m_pfilemap,
|
|
*pcchTargetOffset, *pidEngine,
|
|
wszSourceText, wszTargetText
|
|
);
|
|
DBGPRINTF((
|
|
DBG_CONTEXT,
|
|
"Source offset %d (Text: \"%S\") corresponds to target offset %d (Text: \"%S\")\n",
|
|
cchSourceOffset, wszSourceText,
|
|
*pcchTargetOffset, wszTargetText
|
|
));
|
|
}
|
|
#endif
|
|
return !pSourceInfoLE->m_fIsHTML;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetActiveScript
|
|
Return a cached script from the template - only used in debug mode
|
|
*/
|
|
CActiveScriptEngine *CTemplate::GetActiveScript(ULONG idEngine)
|
|
{
|
|
if (m_rgpDebugScripts == NULL)
|
|
return NULL;
|
|
|
|
else
|
|
{
|
|
Assert (idEngine < m_cScriptEngines);
|
|
CActiveScriptEngine *pEng = m_rgpDebugScripts[idEngine];
|
|
if (pEng)
|
|
pEng->AddRef();
|
|
|
|
return pEng;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::AddScript
|
|
add an active script to the template object
|
|
*/
|
|
HRESULT CTemplate::AddScript(ULONG idEngine, CActiveScriptEngine *pScriptEngine)
|
|
{
|
|
if (m_rgpDebugScripts == NULL)
|
|
{
|
|
if (
|
|
(m_rgpDebugScripts = new CActiveScriptEngine *[m_cScriptEngines])
|
|
== NULL
|
|
)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
memset(m_rgpDebugScripts, 0, m_cScriptEngines * sizeof(CActiveScriptEngine *));
|
|
}
|
|
|
|
Assert (idEngine < m_cScriptEngines);
|
|
CActiveScriptEngine **ppScriptElem = &m_rgpDebugScripts[idEngine];
|
|
|
|
if (*ppScriptElem != NULL)
|
|
(*ppScriptElem)->Release();
|
|
|
|
*ppScriptElem = pScriptEngine;
|
|
pScriptEngine->AddRef();
|
|
|
|
// Initialize the script engine now (is currently uninitialized)
|
|
// so that the debugger user can set breakpoints.
|
|
IActiveScript *pActiveScript = pScriptEngine->GetActiveScript();
|
|
HRESULT hr;
|
|
|
|
TRY
|
|
hr = pActiveScript->SetScriptSite(static_cast<IActiveScriptSite *>(pScriptEngine));
|
|
CATCH(nExcept)
|
|
HandleErrorMissingFilename(IDE_SCRIPT_ENGINE_GPF,
|
|
NULL,
|
|
TRUE,
|
|
nExcept,
|
|
"IActiveScript::SetScriptSite()",
|
|
"CTemplate::AddScript()");
|
|
hr = nExcept;
|
|
END_TRY
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
*ppScriptElem = NULL;
|
|
return E_FAIL;
|
|
}
|
|
|
|
TRY
|
|
hr = pActiveScript->SetScriptState(SCRIPTSTATE_INITIALIZED);
|
|
CATCH(nExcept)
|
|
HandleErrorMissingFilename(IDE_SCRIPT_ENGINE_GPF,
|
|
NULL,
|
|
TRUE,
|
|
nExcept,
|
|
"IActiveScript::SetScriptState()",
|
|
"CTemplate::AddScript()");
|
|
hr = nExcept;
|
|
END_TRY
|
|
|
|
if (FAILED(hr))
|
|
return E_FAIL;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::AppendMapFile
|
|
Appends a filemap to the workstore and memory-maps its file
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
Allocates memory; throws exception on error
|
|
*/
|
|
void
|
|
CTemplate::AppendMapFile
|
|
(
|
|
LPCTSTR szFileSpec, // file spec for this file
|
|
CFileMap* pfilemapCurrent, // ptr to filemap of parent file
|
|
BOOLB fVirtual, // is file spec virtual or relative?
|
|
CHitObj* pHitObj, // ptr to template's hit object
|
|
BOOLB fGlobalAsa // is this file the global.asa file?
|
|
)
|
|
{
|
|
// alloc or realloc as needed
|
|
if(m_cFilemaps++ == 0)
|
|
m_rgpFilemaps = (CFileMap**) CTemplate::SmallMalloc(sizeof(CFileMap*));
|
|
else
|
|
m_rgpFilemaps = (CFileMap**) CTemplate::SmallReAlloc(m_rgpFilemaps, m_cFilemaps * sizeof(CFileMap*));
|
|
|
|
if(NULL == m_rgpFilemaps)
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
if(NULL == (m_rgpFilemaps[m_cFilemaps - 1] = new CFileMap))
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
// map the file
|
|
m_rgpFilemaps[m_cFilemaps - 1]->MapFile(
|
|
szFileSpec,
|
|
m_szApplnVirtPath,
|
|
pfilemapCurrent,
|
|
fVirtual,
|
|
pHitObj,
|
|
fGlobalAsa
|
|
);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetSegmentsFromFile
|
|
Gets source segments from a source file by calling ExtractAndProcessSegment
|
|
until there are no more segments; populates WorkStore with info on source segments.
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::GetSegmentsFromFile
|
|
(
|
|
CFileMap& filemap, // this file's file map
|
|
CWorkStore& WorkStore, // working storage for source segments
|
|
CHitObj* pHitObj, // Browser request object
|
|
BOOL fIsHTML
|
|
)
|
|
{
|
|
CByteRange brSearch; // byte range to search for source segments
|
|
_TOKEN rgtknOpeners[TOKEN_OPENERS_MAX]; // array of permitted open tokens
|
|
UINT ctknOpeners; // count of permitted open tokens
|
|
SOURCE_SEGMENT ssegThisFile = ssegHTML; // Either HTML or <SCRIPT> segment
|
|
BOOL fPrevCodePageSet = FALSE;
|
|
UINT wPrevCodePage;
|
|
|
|
// init search range to all of file - NOTE we ignore high dword of file size
|
|
brSearch.m_pb = filemap.m_pbStartOfFile;
|
|
brSearch.m_cb = filemap.GetSize();
|
|
|
|
if (fIsHTML)
|
|
{
|
|
// populate array of permitted open tokens
|
|
ctknOpeners = 4;
|
|
rgtknOpeners[0] = CTokenList::tknOpenPrimaryScript;
|
|
rgtknOpeners[1] = CTokenList::tknOpenTaggedScript;
|
|
rgtknOpeners[2] = CTokenList::tknOpenObject;
|
|
rgtknOpeners[3] = CTokenList::tknOpenHTMLComment;
|
|
}
|
|
else
|
|
{
|
|
ctknOpeners = 1;
|
|
rgtknOpeners[0] = CTokenList::tknOpenHTMLComment;
|
|
ssegThisFile = ssegTaggedScript;
|
|
}
|
|
|
|
TRY
|
|
|
|
if ((brSearch.m_cb >= 2)
|
|
&& (((brSearch.m_pb[0] == 0xff) && (brSearch.m_pb[1] == 0xfe))
|
|
|| ((brSearch.m_pb[0] == 0xfe) && (brSearch.m_pb[1] == 0xff)))) {
|
|
ThrowError(brSearch.m_pb,IDE_TEMPLATE_UNICODE_NOTSUP);
|
|
return;
|
|
}
|
|
|
|
// check for the UTF-8 BOM mark. If present, then treat this similar to
|
|
// seeing @CODEPAGE=65001. Note that previous values are retained in the
|
|
// event that there are differing @CODEPAGE settings. This probably should
|
|
// be an error in itself, but I can imagine that this might break a lot of
|
|
// apps as more and more UTF8 files are put into use.
|
|
|
|
if ((brSearch.m_cb >= 3)
|
|
&& (brSearch.m_pb[0] == 0xEF)
|
|
&& (brSearch.m_pb[1] == 0xBB)
|
|
&& (brSearch.m_pb[2] == 0xBF)) {
|
|
|
|
pHitObj->SetCodePage(65001);
|
|
|
|
fPrevCodePageSet = m_fCodePageSet;
|
|
wPrevCodePage = m_wCodePage;
|
|
|
|
m_fCodePageSet = TRUE;
|
|
m_wCodePage = 65001;
|
|
brSearch.Advance(3);
|
|
}
|
|
|
|
|
|
// Process source segments until we run out of them, i.e. until search segment is empty
|
|
// NOTE we pass current filemap as 'parent file' to ExtractAndProcessSegment
|
|
// NOTE ExtractAndProcessSegment appends source segments to WorkStore, advancing brSearch as it goes
|
|
while(!brSearch.IsNull())
|
|
ExtractAndProcessSegment(
|
|
brSearch,
|
|
ssegThisFile,
|
|
rgtknOpeners,
|
|
ctknOpeners,
|
|
&filemap,
|
|
WorkStore,
|
|
pHitObj,
|
|
ssegThisFile == ssegTaggedScript,
|
|
fIsHTML
|
|
);
|
|
|
|
CATCH(hrException)
|
|
/*
|
|
NOTE we indicate 'generic error' by m_idErrMsg == 0; this happens as we move
|
|
up the 'include file stack' after processing a specific error (m_idErrMsg != 0).
|
|
Only the specific error is processed; generic error, we simply re-throw exception.
|
|
*/
|
|
if(m_idErrMsg != 0)
|
|
{
|
|
// process specific error
|
|
ProcessSpecificError(filemap, pHitObj);
|
|
|
|
// reset err message so next msg will be generic as we move up the stack
|
|
m_idErrMsg = 0;
|
|
}
|
|
|
|
THROW(hrException);
|
|
|
|
END_TRY
|
|
|
|
if (fPrevCodePageSet){
|
|
m_wCodePage = wPrevCodePage;
|
|
pHitObj->SetCodePage(wPrevCodePage);
|
|
}
|
|
}
|
|
|
|
|
|
#define SZ_REG_LANGUAGE_ENGINES "SYSTEM\\CurrentControlSet\\Services\\W3SVC\\ASP\\LanguageEngines\\"
|
|
/* ============================================================================
|
|
CTemplate::GetLanguageEquivalents
|
|
Gets the "Write", "WriteBlock", etc. equivalents from registry for primary scripting language
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
Throws on error
|
|
*/
|
|
void
|
|
CTemplate::GetLanguageEquivalents
|
|
(
|
|
)
|
|
{
|
|
CByteRange brPrimaryEngine;
|
|
m_pWorkStore->m_ScriptStore.m_bufEngineNames.GetItem(0, brPrimaryEngine); // 0-th engine is primary
|
|
|
|
#if DBG
|
|
|
|
/* DEBUG ONLY - to test the reg lookup code you must:
|
|
1) create the key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\ASP\LanguageEngines\Debug
|
|
2) put the language reg values for WriteBlock and Write under the language you want to test
|
|
*/
|
|
HANDLE hKey;
|
|
if(ERROR_SUCCESS != RegOpenKeyExA(
|
|
HKEY_LOCAL_MACHINE,
|
|
"SYSTEM\\CurrentControlSet\\Services\\W3SVC\\ASp\\LanguageEngines\\Debug",
|
|
0,
|
|
KEY_READ,
|
|
(PHKEY)&hKey
|
|
))
|
|
{
|
|
return;
|
|
}
|
|
|
|
RegCloseKey((HKEY)hKey);
|
|
|
|
#else
|
|
|
|
// if the primary language is one of the big two, return; we don't need to look up equivalents
|
|
if(brPrimaryEngine.FMatchesSz("VBScript"))
|
|
return;
|
|
if(brPrimaryEngine.FMatchesSz("JScript"))
|
|
return;
|
|
if(brPrimaryEngine.FMatchesSz("JavaScript"))
|
|
return;
|
|
if(brPrimaryEngine.FMatchesSz("LiveScript"))
|
|
return;
|
|
|
|
#endif // DBG
|
|
|
|
/* query the registry; language equivalents are stored in:
|
|
HKEY_LOCAL_MACHINE
|
|
key: SYSTEM
|
|
key: CurrentControlSet
|
|
key: Services
|
|
key: W3SVC
|
|
key: ASP
|
|
key: LanguageEngines
|
|
key: <LanguageName>
|
|
value: Write data: <replacement syntax for Response.Write(|)>
|
|
value: WriteBlock data: <replacement syntax for Response.WriteBlock(|)>
|
|
*/
|
|
STACK_BUFFER( tempRegKeyPath, 512 );
|
|
|
|
UINT cchRegKeyPath = strlen(SZ_REG_LANGUAGE_ENGINES);
|
|
|
|
if (!tempRegKeyPath.Resize(cchRegKeyPath + brPrimaryEngine.m_cb + 1)) {
|
|
SetLastError(E_OUTOFMEMORY);
|
|
return;
|
|
}
|
|
|
|
LPSTR szRegKeyPath = static_cast<LPSTR> (tempRegKeyPath.QueryPtr());
|
|
|
|
LPSTR pch = szRegKeyPath;
|
|
|
|
strcpy(pch, SZ_REG_LANGUAGE_ENGINES);
|
|
pch += cchRegKeyPath;
|
|
strncpy(pch, (const char *) brPrimaryEngine.m_pb, brPrimaryEngine.m_cb);
|
|
pch += brPrimaryEngine.m_cb;
|
|
*pch = '\0';
|
|
|
|
HANDLE hKeyScriptLanguage; // handle of script language reg key
|
|
|
|
if(ERROR_SUCCESS == RegOpenKeyExA(
|
|
HKEY_LOCAL_MACHINE, // handle constant
|
|
(const char*) szRegKeyPath, // LPCSTR lpSubKey subkey to open
|
|
0, // DWORD ulOptions reserved; must be zero
|
|
KEY_QUERY_VALUE, // REGSAM samDesired security access mask
|
|
(PHKEY) &hKeyScriptLanguage // PHKEY phkResult address of handle of open key
|
|
))
|
|
{
|
|
SetLanguageEquivalent(hKeyScriptLanguage, "Write", &(m_pWorkStore->m_szWriteOpen), &(m_pWorkStore->m_szWriteClose));
|
|
SetLanguageEquivalent(hKeyScriptLanguage, "WriteBlock", &(m_pWorkStore->m_szWriteBlockOpen), &(m_pWorkStore->m_szWriteBlockClose));
|
|
RegCloseKey((HKEY) hKeyScriptLanguage);
|
|
}
|
|
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::SetLanguageEquivalent
|
|
Sets a "language equivalent" from the registry.
|
|
|
|
Returns:
|
|
language item opener and closer as out-parameters
|
|
Ex: "Response.Write(" and ")"
|
|
Side effects:
|
|
Throws on error
|
|
*/
|
|
void
|
|
CTemplate::SetLanguageEquivalent
|
|
(
|
|
HANDLE hKeyScriptLanguage, // reg key
|
|
LPSTR szLanguageItem, // reg value name - "Write", "WriteBlock", etc.
|
|
LPSTR* pszOpen, // ptr to language item opener, e.g. "Response.Write(" (out-parameter)
|
|
LPSTR* pszClose // ptr to language item closer, e.g. ")" (out-parameter)
|
|
)
|
|
{
|
|
LONG lError;
|
|
DWORD cbSyntax;
|
|
LPSTR szSyntax;
|
|
char* pchInsert;
|
|
UINT cchOpen;
|
|
UINT cchClose;
|
|
|
|
// query registry to get buffer size
|
|
lError = RegQueryValueExA(
|
|
(HKEY) hKeyScriptLanguage, // handle of key to query
|
|
szLanguageItem, // name of value to query
|
|
NULL, // reserved; must be NULL
|
|
NULL, // ptr to value type; not required
|
|
NULL, // ptr to data buffer
|
|
&cbSyntax // ptr to data buffer size
|
|
);
|
|
|
|
if(ERROR_FILE_NOT_FOUND == lError)
|
|
// if we don't find szLanguageItem in registry, return silently, leaving *pszOpen and *pszClose unchanged
|
|
return;
|
|
else if((ERROR_MORE_DATA != lError) && (ERROR_SUCCESS != lError))
|
|
THROW(lError);
|
|
|
|
Assert(cbSyntax > 0);
|
|
|
|
// allocate buffer and re-query registry to get syntax string
|
|
// NOTE RegQueryValueEx returns cbSyntax that includes room for '\0' terminator
|
|
|
|
STACK_BUFFER(tempSyntax, 64);
|
|
|
|
if (!tempSyntax.Resize(cbSyntax)) {
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
szSyntax = static_cast<LPSTR> (tempSyntax.QueryPtr());
|
|
lError = RegQueryValueExA(
|
|
(HKEY) hKeyScriptLanguage, // handle of key to query
|
|
szLanguageItem, // name of value to query
|
|
NULL, // reserved; must be NULL
|
|
NULL, // ptr to value type; not required
|
|
(LPBYTE) szSyntax, // ptr to data buffer
|
|
&cbSyntax // ptr to data buffer size
|
|
);
|
|
|
|
/* NOTE there is the slight possibility of ERROR_FILE_NOT_FOUND or ERROR_MORE_DATA
|
|
if the registry value was deleted or changed between the first and second calls to RegQueryValueEx.
|
|
Since this occurs with vanishingly small probability, we throw (instead of coding the re-try logic).
|
|
*/
|
|
if(ERROR_SUCCESS != lError)
|
|
THROW(lError);
|
|
|
|
pchInsert = szSyntax;
|
|
|
|
while(*pchInsert != '|' && *pchInsert != '\0')
|
|
pchInsert++;
|
|
|
|
cchOpen = DIFF(pchInsert - szSyntax);
|
|
|
|
cchClose = *pchInsert == '|'
|
|
? cbSyntax - cchOpen - 2 // found insert symbol: deduct 2 chars, 1 for insert symbol, 1 for '\0'
|
|
: cbSyntax - cchOpen - 1; // didn't find insert symbol: deduct 1 char for '\0'
|
|
|
|
Assert(FImplies(cchOpen == 0, *szSyntax == '|'));
|
|
Assert(FImplies(*pchInsert == '\0', cchClose == 0));
|
|
|
|
if(cchOpen == 0)
|
|
// opener is empty - set caller's opener ptr null
|
|
*pszOpen = NULL;
|
|
else if(cchOpen > 0)
|
|
{
|
|
// opener is non-empty - set caller's opener to opener in registry
|
|
if(NULL == (*pszOpen = (LPSTR) CTemplate::SmallMalloc(cchOpen + 1)))
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
strncpy(*pszOpen, szSyntax, cchOpen);
|
|
(*pszOpen)[cchOpen] = '\0';
|
|
}
|
|
|
|
if(cchClose == 0)
|
|
// closer is empty - set caller's closer ptr null
|
|
*pszClose = NULL;
|
|
else if(cchClose > 0)
|
|
{
|
|
// closer is non-empty - set caller's closer to closer in registry
|
|
if(NULL == (*pszClose = (LPSTR) CTemplate::SmallMalloc(cchClose + 1)))
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
strncpy(*pszClose, (pchInsert + 1), cchClose);
|
|
(*pszClose)[cchClose] = '\0';
|
|
}
|
|
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::ThrowError
|
|
Sets up for processing a compile failure.
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
Throws error
|
|
*/
|
|
void
|
|
CTemplate::ThrowError
|
|
(
|
|
BYTE* pbErrorLocation, // ptr to error location in source file
|
|
UINT idErrMsg // error id
|
|
)
|
|
{
|
|
m_pbErrorLocation = pbErrorLocation;
|
|
m_idErrMsg = idErrMsg;
|
|
|
|
// bug 80745: always throw compile-failed-don't-cache
|
|
THROW(E_TEMPLATE_COMPILE_FAILED_DONT_CACHE);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::AppendErrorMessageInsert
|
|
Appends an error message insert to member array.
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
Appends to inserts array
|
|
*/
|
|
void
|
|
CTemplate::AppendErrorMessageInsert
|
|
(
|
|
BYTE* pbInsert, // ptr to insert
|
|
UINT cbInsert // length of insert
|
|
)
|
|
{
|
|
if (m_ppszMsgInserts == NULL)
|
|
{
|
|
m_ppszMsgInserts = new char*;
|
|
m_cMsgInserts = 0;
|
|
|
|
if (m_ppszMsgInserts == NULL)
|
|
return;
|
|
}
|
|
|
|
m_ppszMsgInserts[m_cMsgInserts] = new char[cbInsert + 1];
|
|
if (m_ppszMsgInserts[m_cMsgInserts] == NULL)
|
|
return;
|
|
|
|
strncpy(m_ppszMsgInserts[m_cMsgInserts], (const char*)pbInsert, cbInsert);
|
|
m_ppszMsgInserts[m_cMsgInserts++][cbInsert] = NULL;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::ThrowErrorSingleInsert
|
|
Appends a single message insert to member array and throws a compile error.
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
Throws error indirectly
|
|
*/
|
|
void
|
|
CTemplate::ThrowErrorSingleInsert
|
|
(
|
|
BYTE* pbErrorLocation, // ptr to error location in source file
|
|
UINT idErrMsg, // error id
|
|
BYTE* pbInsert, // ptr to insert
|
|
UINT cbInsert // length of insert
|
|
)
|
|
{
|
|
AppendErrorMessageInsert(pbInsert, cbInsert);
|
|
ThrowError(pbErrorLocation, idErrMsg);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::ProcessSpecificError
|
|
Processes a specific compile failure.
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::ProcessSpecificError
|
|
(
|
|
CFileMap& filemap, // source file map
|
|
CHitObj* pHitObj // Browser request object
|
|
)
|
|
{
|
|
// no error msg for generic failures
|
|
if(m_idErrMsg == E_FAIL || m_idErrMsg == E_OUTOFMEMORY)
|
|
return;
|
|
|
|
HandleCTemplateError(
|
|
&filemap,
|
|
m_pbErrorLocation,
|
|
m_idErrMsg,
|
|
m_cMsgInserts,
|
|
m_ppszMsgInserts,
|
|
pHitObj
|
|
);
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::ShowErrorInDebugger
|
|
Display a runtime error by invoking the JIT debugger
|
|
|
|
Returns:
|
|
failure if debugger won't start
|
|
|
|
Side effects:
|
|
None.
|
|
*/
|
|
HRESULT
|
|
CTemplate::ShowErrorInDebugger
|
|
(
|
|
CFileMap* pfilemap,
|
|
UINT cchErrorLocation,
|
|
char* szDescription,
|
|
CHitObj *pHitObj,
|
|
BOOL fAttachDocument
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
char szDebugTitle[64];
|
|
|
|
if (pfilemap == NULL || szDescription == NULL || pHitObj == NULL)
|
|
return E_POINTER;
|
|
|
|
// Create a new document context for this statement
|
|
// CONSIDER: character count that we return is bogus - however our debugging
|
|
// client (Caesar's) does not use this information anyway.
|
|
//
|
|
CTemplateDocumentContext *pDebugContext = new CTemplateDocumentContext(this, cchErrorLocation, 1);
|
|
if (pDebugContext == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
// Make sure debug document is attached to debugger
|
|
if (fAttachDocument)
|
|
AttachTo(pHitObj->PAppln());
|
|
|
|
// Yes it does, bring up the debugger on this line
|
|
hr = InvokeDebuggerWithThreadSwitch(g_pDebugApp, DEBUGGER_UI_BRING_DOC_CONTEXT_TO_TOP, pDebugContext);
|
|
if (FAILED(hr))
|
|
goto LExit;
|
|
|
|
// Load the compiler message string
|
|
CchLoadStringOfId(IDE_TEMPLATE_ERRMSG_TITLE, szDebugTitle, sizeof szDebugTitle);
|
|
|
|
// pop up a message box with the error description
|
|
MessageBoxA(NULL, szDescription, szDebugTitle, MB_SERVICE_NOTIFICATION | MB_TOPMOST | MB_OK | MB_ICONEXCLAMATION);
|
|
|
|
LExit:
|
|
if (pDebugContext)
|
|
pDebugContext->Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::HandleCTemplateError
|
|
Handles template compilation errors
|
|
|
|
Returns:
|
|
Nothing
|
|
|
|
Side effects:
|
|
None.
|
|
*/
|
|
void
|
|
CTemplate::HandleCTemplateError
|
|
(
|
|
CFileMap* pfilemap, // ptr to source file map
|
|
BYTE* pbErrorLocation, // ptr to source location where error occurred
|
|
UINT idErrMsg, // error message id
|
|
UINT cMsgInserts, // count of insert strings for error msg
|
|
char** ppszMsgInserts, // array of ptrs to error msg insert strings
|
|
CHitObj* pHitObj // Browser Request
|
|
)
|
|
{
|
|
char szErrMsgPattern[MAX_RESSTRINGSIZE]; // error message pattern
|
|
CHAR szLineNum[12];
|
|
TCHAR szFileName[512];
|
|
CHAR szShortDes[256];
|
|
CHAR szEngine[256];
|
|
CHAR szErrCode[20];
|
|
CHAR szLongDes[MAX_RESSTRINGSIZE];
|
|
CHAR szCombinedDes[sizeof szShortDes + sizeof szLongDes]; // long & short desc
|
|
DWORD dwMask;
|
|
UINT cch;
|
|
|
|
|
|
// if request ptr or ecb ptr is null, bail; we won't be able to write error msg anyway
|
|
if(pHitObj == NULL)
|
|
return;
|
|
|
|
/* if this was a security error, process it specially and bail
|
|
NOTE security error causes exception, rather than true error id
|
|
NOTE template will be destroyed anyway in this case, so no need to maintain m_pszLastErrorMessage
|
|
*/
|
|
if(idErrMsg == E_USER_LACKS_PERMISSIONS)
|
|
{
|
|
Assert(cMsgInserts == 1);
|
|
HandleAccessFailure(pHitObj,
|
|
(m_rgpFilemaps && m_rgpFilemaps[0])? m_rgpFilemaps[0]->m_szPathTranslated : NULL);
|
|
|
|
return;
|
|
}
|
|
|
|
// get error resource message
|
|
LoadErrResString(idErrMsg, &dwMask, szErrCode, szShortDes, szLongDes);
|
|
|
|
// if we have a specific error location, construct msg prefix
|
|
if(pbErrorLocation != NULL) {
|
|
Assert(pfilemap != NULL);
|
|
// get line number of error location as string
|
|
_itoa(SourceLineNumberFromPb(pfilemap, pbErrorLocation), szLineNum, 10);
|
|
}
|
|
else {
|
|
szLineNum[0] = NULL;
|
|
}
|
|
|
|
if(pfilemap != NULL) {
|
|
cch = _tcslen(pfilemap->m_szPathInfo);
|
|
_tcsncpy(szFileName, pfilemap->m_szPathInfo, cch);
|
|
}
|
|
else {
|
|
cch = 0;
|
|
}
|
|
|
|
szFileName[cch] = '\0';
|
|
|
|
//Load Default Engine from resource
|
|
cch = CchLoadStringOfId(IDS_ENGINE, szEngine, sizeof szEngine);
|
|
szEngine[cch] = '\0';
|
|
|
|
// resolve error msg pattern and inserts into actual error msg
|
|
cch = strlen(szLongDes);
|
|
memcpy(szErrMsgPattern, szLongDes, cch);
|
|
szErrMsgPattern[cch] = '\0';
|
|
|
|
// get an idea of the possibility of a buffer overrunn
|
|
UINT dwTotalLen=0;
|
|
BOOL fTooBig = FALSE;
|
|
|
|
if (cMsgInserts) {
|
|
// allow 32 characters for space, etc.
|
|
dwTotalLen = 32 + strlen(szErrMsgPattern);
|
|
for (UINT i = 0; i < cMsgInserts; i++)
|
|
dwTotalLen += strlen(ppszMsgInserts[i]);
|
|
|
|
if (dwTotalLen > sizeof szLongDes) {
|
|
cch = CchLoadStringOfId(IDE_TOOBIG, szLongDes, sizeof szLongDes);
|
|
szLongDes[cch] = '\0';
|
|
fTooBig = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!fTooBig)
|
|
GetSzFromPatternInserts(szErrMsgPattern, cMsgInserts, ppszMsgInserts, szLongDes);
|
|
|
|
// attempt to bring up debugger to display the error - if we cannot then log the error
|
|
|
|
/* Find the character offset closest to cbErrorLocation. This will be
|
|
* the place where we start looping with CharNext() to get the full
|
|
* character offset.
|
|
*
|
|
* NOTE: compilation is done in two phases.
|
|
* Errors are detected and reported in phase 1.
|
|
* The DBCS mapping is created in phase 2.
|
|
*
|
|
* Therefore, we don't have the benefit of the rgByte2DBCS table
|
|
* because it doesn't exist yet. Therefore we are left with a SLOW
|
|
* loop starting at BOF. To make things not so abysmal, we don't
|
|
* do the loop on SBCS charsets. We also don't do this conversion
|
|
* unless debugging is enabled.
|
|
*/
|
|
|
|
if (FCaesars() && pHitObj->PAppln()->FDebuggable()) {
|
|
unsigned cchErrorLocation = CharAdvDBCS(
|
|
(WORD)m_wCodePage,
|
|
reinterpret_cast<char *>(pfilemap->m_pbStartOfFile),
|
|
reinterpret_cast<char *>(pbErrorLocation),
|
|
INFINITE,
|
|
NULL);
|
|
|
|
// Create the description string
|
|
char *szEnd = strcpyExA(szCombinedDes, szShortDes);
|
|
*szEnd++ = '\n';
|
|
*szEnd++ = '\n';
|
|
strcpy(szEnd, szLongDes);
|
|
|
|
ShowErrorInDebugger(pfilemap, cchErrorLocation, szCombinedDes, pHitObj, idErrMsg != IDE_TEMPLATE_CYCLIC_INCLUDE);
|
|
}
|
|
|
|
//cache the info in case we need to use later.
|
|
m_dwLastErrorMask = dwMask;
|
|
//delay NULL check to caller who use this info.
|
|
#if UNICODE
|
|
m_pszLastErrorInfo[ILE_szFileName] = StringDupUTF8(szFileName);
|
|
#else
|
|
m_pszLastErrorInfo[ILE_szFileName] = StringDupA(szFileName);
|
|
#endif
|
|
m_pszLastErrorInfo[ILE_szLineNum] = StringDupA(szLineNum);
|
|
m_pszLastErrorInfo[ILE_szEngine] = StringDupA(szEngine);
|
|
m_pszLastErrorInfo[ILE_szErrorCode] = StringDupA(szErrCode);
|
|
m_pszLastErrorInfo[ILE_szShortDes] = StringDupA(szShortDes);
|
|
m_pszLastErrorInfo[ILE_szLongDes] = StringDupA(szLongDes);
|
|
|
|
SendToLog( m_dwLastErrorMask,
|
|
m_pszLastErrorInfo[ILE_szFileName],
|
|
m_pszLastErrorInfo[ILE_szLineNum],
|
|
m_pszLastErrorInfo[ILE_szEngine],
|
|
m_pszLastErrorInfo[ILE_szErrorCode],
|
|
m_pszLastErrorInfo[ILE_szShortDes],
|
|
m_pszLastErrorInfo[ILE_szLongDes],
|
|
pHitObj);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::FreeGoodTemplateMemory
|
|
Frees memory allocated for a 'good' (successfully compiled) template.
|
|
This includes the template itself, memory to support compile-time errors
|
|
(since the entire concatenated compile-time error message is cached in
|
|
last-err-msg member), and memory to support run-time errors (since if the
|
|
template didn't compile, it can't run).
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::FreeGoodTemplateMemory
|
|
(
|
|
)
|
|
{
|
|
UINT i;
|
|
|
|
LargeTemplateFreeNullify((void**) &m_pbStart);
|
|
SmallTemplateFreeNullify((void**) &m_rgpSegmentFilemaps);
|
|
|
|
delete[] m_rgrgSourceInfos;
|
|
m_rgrgSourceInfos = NULL;
|
|
|
|
if(m_ppszMsgInserts)
|
|
{
|
|
for(i = 0; i < m_cMsgInserts; i++)
|
|
delete m_ppszMsgInserts[i];
|
|
delete m_ppszMsgInserts;
|
|
m_ppszMsgInserts = NULL;
|
|
}
|
|
|
|
// release the collected type libs
|
|
ReleaseTypeLibs();
|
|
|
|
// release any 449-echo-cookie objects
|
|
Release449();
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::UnmapFiles
|
|
Unmaps the template's filemaps.
|
|
NOTE: we keep filemap objects around so that filenames will be available for runtime errors
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
Unmaps template's filemaps
|
|
*/
|
|
void
|
|
CTemplate::UnmapFiles
|
|
(
|
|
)
|
|
{
|
|
UINT i;
|
|
for(i = 0; i < m_cFilemaps; i++)
|
|
m_rgpFilemaps[i]->UnmapFile();
|
|
}
|
|
|
|
/*===================================================================
|
|
CTemplate::ExtractAndProcessSegment
|
|
Extracts and processes leading source segment and first contained
|
|
source segment from search range.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::ExtractAndProcessSegment
|
|
(
|
|
CByteRange& brSearch, // byte range to search for next segment-opening token
|
|
const SOURCE_SEGMENT& ssegLeading, // type of 'leading', i.e. pre-token, source segment
|
|
_TOKEN* rgtknOpeners, // array of permitted open tokens
|
|
UINT ctknOpeners, // count of permitted open tokens
|
|
CFileMap* pfilemapCurrent, // ptr to filemap of parent file
|
|
CWorkStore& WorkStore, // working storage for source segments
|
|
CHitObj* pHitObj, // Browser request object
|
|
BOOL fScriptTagProcessed,// has script tag been processed?
|
|
BOOL fIsHTML // are we in HTML segment?
|
|
)
|
|
{
|
|
CByteRange brLeadSegment; // byte range of leading source segment
|
|
SOURCE_SEGMENT ssegContained; // type of 'contained', i.e. post-token, source segment
|
|
CByteRange brContainedSegment; // byte range of contained source segment
|
|
_TOKEN tknOpen; // opening token
|
|
BYTE* pbTokenOpen; // ptr to opening token
|
|
_TOKEN tknClose; // closing token
|
|
BYTE* pbTokenClose; // ptr to closing token
|
|
|
|
// NOTE: If "fScriptTagProcessed" is TRUE, then "fIsHTML" must be FALSE. The reason for
|
|
// both flags is that if "fScriptTagProcessed" is FALSE, then "fIsHTML" may be either TRUE
|
|
// or FALSE (indeterminate)
|
|
//
|
|
Assert (FImplies(fScriptTagProcessed, !fIsHTML));
|
|
|
|
// If search range is empty, return
|
|
if(brSearch.IsNull())
|
|
return;
|
|
|
|
// Set ptr of leading segment to start of search segment
|
|
brLeadSegment.m_pb = brSearch.m_pb;
|
|
|
|
// get open token for contained segment
|
|
pbTokenOpen = GetOpenToken(
|
|
brSearch,
|
|
ssegLeading,
|
|
rgtknOpeners,
|
|
ctknOpeners,
|
|
&tknOpen
|
|
);
|
|
|
|
// Set count of leading segment to distance between start of search range and token
|
|
brLeadSegment.m_cb = DIFF(pbTokenOpen - brSearch.m_pb);
|
|
|
|
// Process leading segment
|
|
ProcessSegment(ssegLeading, brLeadSegment, pfilemapCurrent, WorkStore, fScriptTagProcessed, pHitObj, fIsHTML);
|
|
|
|
// If open token was 'EOF', empty out search range and return
|
|
if(tknOpen == CTokenList::tknEOF)
|
|
{
|
|
brSearch.Nullify();
|
|
return;
|
|
}
|
|
|
|
// Set contained segment type and close token based upon the opener we found
|
|
tknClose = GetComplementToken(tknOpen);
|
|
ssegContained = GetSegmentOfOpenToken(tknOpen);
|
|
|
|
if(ssegContained == ssegHTMLComment)
|
|
// for html comment segments, advance search range to open token
|
|
// NOTE keep html comment tags in segment because they must be sent to client
|
|
brSearch.Advance(DIFF(pbTokenOpen - brSearch.m_pb));
|
|
else
|
|
// for all but html comment segments, advance search range to just past open token
|
|
gm_pTokenList->MovePastToken(tknOpen, pbTokenOpen, brSearch);
|
|
|
|
// Get closing token - if none, throw error
|
|
if(NULL == (pbTokenClose = GetCloseToken(brSearch, tknClose)))
|
|
{
|
|
if(tknOpen == CTokenList::tknOpenPrimaryScript)
|
|
ThrowError(pbTokenOpen, IDE_TEMPLATE_NO_CLOSE_PSCRIPT);
|
|
else if(tknOpen == CTokenList::tknOpenTaggedScript)
|
|
ThrowError(pbTokenOpen, IDE_TEMPLATE_NO_CLOSE_TSCRIPT);
|
|
else if(tknOpen == CTokenList::tknOpenObject)
|
|
ThrowError(pbTokenOpen, IDE_TEMPLATE_NO_CLOSE_OBJECT);
|
|
else if(tknOpen == CTokenList::tknOpenHTMLComment)
|
|
ThrowError(pbTokenOpen, IDE_TEMPLATE_NO_CLOSE_HTML_COMMENT);
|
|
}
|
|
|
|
// calc contained segment
|
|
brContainedSegment.m_pb = brSearch.m_pb;
|
|
brContainedSegment.m_cb = DIFF(pbTokenClose - brSearch.m_pb);
|
|
|
|
// advance search range to just past close token
|
|
gm_pTokenList->MovePastToken(tknClose, pbTokenClose, brSearch);
|
|
|
|
// if an html comment segment, get actual segment type (e.g. might be a server-side include command)
|
|
// NOTE call may also change contained segment byte range
|
|
if(ssegContained == ssegHTMLComment)
|
|
ssegContained = SsegFromHTMLComment(brContainedSegment);
|
|
|
|
// if an html comment segment, add its close tag to contained segment
|
|
// NOTE we keep html comment tags as part of segment so we can process like any other html segment
|
|
if(ssegContained == ssegHTMLComment)
|
|
brContainedSegment.m_cb += CCH_TOKEN(tknClose);
|
|
|
|
if(ssegContained == ssegMetadata)
|
|
{
|
|
// METADATA comments are used by DESIGN time controls and we don't send
|
|
// them to the client.
|
|
|
|
// We process metadata to get to the typelib info
|
|
UINT idError = 0;
|
|
HRESULT hr = ProcessMetadataSegment(brContainedSegment, &idError, pHitObj);
|
|
|
|
if (FAILED(hr))
|
|
ThrowError(brContainedSegment.m_pb, idError);
|
|
}
|
|
else if (ssegContained == ssegFPBot)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
// process contained segment
|
|
ProcessSegment(ssegContained, brContainedSegment, pfilemapCurrent, WorkStore, fScriptTagProcessed, pHitObj, fIsHTML);
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::SsegFromHTMLComment
|
|
Determines source segment type of HTML comment.
|
|
|
|
Returns
|
|
Source segment type
|
|
Side effects
|
|
May advance segment byte range
|
|
*/
|
|
CTemplate::SOURCE_SEGMENT
|
|
CTemplate::SsegFromHTMLComment
|
|
(
|
|
CByteRange& brSegment // source segment
|
|
)
|
|
{
|
|
SOURCE_SEGMENT ssegRet = ssegHTMLComment; // return value
|
|
BYTE* pbToken; // ptr to token
|
|
|
|
if(NULL != (pbToken = gm_pTokenList->GetToken(CTokenList::tknCommandINCLUDE, brSegment, m_wCodePage)))
|
|
{
|
|
gm_pTokenList->MovePastToken(CTokenList::tknCommandINCLUDE, pbToken, brSegment);
|
|
ssegRet = ssegInclude;
|
|
}
|
|
else if(NULL != (pbToken = gm_pTokenList->GetToken(CTokenList::tknTagMETADATA, brSegment, m_wCodePage)))
|
|
{
|
|
gm_pTokenList->MovePastToken(CTokenList::tknTagMETADATA, pbToken, brSegment);
|
|
ssegRet = ssegMetadata;
|
|
}
|
|
else if(NULL != (pbToken = gm_pTokenList->GetToken(CTokenList::tknTagFPBot, brSegment, m_wCodePage)))
|
|
{
|
|
gm_pTokenList->MovePastToken(CTokenList::tknTagFPBot, pbToken, brSegment);
|
|
ssegRet = ssegFPBot;
|
|
}
|
|
|
|
return ssegRet;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::ProcessSegment
|
|
Processes a source segment based on its type.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::ProcessSegment
|
|
(
|
|
SOURCE_SEGMENT sseg, // segment type
|
|
CByteRange& brSegment, // segment byte range
|
|
CFileMap* pfilemapCurrent, // ptr to filemap of parent file
|
|
CWorkStore& WorkStore, // working storage for source segments
|
|
BOOL fScriptTagProcessed, // has script tag been processed?
|
|
CHitObj* pHitObj, // Browser request object
|
|
BOOL fIsHTML // Is segment in HTML block or script?
|
|
)
|
|
{
|
|
UINT idSequence; // sequence id for this segment
|
|
|
|
// if segment is entirely white space, silently return
|
|
if(FByteRangeIsWhiteSpace(brSegment))
|
|
return;
|
|
|
|
// set local sequence id and increment member
|
|
idSequence = WorkStore.m_idCurSequence++;
|
|
|
|
// Process segment based on its type
|
|
if(sseg == ssegHTML)
|
|
ProcessHTMLSegment(brSegment, WorkStore.m_bufHTMLSegments, idSequence, pfilemapCurrent);
|
|
else if(sseg == ssegHTMLComment)
|
|
ProcessHTMLCommentSegment(brSegment, pfilemapCurrent, WorkStore, pHitObj);
|
|
else if(sseg == ssegPrimaryScript || sseg == ssegTaggedScript)
|
|
ProcessScriptSegment(sseg, brSegment, pfilemapCurrent, WorkStore, idSequence, (BOOLB)!!fScriptTagProcessed, pHitObj);
|
|
else if(sseg == ssegObject)
|
|
ProcessObjectSegment(brSegment, pfilemapCurrent, WorkStore, idSequence);
|
|
else if(sseg == ssegInclude)
|
|
{
|
|
if (! fIsHTML)
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_SSI_COMMAND);
|
|
|
|
ProcessIncludeFile(brSegment, pfilemapCurrent, WorkStore, idSequence, pHitObj, fIsHTML);
|
|
}
|
|
|
|
// malloc/realloc array if needed
|
|
if(m_cSegmentFilemapSlots == 0)
|
|
{
|
|
m_cSegmentFilemapSlots = C_SCRIPTSEGMENTSDEFAULT + C_HTMLSEGMENTSDEFAULT;
|
|
if(NULL == (m_rgpSegmentFilemaps = (CFileMap**) CTemplate::SmallMalloc(m_cSegmentFilemapSlots * sizeof(CFileMap*))))
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
else if(idSequence >= m_cSegmentFilemapSlots)
|
|
{
|
|
// grab twice what we had before
|
|
m_cSegmentFilemapSlots *= 2;
|
|
if(NULL == (m_rgpSegmentFilemaps = (CFileMap**) CTemplate::SmallReAlloc(m_rgpSegmentFilemaps,
|
|
m_cSegmentFilemapSlots * sizeof(CFileMap*))))
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
|
|
// set filemap ptr for this segment - NOTE 'parent' filemap is also current file map
|
|
m_rgpSegmentFilemaps[idSequence] = pfilemapCurrent;
|
|
}
|
|
|
|
/* ========================================================
|
|
CTemplate::ProcessHTMLSegment
|
|
|
|
Processes an HTML segment.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::ProcessHTMLSegment
|
|
(
|
|
CByteRange& brHTML, // html segment
|
|
CBuffer& bufHTMLBlocks, // working storage for html blocks
|
|
UINT idSequence, // segment sequence id
|
|
CFileMap* pfilemapCurrent // current filemap
|
|
)
|
|
{
|
|
if(!(brHTML.IsNull()))
|
|
// If byte range is non-empty, store it in html buffer (non-local)
|
|
bufHTMLBlocks.Append(brHTML, FALSE, idSequence, pfilemapCurrent);
|
|
}
|
|
|
|
/* ========================================================
|
|
CTemplate::ProcessHTMLCommentSegment
|
|
Processes an HTML comment segment: within an HTML comment we
|
|
honor plain text (passed through as HTML comment) and primary script.
|
|
See bug 182 for istudio scenarios that require this behavior.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::ProcessHTMLCommentSegment
|
|
(
|
|
CByteRange& brSegment, // segment byte range
|
|
CFileMap* pfilemapCurrent, // ptr to filemap of parent file
|
|
CWorkStore& WorkStore, // working storage for source segments
|
|
CHitObj* pHitObj // Browser request object
|
|
)
|
|
{
|
|
_TOKEN* rgtknOpeners; // array of permitted open tokens
|
|
UINT ctknOpeners; // count of permitted open tokens
|
|
|
|
// populate array of permitted open tokens
|
|
ctknOpeners = 1;
|
|
_TOKEN tknOpeners[1];
|
|
rgtknOpeners = tknOpeners;
|
|
rgtknOpeners[0] = CTokenList::tknOpenPrimaryScript;
|
|
|
|
// Process source segments embedded within HTML comment segment
|
|
while(!brSegment.IsNull())
|
|
ExtractAndProcessSegment(
|
|
brSegment, // byte range to search for next segment-opening token
|
|
ssegHTML, // type of 'leading', i.e. pre-token, source segment
|
|
rgtknOpeners, // array of permitted open tokens
|
|
ctknOpeners, // count of permitted open tokens
|
|
pfilemapCurrent,// ptr to filemap of parent file
|
|
WorkStore, // working storage for source segments
|
|
pHitObj // Browser request object
|
|
);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::ProcessScriptSegment
|
|
Processes a script segment.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::ProcessScriptSegment
|
|
(
|
|
SOURCE_SEGMENT sseg, // segment type
|
|
CByteRange& brSegment, // segment byte range
|
|
CFileMap* pfilemapCurrent, // ptr to filemap of parent file
|
|
CWorkStore& WorkStore, // working storage for scripts
|
|
UINT idSequence, // segment sequence id
|
|
BOOLB fScriptTagProcessed,// has script tag been processed?
|
|
CHitObj* pHitObj // Browser request object
|
|
)
|
|
{
|
|
CByteRange brEngine; // script engine name - NOTE constructed null
|
|
|
|
if(m_fGlobalAsa)
|
|
if(sseg == ssegPrimaryScript)
|
|
// error out on primary script if we are processing global.asa
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_GLOBAL_PSCRIPT);
|
|
|
|
if(sseg == ssegPrimaryScript)
|
|
{
|
|
CByteRange brTemp = brSegment;
|
|
|
|
LTrimWhiteSpace(brTemp);
|
|
|
|
if(*brTemp.m_pb == '@') // CONSIDER: tknTagSetPriScriptLang
|
|
{
|
|
// impossible condition: page-level @ commands can't be allowed if they have already been executed
|
|
Assert(!(WorkStore.m_fPageCommandsAllowed && WorkStore.m_fPageCommandsExecuted));
|
|
|
|
if(!WorkStore.m_fPageCommandsAllowed)
|
|
{
|
|
if(WorkStore.m_fPageCommandsExecuted)
|
|
// error out if trying to re-execute page-level @ commands
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_PAGE_COMMAND_REPEATED);
|
|
else
|
|
// error out if trying to execute page-level @ commands when not allowed
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_PAGE_COMMAND_NOT_FIRST);
|
|
}
|
|
|
|
// if we made it here, must be allowed to execute page-level @ commands AND they have not been executed
|
|
Assert((WorkStore.m_fPageCommandsAllowed && !WorkStore.m_fPageCommandsExecuted));
|
|
|
|
/* set primary script language if required
|
|
NOTE we call GetTagName to see if LANGUAGE tag occurs in tags segment; this is somewhat wasteful,
|
|
since BrValueOfTag must simply call GetTagName again. However, this scheme is easier than changing
|
|
BrValueOfTag to return a BOOL and amending all its other callers, who don't need this info.
|
|
*/
|
|
|
|
// Flags and counters used to track and validate the @ command directive
|
|
//
|
|
int nFirstPass = 1;
|
|
int nOffset = 0;
|
|
BOOLB fTagLanguage = TRUE;
|
|
BOOLB fTagCodePage = TRUE;
|
|
BOOLB fTagLCID = TRUE;
|
|
BOOLB fTagTransacted = TRUE;
|
|
BOOLB fTagSession = TRUE;
|
|
|
|
while( GetTag( brSegment, nFirstPass) )
|
|
{
|
|
nFirstPass =2;
|
|
nOffset = 0;
|
|
|
|
if ( fTagLanguage && CompTagName( brSegment, CTokenList::tknTagLanguage ) )
|
|
{
|
|
fTagLanguage = FALSE;
|
|
brEngine = BrValueOfTag(brSegment, CTokenList::tknTagLanguage);
|
|
if ( brEngine.IsNull() )
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_NO_ENGINE_NAME);
|
|
|
|
// get prog lang id
|
|
PROGLANG_ID ProgLangId;
|
|
HRESULT hr = GetProgLangId(brEngine, &ProgLangId);
|
|
|
|
if(hr == TYPE_E_ELEMENTNOTFOUND)
|
|
// if prog lang not found, throw error
|
|
ThrowErrorSingleInsert(
|
|
brEngine.m_pb,
|
|
IDE_TEMPLATE_BAD_PROGLANG,
|
|
brEngine.m_pb,
|
|
brEngine.m_cb
|
|
);
|
|
else if(FAILED(hr))
|
|
// other failure: re-throw exception code
|
|
THROW(hr);
|
|
|
|
Assert(WorkStore.m_ScriptStore.CountPreliminaryEngines() >= 1);
|
|
|
|
// Set 0-th (primary) script engine to user-specified value
|
|
WorkStore.m_ScriptStore.m_bufEngineNames.SetItem(
|
|
0, // index of item to set
|
|
brEngine, // engine name
|
|
FALSE, // item is non-local
|
|
0, // sequence id (don't care)
|
|
NULL // filemap ptr (don't care)
|
|
);
|
|
|
|
// Set 0-th (primary) prog lang id to engine's
|
|
WorkStore.m_ScriptStore.m_rgProgLangId[0] = ProgLangId;
|
|
brSegment.Advance(DIFF(brEngine.m_pb - brSegment.m_pb));
|
|
|
|
}
|
|
|
|
/* set code page if required
|
|
see NOTE above for why we call we call GetTagName.
|
|
*/
|
|
else if ( fTagCodePage && CompTagName( brSegment, CTokenList::tknTagCodePage ) )
|
|
{
|
|
fTagCodePage = FALSE;
|
|
CByteRange brCodePage = BrValueOfTag( brSegment, CTokenList::tknTagCodePage );
|
|
if ( brCodePage.IsNull() )
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_NO_CODEPAGE);
|
|
|
|
if ( brCodePage.m_cb > 10 )
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_CODEPAGE);
|
|
|
|
char szCodePage[31];
|
|
strncpy( szCodePage, (char*) brCodePage.m_pb, brCodePage.m_cb );
|
|
szCodePage[ brCodePage.m_cb ] = '\0';
|
|
|
|
char *pchEnd;
|
|
UINT uCodePage = UINT( strtoul( szCodePage, &pchEnd, 10 ) );
|
|
|
|
// verify that pchEnd is the NULL
|
|
if (*pchEnd != 0)
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_CODEPAGE);
|
|
|
|
if ( FAILED( pHitObj->SetCodePage( uCodePage ) ) )
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_CODEPAGE);
|
|
else
|
|
{
|
|
m_wCodePage = uCodePage;
|
|
m_fCodePageSet = TRUE;
|
|
}
|
|
|
|
brSegment.Advance(DIFF(brCodePage.m_pb - brSegment.m_pb));
|
|
}
|
|
/* set LCID if required
|
|
see NOTE above for why we call we call GetTagName.
|
|
*/
|
|
else if ( fTagLCID && CompTagName( brSegment, CTokenList::tknTagLCID ) )
|
|
{
|
|
fTagLCID = FALSE;
|
|
CByteRange brLCID = BrValueOfTag( brSegment, CTokenList::tknTagLCID );
|
|
if ( brLCID.IsNull() )
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_NO_LCID);
|
|
|
|
char szLCID[31];
|
|
strncpy( szLCID, (char*) brLCID.m_pb, brLCID.m_cb );
|
|
szLCID[ brLCID.m_cb ] = '\0';
|
|
|
|
char *pchEnd;
|
|
UINT uLCID = UINT( strtoul( szLCID, &pchEnd, 10 ) );
|
|
|
|
// verify that pchEnd is the NULL
|
|
if (*pchEnd != 0)
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_LCID);
|
|
|
|
if ( FAILED( pHitObj->SetLCID( uLCID ) ) )
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_LCID);
|
|
else
|
|
{
|
|
m_lLCID = uLCID;
|
|
m_fLCIDSet = TRUE;
|
|
}
|
|
|
|
brSegment.Advance(DIFF(brLCID.m_pb - brSegment.m_pb));
|
|
}
|
|
/* Set transacted if requiured
|
|
see NOTE above for why we call GetTagName
|
|
*/
|
|
else if ( fTagTransacted && CompTagName( brSegment, CTokenList::tknTagTransacted ) )
|
|
{
|
|
|
|
STACK_BUFFER( tempTransValue, 32 );
|
|
|
|
fTagTransacted = FALSE;
|
|
CByteRange brTransacted = BrValueOfTag( brSegment, CTokenList::tknTagTransacted );
|
|
if ( brTransacted.IsNull() )
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_TRANSACTED_VALUE);
|
|
|
|
if (!tempTransValue.Resize(brTransacted.m_cb + 1)) {
|
|
ThrowError(brSegment.m_pb, IDE_OOM);
|
|
}
|
|
|
|
LPSTR szTransacted = static_cast<LPSTR> (tempTransValue.QueryPtr());
|
|
strncpy(szTransacted, (LPCSTR)brTransacted.m_pb, brTransacted.m_cb);
|
|
szTransacted[brTransacted.m_cb]='\0';
|
|
if (!strcmpi(szTransacted, "REQUIRED"))
|
|
m_ttTransacted = ttRequired;
|
|
else if (!strcmpi(szTransacted, "REQUIRES_NEW"))
|
|
m_ttTransacted = ttRequiresNew;
|
|
else if (!strcmpi(szTransacted, "SUPPORTED"))
|
|
m_ttTransacted = ttSupported;
|
|
else if (!strcmpi(szTransacted, "NOT_SUPPORTED"))
|
|
m_ttTransacted = ttNotSupported;
|
|
else
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_TRANSACTED_VALUE);
|
|
|
|
brSegment.Advance(DIFF(brTransacted.m_pb - brSegment.m_pb));
|
|
}
|
|
/* Set session flag
|
|
see NOTE above for why we call GetTagName
|
|
*/
|
|
else if ( fTagSession && CompTagName( brSegment, CTokenList::tknTagSession ) )
|
|
{
|
|
|
|
STACK_BUFFER( tempSession, 16 );
|
|
|
|
fTagSession = FALSE;
|
|
CByteRange brSession = BrValueOfTag( brSegment, CTokenList::tknTagSession );
|
|
if ( brSession.IsNull() )
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_SESSION_VALUE);
|
|
|
|
if (!tempSession.Resize(brSession.m_cb + 1))
|
|
ThrowError(brSegment.m_pb, IDE_OOM);
|
|
|
|
LPSTR szSession = static_cast<LPSTR> (tempSession.QueryPtr());
|
|
strncpy(szSession, (LPCSTR)brSession.m_pb, brSession.m_cb);
|
|
szSession[brSession.m_cb]='\0';
|
|
if (strcmpi(szSession, "TRUE") == 0)
|
|
{
|
|
m_fSession = TRUE;
|
|
if (!pHitObj->PAppln()->QueryAppConfig()->fAllowSessionState())
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_CANT_ENABLE_SESSIONS);
|
|
}
|
|
else if (strcmpi(szSession, "FALSE") == 0)
|
|
m_fSession = FALSE;
|
|
else
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_BAD_SESSION_VALUE);
|
|
|
|
brSegment.Advance(DIFF(brSession.m_pb - brSegment.m_pb));
|
|
}
|
|
else
|
|
ThrowErrorSingleInsert( brSegment.m_pb,
|
|
IDE_TEMPLATE_BAD_AT_COMMAND,
|
|
brSegment.m_pb,
|
|
brSegment.m_cb
|
|
);
|
|
}
|
|
|
|
if (nFirstPass == 1)
|
|
ThrowErrorSingleInsert( brSegment.m_pb,
|
|
IDE_TEMPLATE_BAD_AT_COMMAND,
|
|
brSegment.m_pb,
|
|
brSegment.m_cb
|
|
);
|
|
|
|
|
|
|
|
// set flag true and ignore remainder of segment, since we only use this segment for page-level @ commands
|
|
WorkStore.m_fPageCommandsExecuted = TRUE;
|
|
goto LExit;
|
|
}
|
|
|
|
}
|
|
|
|
if(sseg == ssegTaggedScript)
|
|
{
|
|
if(!fScriptTagProcessed)
|
|
{
|
|
/* semantics of script-tag-processed flag:
|
|
- if false, we have a 'fresh' tagged script block, so we need to get its engine name
|
|
(which also advances past the script tag header) and then process the tagged segment
|
|
via indirect recursive call
|
|
- if true, we have aleady been called recursively, so we bypass further recursion
|
|
and simply append to store
|
|
*/
|
|
CByteRange brIncludeFile;
|
|
GetScriptEngineOfSegment(brSegment, WorkStore.m_brCurEngine, brIncludeFile);
|
|
if (! brIncludeFile.IsNull())
|
|
{
|
|
|
|
STACK_BUFFER( tempInclude, 256 );
|
|
|
|
if (!tempInclude.Resize(brIncludeFile.m_cb + 1)) {
|
|
ThrowError(brSegment.m_pb, IDE_OOM);
|
|
}
|
|
|
|
// Create Null-terminated string from brIncludeFile
|
|
char *szFileSpec = reinterpret_cast<char *>(tempInclude.QueryPtr());
|
|
memcpy(szFileSpec, brIncludeFile.m_pb, brIncludeFile.m_cb);
|
|
szFileSpec[brIncludeFile.m_cb] = 0;
|
|
if (szFileSpec[0] == '\\') // metabase stuff chokes on initial '\' char
|
|
szFileSpec[0] = '/';
|
|
|
|
// read the include file (szFileSpec & brIncludeFile in this case point to same string contents.
|
|
// however, "brIncludeFile" is used as an error location.
|
|
//
|
|
TRY
|
|
ProcessIncludeFile2(szFileSpec, brIncludeFile, pfilemapCurrent, WorkStore, idSequence, pHitObj, FALSE);
|
|
CATCH(hrException)
|
|
|
|
// The TRY/CATCH below may re-throw a IDE_TEMPLATE_BAD_PROGLANG when the
|
|
// segment being processed is tagged script with a SRC file. The reason being
|
|
// that to properly report the error, the ThrowErrorSingleInsert must be called
|
|
// from the template which contained the script tag with the bad prog lang. If
|
|
// called from the template created containing the included script, then the
|
|
// brEngine assigned below is not pointing into the included script's filemap
|
|
// which results in AVs because we can't do the pointer math to determine the
|
|
// line number.
|
|
|
|
if(hrException == IDE_TEMPLATE_BAD_PROGLANG)
|
|
// exception code is really an error message id: set err id to it
|
|
ThrowErrorSingleInsert(
|
|
WorkStore.m_brCurEngine.m_pb,
|
|
IDE_TEMPLATE_BAD_PROGLANG,
|
|
WorkStore.m_brCurEngine.m_pb,
|
|
WorkStore.m_brCurEngine.m_cb
|
|
);
|
|
else
|
|
|
|
// other exception: re-throw
|
|
THROW(hrException);
|
|
|
|
END_TRY
|
|
|
|
|
|
// done - don't process script text
|
|
return;
|
|
}
|
|
else
|
|
ProcessTaggedScriptSegment(brSegment, pfilemapCurrent, WorkStore, pHitObj);
|
|
}
|
|
|
|
brEngine = WorkStore.m_brCurEngine;
|
|
}
|
|
|
|
TRY
|
|
// append script segment to store
|
|
WorkStore.m_ScriptStore.AppendScript(brSegment, brEngine, (sseg == ssegPrimaryScript), idSequence, pfilemapCurrent);
|
|
|
|
CATCH(hrException)
|
|
// NOTE exception code from AppendScript() is overloaded: it can be an error message id or a true exception
|
|
|
|
// if the brEngine does not point to memory within the current filemap, then
|
|
// we must have come into here because of a tagged script statement with a SRC=
|
|
// attrib. In which case, we won't call ThrowError from here but will re-throw
|
|
// the error to be caught above.
|
|
|
|
if((hrException == IDE_TEMPLATE_BAD_PROGLANG)
|
|
&& (brEngine.m_pb >= pfilemapCurrent->m_pbStartOfFile)
|
|
&& (brEngine.m_pb < (pfilemapCurrent->m_pbStartOfFile + pfilemapCurrent->GetSize()))) {
|
|
// exception code is really an error message id: set err id to it
|
|
ThrowErrorSingleInsert(
|
|
brEngine.m_pb,
|
|
IDE_TEMPLATE_BAD_PROGLANG,
|
|
brEngine.m_pb,
|
|
brEngine.m_cb
|
|
);
|
|
}
|
|
else
|
|
// other exception: re-throw
|
|
THROW(hrException);
|
|
|
|
END_TRY
|
|
|
|
LExit:
|
|
// set flag to say we can no longer set primary language (must be in first script segment, if at all)
|
|
WorkStore.m_fPageCommandsAllowed = FALSE;
|
|
}
|
|
|
|
|
|
/* ========================================================
|
|
CTemplate::ProcessMetadataSegment
|
|
Parses the metadata comment for typelib information.
|
|
|
|
Returns
|
|
HRESULT
|
|
*/
|
|
HRESULT
|
|
CTemplate::ProcessMetadataSegment
|
|
(
|
|
const CByteRange& brSegment,
|
|
UINT *pidError,
|
|
CHitObj *pHitObj
|
|
)
|
|
{
|
|
// TYPELIB
|
|
if (FTagHasValue(brSegment,
|
|
CTokenList::tknTagType,
|
|
CTokenList::tknValueTypeLib))
|
|
{
|
|
return ProcessMetadataTypelibSegment(brSegment, pidError, pHitObj);
|
|
}
|
|
// METADATA INVALID in Global.asa
|
|
else if (m_fGlobalAsa)
|
|
{
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_METADATA_IN_GLOBAL_ASA);
|
|
return E_TEMPLATE_COMPILE_FAILED_DONT_CACHE; // to keep compiler happy; in reality doesn't return.
|
|
}
|
|
// COOKIE
|
|
else if (FTagHasValue(brSegment,
|
|
CTokenList::tknTagType,
|
|
CTokenList::tknValueCookie))
|
|
{
|
|
return ProcessMetadataCookieSegment(brSegment, pidError, pHitObj);
|
|
}
|
|
// Ignore everything else
|
|
else
|
|
{
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
|
|
/* ========================================================
|
|
CTemplate::ProcessMetadataTypelibSegment
|
|
Parses the metadata comment for typelib information.
|
|
|
|
Returns
|
|
HRESULT
|
|
*/
|
|
HRESULT
|
|
CTemplate::ProcessMetadataTypelibSegment
|
|
(
|
|
const CByteRange& brSegment,
|
|
UINT *pidError,
|
|
CHitObj *pHitObj
|
|
)
|
|
{
|
|
// Ignore ENDSPAN segments
|
|
if (GetTagName(brSegment, CTokenList::tknTagEndspan))
|
|
{
|
|
// ENDSPAN found - ignore
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT hr;
|
|
char szFile[MAX_PATH+1];
|
|
DWORD cbFile;
|
|
|
|
// Try to get the filename
|
|
CByteRange br = BrValueOfTag(brSegment, CTokenList::tknTagFile);
|
|
if (!br.IsNull())
|
|
{
|
|
// filename present
|
|
if (br.m_cb > MAX_PATH)
|
|
{
|
|
// file too long
|
|
*pidError = IDE_TEMPLATE_BAD_TYPELIB_SPEC;
|
|
return E_FAIL;
|
|
}
|
|
memcpy(szFile, br.m_pb, br.m_cb);
|
|
cbFile = br.m_cb;
|
|
szFile[cbFile] = '\0';
|
|
}
|
|
else
|
|
{
|
|
// No filename - use GUID, version, LCID to get file
|
|
|
|
char szUUID[44]; // {} + hex chars + dashes
|
|
char szVers[16]; // "1.0", etc
|
|
char szLCID[16]; // locale id - a number
|
|
|
|
br = BrValueOfTag(brSegment, CTokenList::tknTagUUID);
|
|
if (br.IsNull() || br.m_cb > sizeof(szUUID)-3)
|
|
{
|
|
// no filename and no uuid -> invalid typelib spec
|
|
*pidError = IDE_TEMPLATE_BAD_TYPELIB_SPEC;
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (br.m_pb[0] == '{')
|
|
{
|
|
// already in braces
|
|
memcpy(szUUID, br.m_pb, br.m_cb);
|
|
szUUID[br.m_cb] = '\0';
|
|
}
|
|
else
|
|
{
|
|
// enclose in {}
|
|
szUUID[0] = '{';
|
|
memcpy(szUUID+1, br.m_pb, br.m_cb);
|
|
szUUID[br.m_cb+1] = '}';
|
|
szUUID[br.m_cb+2] = '\0';
|
|
}
|
|
|
|
// Optional Version
|
|
szVers[0] = '\0';
|
|
br = BrValueOfTag(brSegment, CTokenList::tknTagVersion);
|
|
if (!br.IsNull() && br.m_cb < sizeof(szVers)-1)
|
|
{
|
|
memcpy(szVers, br.m_pb, br.m_cb);
|
|
szVers[br.m_cb] = '\0';
|
|
}
|
|
|
|
// Optional LCID
|
|
LCID lcid;
|
|
br = BrValueOfTag(brSegment, CTokenList::tknTagLCID);
|
|
if (!br.IsNull() && br.m_cb < sizeof(szLCID)-1)
|
|
{
|
|
memcpy(szLCID, br.m_pb, br.m_cb);
|
|
szLCID[br.m_cb] = '\0';
|
|
lcid = strtoul(szLCID, NULL, 16);
|
|
}
|
|
else
|
|
{
|
|
// if the LCID is not defined -> use system's
|
|
lcid = GetSystemDefaultLCID();
|
|
}
|
|
|
|
// Get TYPELIB filename from registry
|
|
hr = GetTypelibFilenameFromRegistry
|
|
(
|
|
szUUID,
|
|
szVers,
|
|
lcid,
|
|
szFile,
|
|
MAX_PATH
|
|
);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
*pidError = IDE_TEMPLATE_BAD_TYPELIB_REG_SPEC;
|
|
return hr;
|
|
}
|
|
|
|
cbFile = strlen(szFile);
|
|
}
|
|
|
|
// Convert filename to double-byte to call LoadTypeLib()
|
|
|
|
STACK_BUFFER( tempFile, MAX_PATH * sizeof(WCHAR) );
|
|
|
|
if (!tempFile.Resize((cbFile+1) * sizeof(WCHAR))) {
|
|
*pidError = IDE_OOM;
|
|
return E_FAIL;
|
|
}
|
|
|
|
LPWSTR wszFile = (LPWSTR)tempFile.QueryPtr();
|
|
|
|
if (MultiByteToWideChar(pHitObj->GetCodePage(), MB_ERR_INVALID_CHARS,
|
|
szFile, cbFile, wszFile, cbFile) == 0)
|
|
{
|
|
*pidError = IDE_OOM;
|
|
return E_FAIL;
|
|
}
|
|
wszFile[cbFile] = L'\0';
|
|
|
|
// LoadTypeLib() to get ITypeLib*
|
|
ITypeLib *ptlb = NULL;
|
|
hr = LoadTypeLib(wszFile, &ptlb);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
*pidError = IDE_TEMPLATE_LOAD_TYPELIB_FAILED;
|
|
return hr;
|
|
}
|
|
|
|
// Remember ITypeLib* in the array
|
|
Assert(ptlb);
|
|
hr = m_rgpTypeLibs.append(ptlb);
|
|
if (FAILED(hr))
|
|
{
|
|
*pidError = IDE_TEMPLATE_LOAD_TYPELIB_FAILED;
|
|
return hr;
|
|
}
|
|
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/* ========================================================
|
|
CTemplate::ProcessMetadataCookieSegment
|
|
Parses the metadata comment for cookie information.
|
|
|
|
Returns
|
|
HRESULT
|
|
*/
|
|
HRESULT
|
|
CTemplate::ProcessMetadataCookieSegment
|
|
(
|
|
const CByteRange& brSegment,
|
|
UINT *pidError,
|
|
CHitObj *pHitObj
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
CByteRange br;
|
|
char *pszName;
|
|
char szFile[MAX_PATH+1];
|
|
TCHAR sztFile[MAX_PATH+1];
|
|
CMBCSToWChar convStr;
|
|
|
|
STACK_BUFFER( tempCookie, 64 );
|
|
STACK_BUFFER( tempFile, 64 );
|
|
|
|
// Try to get the cookie name
|
|
br = BrValueOfTag(brSegment, CTokenList::tknTagName);
|
|
if (br.IsNull() || (br.m_cb == 0)) {
|
|
*pidError = IDE_TEMPLATE_BAD_COOKIE_SPEC_NAME;
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (!tempCookie.Resize(br.m_cb + 1)) {
|
|
*pidError = IDE_OOM;
|
|
return E_FAIL;
|
|
}
|
|
|
|
pszName = (char *)tempCookie.QueryPtr();
|
|
if (!pszName)
|
|
{
|
|
*pidError = IDE_OOM;
|
|
return E_FAIL;
|
|
}
|
|
memcpy(pszName, br.m_pb, br.m_cb);
|
|
pszName[br.m_cb] = '\0';
|
|
|
|
|
|
// Try to get the path to the script
|
|
br = BrValueOfTag(brSegment, CTokenList::tknTagSrc);
|
|
if (br.IsNull() || (br.m_cb >= MAX_PATH) || (br.m_cb == 0))
|
|
{
|
|
*pidError = IDE_TEMPLATE_BAD_COOKIE_SPEC_SRC;
|
|
return E_FAIL;
|
|
}
|
|
memcpy(szFile, br.m_pb, br.m_cb);
|
|
szFile[br.m_cb] = '\0';
|
|
|
|
// Convert file to physical path
|
|
Assert(pHitObj->PServer());
|
|
|
|
WCHAR *pCookieFile;
|
|
#if _IIS_5_1
|
|
// just use CP_ACP for 5.1 since the Core can't handle anything else anyway
|
|
if (FAILED (convStr.Init (szFile))) {
|
|
#else
|
|
// 6.0 can handle UNICODE. Convert using script code page
|
|
if (FAILED (convStr.Init (szFile,pHitObj->GetCodePage()))) {
|
|
#endif
|
|
*pidError = IDE_OOM;
|
|
return E_FAIL;
|
|
}
|
|
|
|
pCookieFile = convStr.GetString();
|
|
if (FAILED(pHitObj->PServer()->MapPathInternal(0, pCookieFile, sztFile)))
|
|
{
|
|
*pidError = IDE_TEMPLATE_BAD_COOKIE_SPEC_SRC;
|
|
return E_FAIL;
|
|
}
|
|
Normalize(sztFile);
|
|
|
|
// Construct 449-echo-cookie object
|
|
C449Cookie *p449 = NULL;
|
|
hr = Create449Cookie(pszName, sztFile, &p449);
|
|
if (FAILED(hr))
|
|
{
|
|
*pidError = IDE_TEMPLATE_LOAD_COOKIESCRIPT_FAILED;
|
|
return hr;
|
|
}
|
|
|
|
// Remember 449 cookie in the array
|
|
Assert(p449);
|
|
hr = m_rgp449.append(p449);
|
|
if (FAILED(hr)) {
|
|
*pidError = IDE_TEMPLATE_LOAD_COOKIESCRIPT_FAILED;
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/* ========================================================
|
|
CTemplate::GetScriptEngineOfSegment
|
|
Returns script engine name for a script segment.
|
|
|
|
Returns
|
|
Byte range containing script engine name
|
|
Side effects
|
|
Advances segment byte range past close tag token
|
|
*/
|
|
void
|
|
CTemplate::GetScriptEngineOfSegment
|
|
(
|
|
CByteRange& brSegment, // segment byte range
|
|
CByteRange& brEngine, // script engine name
|
|
CByteRange& brInclude // value of SRC tag
|
|
)
|
|
{
|
|
BYTE* pbCloseTag; // ptr to close of start tag
|
|
// tags contained in start tag
|
|
CByteRange brTags = BrTagsFromSegment(brSegment, CTokenList::tknCloseTaggedScript, &pbCloseTag);
|
|
|
|
// if no close found, throw error
|
|
if(pbCloseTag == NULL)
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_NO_CLOSE_TSCRIPT);
|
|
|
|
Assert(FTagHasValue(brTags, CTokenList::tknTagRunat, CTokenList::tknValueServer));
|
|
|
|
// get engine name from tags
|
|
brEngine = BrValueOfTag(brTags, CTokenList::tknTagLanguage);
|
|
if(brEngine.IsNull())
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_NO_ENGINE_NAME);
|
|
|
|
// Get SRC attribute from tags
|
|
brInclude = BrValueOfTag(brTags, CTokenList::tknTagSrc);
|
|
|
|
// advance segment past close tag token
|
|
gm_pTokenList->MovePastToken(CTokenList::tknCloseTag, pbCloseTag, brSegment);
|
|
}
|
|
|
|
/* ========================================================
|
|
CTemplate::ProcessTaggedScriptSegment
|
|
Processes a tagged script segment: within tagged script we
|
|
honor plain text (passed through as script text) and HTML comments.
|
|
See bug 423 for istudio scenarios that require this behavior.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::ProcessTaggedScriptSegment
|
|
(
|
|
CByteRange& brSegment, // segment byte range
|
|
CFileMap* pfilemapCurrent,// ptr to filemap of parent file
|
|
CWorkStore& WorkStore, // working storage for source segments
|
|
CHitObj* pHitObj // Browser request object
|
|
)
|
|
{
|
|
_TOKEN* rgtknOpeners; // array of permitted open tokens
|
|
_TOKEN tknOpeners[1];
|
|
UINT ctknOpeners; // count of permitted open tokens
|
|
|
|
// populate array of permitted open tokens
|
|
ctknOpeners = 1;
|
|
rgtknOpeners = tknOpeners;
|
|
rgtknOpeners[0] = CTokenList::tknOpenHTMLComment;
|
|
|
|
// Process source segments embedded within tagged script segment
|
|
while(!brSegment.IsNull())
|
|
ExtractAndProcessSegment(
|
|
brSegment, // byte range to search for next segment-opening token
|
|
ssegTaggedScript, // type of 'leading', i.e. pre-token, source segment
|
|
rgtknOpeners, // array of permitted open tokens
|
|
ctknOpeners, // count of permitted open tokens
|
|
pfilemapCurrent, // ptr to filemap of parent file
|
|
WorkStore, // working storage for source segments
|
|
pHitObj, // Browser request object
|
|
TRUE, // script tag has been processed
|
|
FALSE // NOT in HTML segment
|
|
);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::ProcessObjectSegment
|
|
Processes an object segment.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
throws on error
|
|
*/
|
|
void
|
|
CTemplate::ProcessObjectSegment
|
|
(
|
|
CByteRange& brSegment, // segment byte range
|
|
CFileMap* pfilemapCurrent,// ptr to filemap of parent file
|
|
CWorkStore& WorkStore, // working storage for source segments
|
|
UINT idSequence // segment sequence id
|
|
)
|
|
{
|
|
BYTE* pbCloseTag; // ptr to close of start tag
|
|
// tags contained in start tag
|
|
CByteRange brTags = BrTagsFromSegment(brSegment, CTokenList::tknCloseObject, &pbCloseTag);
|
|
|
|
// if no close found, bail on error
|
|
if(pbCloseTag == NULL)
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_NO_CLOSE_OBJECT);
|
|
|
|
// if this is a server object (RUNAT=Server), process its tags
|
|
if(FTagHasValue(brTags, CTokenList::tknTagRunat, CTokenList::tknValueServer))
|
|
{
|
|
CLSID ClsId; // clsid
|
|
|
|
// get name value
|
|
CByteRange brName = BrValueOfTag(brTags, CTokenList::tknTagID);
|
|
|
|
// if name is null, error out
|
|
if(brName.IsNull())
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_NO_OBJECT_NAME);
|
|
|
|
if(!FValidObjectName(brName))
|
|
ThrowErrorSingleInsert(brName.m_pb, IDE_TEMPLATE_INVALID_OBJECT_NAME, brName.m_pb, brName.m_cb);
|
|
|
|
// get values for ClassID and ProgID tags
|
|
CByteRange brClassIDText = BrValueOfTag(brTags, CTokenList::tknTagClassID);
|
|
CByteRange brProgIDText = BrValueOfTag(brTags, CTokenList::tknTagProgID);
|
|
|
|
if(!brClassIDText.IsNull())
|
|
// if we find a text class id, set clsid with it
|
|
// NOTE progid tag is ignored if classid tag exists
|
|
GetCLSIDFromBrClassIDText(brClassIDText, &ClsId);
|
|
else if(!brProgIDText.IsNull())
|
|
// else if we find a prog id, resolve it into a class id
|
|
GetCLSIDFromBrProgIDText(brProgIDText, &ClsId);
|
|
else
|
|
// else, throw error; can't create an object without at least one of classid or progid
|
|
ThrowErrorSingleInsert(brTags.m_pb, IDE_TEMPLATE_NO_CLASSID_PROGID, brName.m_pb, brName.m_cb);
|
|
|
|
// set scope; bail if bogus
|
|
CompScope csScope = csUnknown;
|
|
CByteRange brScope = BrValueOfTag(brTags, CTokenList::tknTagScope);
|
|
if(brScope.FMatchesSz(SZ_TOKEN(CTokenList::tknValuePage)) || brScope.IsNull())
|
|
// non-existent scope tag defaults to page scope
|
|
csScope = csPage;
|
|
else if(brScope.FMatchesSz(SZ_TOKEN(CTokenList::tknValueApplication)))
|
|
csScope = csAppln;
|
|
else if(brScope.FMatchesSz(SZ_TOKEN(CTokenList::tknValueSession)))
|
|
csScope = csSession;
|
|
else
|
|
ThrowError(brTags.m_pb, IDE_TEMPLATE_BAD_OBJECT_SCOPE);
|
|
|
|
if(!m_fGlobalAsa && csScope != csPage)
|
|
// error out on non-page-level object if we are processing anything but global.asa
|
|
ThrowErrorSingleInsert(brTags.m_pb, IDE_TEMPLATE_BAD_PAGE_OBJECT_SCOPE, brName.m_pb, brName.m_cb);
|
|
else if(m_fGlobalAsa && csScope == csPage)
|
|
// error out on page-level object if we are processing global.asa
|
|
ThrowErrorSingleInsert(brTags.m_pb, IDE_TEMPLATE_BAD_GLOBAL_OBJECT_SCOPE, brName.m_pb, brName.m_cb);
|
|
|
|
// set threading model
|
|
CompModel cmModel = cmSingle;
|
|
CompModelFromCLSID(ClsId, &cmModel);
|
|
|
|
// append object-info to store
|
|
WorkStore.m_ObjectInfoStore.AppendObject(brName, ClsId, csScope, cmModel, idSequence);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetCLSIDFromBrClassIDText
|
|
Sets a clsid from a byte range containing an ANSI text version of a class id
|
|
|
|
Returns
|
|
ptr to clsid (out-parameter)
|
|
Side effects
|
|
throws on error
|
|
*/
|
|
void
|
|
CTemplate::GetCLSIDFromBrClassIDText
|
|
(
|
|
CByteRange& brClassIDText,
|
|
LPCLSID pclsid
|
|
)
|
|
{
|
|
// if class id text starts with its standard object tag prefix, advance past it
|
|
if(!_strnicmp((char*)brClassIDText.m_pb, "clsid:", 6))
|
|
brClassIDText.Advance(6);
|
|
|
|
// if class id text is bracketed with {}, adjust byte range to strip them
|
|
// NOTE we always add {} below, because normal case is that they are missing from input text
|
|
if(*brClassIDText.m_pb == '{')
|
|
brClassIDText.Advance(1);
|
|
if(*(brClassIDText.m_pb + brClassIDText.m_cb - 1) == '}')
|
|
brClassIDText.m_cb--;
|
|
|
|
// Allocate a wide char string for the string version of class id
|
|
// NOTE we add 3 characters to hold {} and null terminator
|
|
OLECHAR* pszWideClassID = new WCHAR[brClassIDText.m_cb + 3];
|
|
if (NULL == pszWideClassID)
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
// start wide string class id with left brace
|
|
pszWideClassID[0] = '{';
|
|
|
|
// Convert the string class id to wide chars
|
|
if (0 == MultiByteToWideChar( CP_ACP, // ANSI code page
|
|
MB_ERR_INVALID_CHARS, // err on invalid chars
|
|
(LPCSTR)brClassIDText.m_pb, // input ANSI string version of class id
|
|
brClassIDText.m_cb, // length of input string
|
|
pszWideClassID + 1, // location for output wide string class id
|
|
brClassIDText.m_cb // size of output buffer
|
|
))
|
|
{
|
|
delete [] pszWideClassID;
|
|
THROW(E_FAIL);
|
|
}
|
|
|
|
// append right brace to wide string
|
|
pszWideClassID[brClassIDText.m_cb + 1] = '}';
|
|
|
|
// Null terminate the wide string
|
|
pszWideClassID[brClassIDText.m_cb + 2] = NULL;
|
|
|
|
// Now get the clsid from wide string class id
|
|
if(FAILED(CLSIDFromString(pszWideClassID, pclsid)))
|
|
{
|
|
delete [] pszWideClassID;
|
|
ThrowErrorSingleInsert(brClassIDText.m_pb, IDE_TEMPLATE_BAD_CLASSID, brClassIDText.m_pb, brClassIDText.m_cb);
|
|
}
|
|
|
|
if(NULL != pszWideClassID)
|
|
delete [] pszWideClassID;
|
|
}
|
|
|
|
/* ===================================================================
|
|
CTemplate::GetCLSIDFromBrProgIDText
|
|
Gets a clsid from the registry given a ProgID
|
|
|
|
Returns
|
|
ptr to clsid (out-parameter)
|
|
Side effects
|
|
throws on error
|
|
*/
|
|
void
|
|
CTemplate::GetCLSIDFromBrProgIDText
|
|
(
|
|
CByteRange& brProgIDText,
|
|
LPCLSID pclsid
|
|
)
|
|
{
|
|
// allocate a wide char string for ProgID plus null terminator
|
|
OLECHAR* pszWideProgID = new WCHAR[brProgIDText.m_cb + 1];
|
|
if (NULL == pszWideProgID)
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
// Convert the string class id to wide chars
|
|
if (0 == MultiByteToWideChar( CP_ACP, // ANSI code page
|
|
MB_ERR_INVALID_CHARS, // err on invalid chars
|
|
(LPCSTR)brProgIDText.m_pb, // input ANSI string version of prog id
|
|
brProgIDText.m_cb, // length of input string
|
|
pszWideProgID, // location for output wide string prog id
|
|
brProgIDText.m_cb // size of output buffer
|
|
))
|
|
{
|
|
delete [] pszWideProgID; pszWideProgID = NULL;
|
|
THROW(E_FAIL);
|
|
}
|
|
|
|
// Null terminate the wide string
|
|
pszWideProgID[brProgIDText.m_cb] = NULL;
|
|
|
|
// Now get clsid from ProgID
|
|
if(FAILED(CLSIDFromProgID(pszWideProgID, pclsid)))
|
|
{
|
|
delete [] pszWideProgID; pszWideProgID = NULL;
|
|
ThrowErrorSingleInsert(brProgIDText.m_pb, IDE_TEMPLATE_BAD_PROGID, brProgIDText.m_pb, brProgIDText.m_cb);
|
|
}
|
|
|
|
// Cache ProgId to CLSID mapping
|
|
g_TypelibCache.RememberProgidToCLSIDMapping(pszWideProgID, *pclsid);
|
|
|
|
if (NULL != pszWideProgID)
|
|
delete [] pszWideProgID;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::FValidObjectName
|
|
Determines whether an object name clashes with a Denali intrinsic object name.
|
|
|
|
Returns
|
|
TRUE or FALSE
|
|
Side effects
|
|
None
|
|
*/
|
|
BOOLB
|
|
CTemplate::FValidObjectName
|
|
(
|
|
CByteRange& brName // object name
|
|
)
|
|
{
|
|
if(brName.FMatchesSz(SZ_OBJ_APPLICATION))
|
|
return FALSE;
|
|
if(brName.FMatchesSz(SZ_OBJ_REQUEST))
|
|
return FALSE;
|
|
if(brName.FMatchesSz(SZ_OBJ_RESPONSE))
|
|
return FALSE;
|
|
if(brName.FMatchesSz(SZ_OBJ_SERVER))
|
|
return FALSE;
|
|
if(brName.FMatchesSz(SZ_OBJ_CERTIFICATE))
|
|
return FALSE;
|
|
if(brName.FMatchesSz(SZ_OBJ_SESSION))
|
|
return FALSE;
|
|
if(brName.FMatchesSz(SZ_OBJ_SCRIPTINGNAMESPACE))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::ProcessIncludeFile
|
|
|
|
Processes an include file.
|
|
|
|
Returns
|
|
Nothing
|
|
*/
|
|
void
|
|
CTemplate::ProcessIncludeFile
|
|
(
|
|
CByteRange& brSegment, // segment byte range
|
|
CFileMap* pfilemapCurrent, // ptr to filemap of parent file
|
|
CWorkStore& WorkStore, // current working storage
|
|
UINT idSequence, // sequence #
|
|
CHitObj* pHitObj, // Browser request object pointer
|
|
BOOL fIsHTML
|
|
)
|
|
{
|
|
CByteRange brFileSpec; // filespec of include file
|
|
BOOLB fVirtual = FALSE; // is include filespec virtual?
|
|
// filespec of include file (sz)
|
|
CHAR szFileSpec[MAX_PATH + 1];
|
|
LPSTR szTemp = szFileSpec; // temp ptr to filespec
|
|
|
|
// get value of FILE tag
|
|
brFileSpec = BrValueOfTag(brSegment, CTokenList::tknTagFile);
|
|
|
|
if(brFileSpec.IsNull())
|
|
{
|
|
// if we found no FILE tag, get value of VIRTUAL tag
|
|
brFileSpec = BrValueOfTag(brSegment, CTokenList::tknTagVirtual);
|
|
fVirtual = TRUE;
|
|
}
|
|
|
|
if(brFileSpec.IsNull())
|
|
// if we found neither, error out
|
|
ThrowError(brSegment.m_pb, IDE_TEMPLATE_NO_INCLUDE_NAME);
|
|
|
|
if (brFileSpec.m_cb>MAX_PATH)
|
|
{
|
|
// return the last MAX_PATH chars of the file name. This is done
|
|
// this way to avoid a Error Too Long message when the include
|
|
// file spec is exceedingly long.
|
|
|
|
char fileNameLast[MAX_PATH+4];
|
|
strcpy(fileNameLast, "...");
|
|
strcpy(fileNameLast+3, (LPSTR)(brFileSpec.m_pb+brFileSpec.m_cb-MAX_PATH));
|
|
|
|
ThrowErrorSingleInsert(brFileSpec.m_pb,
|
|
IDE_TEMPLATE_BAD_INCLUDE,
|
|
brFileSpec.m_pb,
|
|
brFileSpec.m_cb);
|
|
|
|
}
|
|
|
|
// NOTE we manipulate temp sz to preserve szFileSpec
|
|
if(fVirtual)
|
|
{
|
|
if(*brFileSpec.m_pb == '\\')
|
|
{
|
|
// if VIRTUAL path starts with backslash, replace it with fwd slash
|
|
*szTemp++ = '/';
|
|
brFileSpec.Advance(1);
|
|
}
|
|
else if(*brFileSpec.m_pb != '/')
|
|
// if VIRTUAL path starts with anything other than fwd slash or backslash, prepend fwd slash
|
|
*szTemp++ = '/';
|
|
}
|
|
|
|
// append supplied path to temp sz
|
|
strncpy(szTemp, (LPCSTR) brFileSpec.m_pb, brFileSpec.m_cb);
|
|
szTemp[brFileSpec.m_cb] = NULL;
|
|
|
|
if(!fVirtual)
|
|
{
|
|
// if FILE filespec starts with \ or /, hurl
|
|
if(*szFileSpec == '\\' || *szFileSpec == '/')
|
|
ThrowErrorSingleInsert(
|
|
brFileSpec.m_pb,
|
|
IDE_TEMPLATE_BAD_FILE_TAG,
|
|
brFileSpec.m_pb,
|
|
brFileSpec.m_cb
|
|
);
|
|
}
|
|
|
|
// NOTE: szFileSpec is the doctored path (it possibly has "/" prepended.
|
|
// brFileSpec is used as the error location.
|
|
//
|
|
ProcessIncludeFile2(szFileSpec, brFileSpec, pfilemapCurrent, WorkStore, idSequence, pHitObj, fIsHTML);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::ProcessIncludeFile2
|
|
|
|
adds a #include file to the CTemplate and starts the template to processing
|
|
the file.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
Calls GetSegmentsFromFile recursively
|
|
|
|
NOTE - kind of an oddball thing here. The szFileSpec in this case is
|
|
intentionally ANSI as it came from the ASP script content. It may need
|
|
to be converted to UNICODE.
|
|
*/
|
|
void
|
|
CTemplate::ProcessIncludeFile2
|
|
(
|
|
CHAR * szAnsiFileSpec, // file to include
|
|
CByteRange& brErrorLocation, // ByteRange in source where errors should be reported
|
|
CFileMap* pfilemapCurrent, // ptr to filemap of parent file
|
|
CWorkStore& WorkStore, // current working storage
|
|
UINT idSequence, // sequence #
|
|
CHitObj* pHitObj, // Browser request object pointer
|
|
BOOL fIsHTML
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
TCHAR *szFileSpec;
|
|
|
|
#if UNICODE
|
|
CMBCSToWChar convFileSpec;
|
|
|
|
if (FAILED(hr = convFileSpec.Init(szAnsiFileSpec, pHitObj->GetCodePage()))) {
|
|
THROW(hr);
|
|
}
|
|
szFileSpec = convFileSpec.GetString();
|
|
#else
|
|
szFileSpec = szAnsiFileSpec;
|
|
#endif
|
|
// if parent paths are disallowed and filespec contains parent dir reference, hurl
|
|
if (!pHitObj->QueryAppConfig()->fEnableParentPaths() && _tcsstr(szFileSpec, _T("..")))
|
|
ThrowErrorSingleInsert(
|
|
brErrorLocation.m_pb,
|
|
IDE_TEMPLATE_DISALLOWED_PARENT_PATH,
|
|
brErrorLocation.m_pb,
|
|
brErrorLocation.m_cb
|
|
);
|
|
|
|
TRY
|
|
AppendMapFile(
|
|
szFileSpec,
|
|
pfilemapCurrent,
|
|
(szFileSpec[0] == _T('/')) || (szFileSpec[0] == _T('\\')), // fVirtual
|
|
pHitObj, // main file's hit object
|
|
FALSE // not the global.asa file
|
|
);
|
|
CATCH(hrException)
|
|
|
|
// MapFile() threw an exception: delete last filemap's memory and decrement filemap counter
|
|
// NOTE this is a bit hokey, but we need to do it here rather than AppendMapFile (where we allocated)
|
|
// because its other caller(s) may not want this behavior
|
|
delete m_rgpFilemaps[m_cFilemaps-- - 1];
|
|
|
|
/* NOTE exception code from MapFile() is overloaded: it can sometimes
|
|
be an error message id, sometimes a true exception
|
|
NOTE security error causes exception E_USER_LACKS_PERMISSIONS, rather than error id,
|
|
but we pass it thru as if it were an error id because the various error-catch routines
|
|
know how to handle E_USER_LACKS_PERMISSIONS specially.
|
|
*/
|
|
UINT idErrMsg;
|
|
if(hrException == IDE_TEMPLATE_CYCLIC_INCLUDE || hrException == E_USER_LACKS_PERMISSIONS)
|
|
// exception code is really an error message id: set err id to it
|
|
idErrMsg = hrException;
|
|
else if(hrException == E_COULDNT_OPEN_SOURCE_FILE)
|
|
// exception is generic couldn't-open-file : set err id to generic bad-file error
|
|
idErrMsg = IDE_TEMPLATE_BAD_INCLUDE;
|
|
else
|
|
// other exception: re-throw
|
|
THROW(hrException);
|
|
|
|
ThrowErrorSingleInsert(
|
|
brErrorLocation.m_pb,
|
|
idErrMsg,
|
|
brErrorLocation.m_pb,
|
|
brErrorLocation.m_cb
|
|
);
|
|
END_TRY
|
|
|
|
// store ptr to current file map in local before recursive call (which may increment m_cFilemaps)
|
|
CFileMap* pfilemap = m_rgpFilemaps[m_cFilemaps - 1];
|
|
|
|
// get inc-file object from cache
|
|
CIncFile* pIncFile;
|
|
|
|
if(FAILED(hr = g_IncFileMap.GetIncFile(pfilemap->m_szPathTranslated, &pIncFile)))
|
|
THROW(hr);
|
|
|
|
// add this template to inc-file's template list
|
|
if (FAILED(hr = pIncFile->AddTemplate(this)))
|
|
THROW(hr);
|
|
|
|
// set filemap's inc-file ptr
|
|
pfilemap->m_pIncFile = pIncFile;
|
|
|
|
// get source segments from include file
|
|
// bugs 1363, 1364: process include file only after we establish dependencies;
|
|
// required for cache flushing to work correctly after compile errors
|
|
GetSegmentsFromFile(*pfilemap, WorkStore, pHitObj, fIsHTML);
|
|
}
|
|
|
|
/* ===================================================================
|
|
CTemplate::GetOpenToken
|
|
Returns the token index of and a ptr to the first valid open token
|
|
in search range. For the open token to be valid, we must bypass
|
|
segments we should not process, e.g. scripts or objects not tagged as 'server'
|
|
|
|
Returns
|
|
ptr to open token; ptr to open token enum value (out-parameter)
|
|
Side effects
|
|
None
|
|
*/
|
|
BYTE*
|
|
CTemplate::GetOpenToken
|
|
(
|
|
CByteRange brSearch, // (ByVal) byte range to search for next segment-opening token
|
|
SOURCE_SEGMENT ssegLeading, // type of 'leading', i.e. pre-token, source segment
|
|
// (only used when deciding to ignore non-SSI comments)
|
|
_TOKEN* rgtknOpeners, // array of permitted open tokens
|
|
UINT ctknOpeners, // count of permitted open tokens
|
|
_TOKEN* ptknOpen // ptr to open token enum value (out-parameter)
|
|
)
|
|
{
|
|
BYTE* pbTokenOpen = NULL; // ptr to opening token
|
|
|
|
// keep getting segment-opening tokens until we find one that we need to process
|
|
while(TRUE)
|
|
{
|
|
// Get next open token in search range
|
|
*ptknOpen = gm_pTokenList->NextOpenToken(
|
|
brSearch,
|
|
rgtknOpeners,
|
|
ctknOpeners,
|
|
&pbTokenOpen,
|
|
m_wCodePage
|
|
);
|
|
|
|
/* Certain tokens must be followed immediately by white space; others need not.
|
|
NOTE it is pure coincidence that the 'white-space tokens' are also those that
|
|
get special processing below; hence we handle white space issue there.
|
|
If we ever add another 'white-space token' that doesn't require the special processing,
|
|
we will need to handle the white space issue here.
|
|
*/
|
|
|
|
/* Similar thing applies to non-include and non-metadata HTML
|
|
comments. We really don't care for them to generate their
|
|
own segments -- we can reduce number of Response.WriteBlock()
|
|
calls by considering them part of the preceding HTML segment.
|
|
*/
|
|
|
|
if (*ptknOpen == CTokenList::tknOpenHTMLComment)
|
|
{
|
|
if (ssegLeading != ssegHTML) // if not inside HTML
|
|
break; // generate a new segment
|
|
|
|
// for HTML comments check if it is an include or metadata
|
|
// and if not, this is not a separate segment - keep on looking
|
|
// for the next opener
|
|
|
|
// advance search range to just past open token
|
|
gm_pTokenList->MovePastToken(*ptknOpen, pbTokenOpen, brSearch);
|
|
|
|
// find end of comment
|
|
BYTE *pbClose = gm_pTokenList->GetToken(CTokenList::tknCloseHTMLComment,
|
|
brSearch, m_wCodePage);
|
|
if (pbClose == NULL)
|
|
{
|
|
// Error -- let other code handle this
|
|
break;
|
|
}
|
|
|
|
// construct comment byte range to limit search to it
|
|
CByteRange brComment = brSearch;
|
|
brComment.m_cb = DIFF(pbClose - brSearch.m_pb);
|
|
|
|
// look for metadata and include (only inside the comment)
|
|
|
|
if (gm_pTokenList->GetToken(CTokenList::tknCommandINCLUDE,
|
|
brComment, m_wCodePage))
|
|
{
|
|
// SSI inclide -- keep it
|
|
break;
|
|
}
|
|
else if (gm_pTokenList->GetToken(CTokenList::tknTagMETADATA,
|
|
brComment, m_wCodePage))
|
|
{
|
|
// METADATA -- keep it
|
|
break;
|
|
}
|
|
else if (gm_pTokenList->GetToken(CTokenList::tknTagFPBot,
|
|
brComment, m_wCodePage))
|
|
{
|
|
// METADATA -- keep it
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Regular comment - ignore it
|
|
goto LKeepLooking;
|
|
}
|
|
}
|
|
else if (*ptknOpen == CTokenList::tknOpenTaggedScript || *ptknOpen == CTokenList::tknOpenObject)
|
|
{
|
|
/* if token was script or object tag, check to see if:
|
|
a) it is followed immediately by white space; if not keep looking
|
|
b) it opens a well-formed segment, i.e. one with a proper close tag; if not, throw error
|
|
c) it is designated runat-server; if not keep looking
|
|
*/
|
|
|
|
// advance search range to just past open token
|
|
gm_pTokenList->MovePastToken(*ptknOpen, pbTokenOpen, brSearch);
|
|
|
|
// bug 760: if token is not followed immediately by white space, keep looking
|
|
if(!brSearch.IsNull())
|
|
if(!FWhiteSpace(*brSearch.m_pb))
|
|
goto LKeepLooking;
|
|
|
|
// ptr to close of start tag
|
|
BYTE* pbCloseTag;
|
|
// tags contained in start tag
|
|
CByteRange brTags = BrTagsFromSegment(
|
|
brSearch,
|
|
GetComplementToken(*ptknOpen),
|
|
&pbCloseTag
|
|
);
|
|
|
|
if(pbCloseTag == NULL)
|
|
{
|
|
// if no close tag, throw error
|
|
if(*ptknOpen == CTokenList::tknOpenObject)
|
|
ThrowError(pbTokenOpen, IDE_TEMPLATE_NO_CLOSE_OBJECT);
|
|
else if(*ptknOpen == CTokenList::tknOpenTaggedScript)
|
|
ThrowError(pbTokenOpen, IDE_TEMPLATE_NO_CLOSE_TSCRIPT);
|
|
}
|
|
|
|
// if this is a server object (RUNAT=Server), we will process it; else keep looking
|
|
if(FTagHasValue(brTags, CTokenList::tknTagRunat, CTokenList::tknValueServer))
|
|
break;
|
|
|
|
}
|
|
else
|
|
{
|
|
// if token was other than script or object tag, or comment
|
|
// we should process segment;
|
|
// hence we have found our open token, so break
|
|
break;
|
|
}
|
|
|
|
LKeepLooking: ;
|
|
}
|
|
|
|
return pbTokenOpen;
|
|
}
|
|
|
|
/* ===================================================================
|
|
CTemplate::GetCloseToken
|
|
Returns a ptr to the next token of type tknClose.
|
|
|
|
Returns
|
|
ptr to close token
|
|
Side effects
|
|
Detects and errors out on attempt to nest tagged script or object blocks.
|
|
*/
|
|
BYTE*
|
|
CTemplate::GetCloseToken
|
|
(
|
|
CByteRange brSearch, // (ByVal) byte range to search
|
|
_TOKEN tknClose // close token
|
|
)
|
|
{
|
|
BYTE* pbTokenClose = gm_pTokenList->GetToken(tknClose, brSearch, m_wCodePage);
|
|
|
|
if(pbTokenClose != NULL)
|
|
if(tknClose == CTokenList::tknCloseTaggedScript || tknClose == CTokenList::tknCloseObject)
|
|
{
|
|
CByteRange brSegment;
|
|
BYTE* pbTokenOpen;
|
|
|
|
brSegment.m_pb = brSearch.m_pb;
|
|
brSegment.m_cb = DIFF(pbTokenClose - brSearch.m_pb);
|
|
|
|
if(NULL != (pbTokenOpen = gm_pTokenList->GetToken(GetComplementToken(tknClose), brSegment, m_wCodePage)))
|
|
{
|
|
if(tknClose == CTokenList::tknCloseTaggedScript)
|
|
ThrowError(pbTokenOpen, IDE_TEMPLATE_NESTED_TSCRIPT);
|
|
else if(tknClose == CTokenList::tknCloseObject)
|
|
ThrowError(pbTokenOpen, IDE_TEMPLATE_NESTED_OBJECT);
|
|
}
|
|
}
|
|
|
|
return pbTokenClose;
|
|
}
|
|
|
|
/*===================================================================
|
|
CTemplate::GetComplementToken
|
|
|
|
Returns a token's compement token.
|
|
|
|
Returns
|
|
Complement token
|
|
Side effects
|
|
None
|
|
*/
|
|
_TOKEN
|
|
CTemplate::GetComplementToken
|
|
(
|
|
_TOKEN tkn
|
|
)
|
|
{
|
|
switch(tkn)
|
|
{
|
|
// open tokens
|
|
case CTokenList::tknOpenPrimaryScript:
|
|
return CTokenList::tknClosePrimaryScript;
|
|
case CTokenList::tknOpenTaggedScript:
|
|
return CTokenList::tknCloseTaggedScript;
|
|
case CTokenList::tknOpenObject:
|
|
return CTokenList::tknCloseObject;
|
|
case CTokenList::tknOpenHTMLComment:
|
|
return CTokenList::tknCloseHTMLComment;
|
|
|
|
// close tokens
|
|
case CTokenList::tknClosePrimaryScript:
|
|
return CTokenList::tknOpenPrimaryScript;
|
|
case CTokenList::tknCloseTaggedScript:
|
|
return CTokenList::tknOpenTaggedScript;
|
|
case CTokenList::tknCloseObject:
|
|
return CTokenList::tknOpenObject;
|
|
case CTokenList::tknCloseHTMLComment:
|
|
return CTokenList::tknOpenHTMLComment;
|
|
}
|
|
|
|
Assert(FALSE);
|
|
return CTokenList::tknEOF;
|
|
}
|
|
|
|
/*===================================================================
|
|
CTemplate::GetSegmentOfOpenToken
|
|
|
|
Returns the segment type of an open token.
|
|
|
|
Returns
|
|
source segment type of open token
|
|
Side effects
|
|
None
|
|
*/
|
|
CTemplate::SOURCE_SEGMENT
|
|
CTemplate::GetSegmentOfOpenToken
|
|
(
|
|
_TOKEN tknOpen
|
|
)
|
|
{
|
|
switch(tknOpen)
|
|
{
|
|
case CTokenList::tknOpenPrimaryScript:
|
|
return ssegPrimaryScript;
|
|
case CTokenList::tknOpenTaggedScript:
|
|
return ssegTaggedScript;
|
|
case CTokenList::tknOpenObject:
|
|
return ssegObject;
|
|
case CTokenList::tknOpenHTMLComment:
|
|
return ssegHTMLComment;
|
|
}
|
|
|
|
return ssegHTML;
|
|
}
|
|
|
|
/* ========================================================
|
|
CTemplate::BrTagsFromSegment
|
|
|
|
Returns the tag range from an HTML start tag
|
|
|
|
Returns
|
|
tag byte range
|
|
Side effects
|
|
none
|
|
*/
|
|
CByteRange
|
|
CTemplate::BrTagsFromSegment
|
|
(
|
|
CByteRange brSegment, // segment byte range
|
|
_TOKEN tknClose, // close token
|
|
BYTE** ppbCloseTag // ptr-to-ptr to close tag - returned to caller
|
|
)
|
|
{
|
|
CByteRange brTags; // tags return value - NOTE constructed null
|
|
// ptr to close token - NOTE null if none within segment byte range
|
|
BYTE* pbTokenClose = GetCloseToken(brSegment, tknClose);
|
|
|
|
// if no close tag found, return null tags
|
|
if(NULL == (*ppbCloseTag = gm_pTokenList->GetToken(CTokenList::tknCloseTag, brSegment, m_wCodePage)))
|
|
goto Exit;
|
|
|
|
// if next non-null close token occurs before close tag, close tag is invalid; return nulls
|
|
if((pbTokenClose != NULL) && (*ppbCloseTag > pbTokenClose ))
|
|
{
|
|
*ppbCloseTag = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
// crack tags from header tag
|
|
brTags.m_pb = brSegment.m_pb;
|
|
brTags.m_cb = DIFF(*ppbCloseTag - brSegment.m_pb);
|
|
|
|
Exit:
|
|
return brTags;
|
|
}
|
|
|
|
/* ========================================================
|
|
CTemplate::BrValueOfTag
|
|
|
|
Returns a tag's value from a byte range; null if tag is not found
|
|
NOTE value search algorithm per W3 HTML spec - see www.w3.org
|
|
|
|
Returns
|
|
byte range of tag's value
|
|
pfTagExists - does the tag exist in tags byte range? (out-parameter)
|
|
NOTE we default *pfTagExists = TRUE; most callers don't care and omit this parameter
|
|
Side effects
|
|
none
|
|
*/
|
|
CByteRange
|
|
CTemplate::BrValueOfTag
|
|
(
|
|
CByteRange brTags, // tags byte range
|
|
_TOKEN tknTagName // tag name token
|
|
)
|
|
{
|
|
CByteRange brTemp = brTags; // temp byte range
|
|
CByteRange brValue; // byte range of value for the given tag - NOTE constructed null
|
|
char chDelimiter = NULL; // value delimiter
|
|
// ptr to tag name
|
|
BYTE* pbTagName = GetTagName(brTags, tknTagName);
|
|
|
|
// If we did not find tag, return
|
|
if(pbTagName == NULL)
|
|
return brValue;
|
|
|
|
// Move past tag name token and pre-separator white space
|
|
brTemp.Advance(DIFF(pbTagName - brTags.m_pb) + CCH_TOKEN(tknTagName));
|
|
LTrimWhiteSpace(brTemp);
|
|
if(brTemp.IsNull())
|
|
goto Exit;
|
|
|
|
// If we did not find separator, return
|
|
if(*brTemp.m_pb != CH_ATTRIBUTE_SEPARATOR)
|
|
goto Exit;
|
|
|
|
// Move past separator and post-separator white space
|
|
brTemp.Advance(sizeof(CH_ATTRIBUTE_SEPARATOR));
|
|
LTrimWhiteSpace(brTemp);
|
|
if(brTemp.IsNull())
|
|
goto Exit;
|
|
|
|
// If value begins with a quote mark, cache it as delimiter
|
|
if((*brTemp.m_pb == CH_SINGLE_QUOTE) || (*brTemp.m_pb == CH_DOUBLE_QUOTE))
|
|
chDelimiter = *brTemp.m_pb;
|
|
|
|
if(chDelimiter)
|
|
{
|
|
// move past delimiter
|
|
brTemp.Advance(sizeof(chDelimiter));
|
|
if(brTemp.IsNull())
|
|
goto Exit;
|
|
}
|
|
|
|
// provisionally set value to temp byte range
|
|
brValue = brTemp;
|
|
|
|
// advance temp byte range to end of value range
|
|
while(
|
|
(chDelimiter && (*brTemp.m_pb != chDelimiter)) // if we have a delimiter, find next delimiter
|
|
|| (!chDelimiter && (!FWhiteSpace(*brTemp.m_pb))) // if we have no delimiter, find next white space
|
|
)
|
|
{
|
|
// advance temp byte range
|
|
brTemp.Advance(1);
|
|
if(brTemp.IsNull())
|
|
{
|
|
if(chDelimiter)
|
|
// we found no closing delimiter, so error out
|
|
ThrowErrorSingleInsert(brValue.m_pb, IDE_TEMPLATE_NO_ATTRIBUTE_DELIMITER,
|
|
pbTagName, CCH_TOKEN(tknTagName));
|
|
else
|
|
// value runs to end of temp byte range, so exit (since we already init'ed to temp)
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// set byte count so that value points to delimited range
|
|
brValue.m_cb = DIFF(brTemp.m_pb - brValue.m_pb);
|
|
|
|
Exit:
|
|
// if tag is empty, raise an error
|
|
if (brValue.IsNull())
|
|
{
|
|
ThrowErrorSingleInsert(brTags.m_pb, IDE_TEMPLATE_VALUE_REQUIRED, pbTagName, CCH_TOKEN(tknTagName));
|
|
}
|
|
|
|
// enforce mandatory tag values if required
|
|
if(tknTagName == CTokenList::tknTagRunat)
|
|
{
|
|
if(!brValue.FMatchesSz(SZ_TOKEN(CTokenList::tknValueServer)))
|
|
ThrowError(brTags.m_pb, IDE_TEMPLATE_RUNAT_NOT_SERVER);
|
|
}
|
|
|
|
return brValue;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CompTagName
|
|
|
|
Compares characters in two buffers (case-insensitive) and returns TRUE or FALSE
|
|
|
|
Side effects
|
|
none
|
|
*/
|
|
BOOL
|
|
CTemplate::CompTagName
|
|
(
|
|
CByteRange &brTags, // tags byte range
|
|
_TOKEN tknTagName // tag name token
|
|
)
|
|
{
|
|
CByteRange brTemp = brTags; // local byte range, so we don't change tags byte range
|
|
UINT cbAttributeName = CCH_TOKEN(tknTagName); // length of tag name
|
|
LPSTR pszAttributeName = SZ_TOKEN(tknTagName); // tag name string
|
|
|
|
// search for potential matches on tag name string, case-insensitive
|
|
if(!brTemp.IsNull())
|
|
if( 0 == _memicmp( brTemp.m_pb, pszAttributeName, cbAttributeName ))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetTagName
|
|
|
|
Returns a ptr to a tag name in a byte range; null if not found
|
|
|
|
Returns
|
|
ptr to tag name
|
|
Side effects
|
|
none
|
|
*/
|
|
BYTE*
|
|
CTemplate::GetTagName
|
|
(
|
|
CByteRange brTags, // tags byte range
|
|
_TOKEN tknTagName // tag name token
|
|
)
|
|
{
|
|
CByteRange brTemp = brTags; // local byte range, so we don't change tags byte range
|
|
UINT cbAttributeName = CCH_TOKEN(tknTagName); // length of tag name
|
|
LPSTR pszAttributeName = SZ_TOKEN(tknTagName); // tag name string
|
|
|
|
// PREFIX: pszAttributeName could be NULL, though I don't think that can happen.
|
|
Assert (pszAttributeName != NULL);
|
|
|
|
while(TRUE)
|
|
{
|
|
// search for potential matches on tag name string, case-insensitive
|
|
while(!brTemp.IsNull())
|
|
{
|
|
if(0 == _strnicmp((char*)brTemp.m_pb, pszAttributeName, cbAttributeName ))
|
|
break;
|
|
brTemp.Advance(1);
|
|
}
|
|
|
|
// if we did not find tag name string at all, return 'not found'
|
|
if(brTemp.IsNull())
|
|
goto NotFound;
|
|
|
|
// if it is a valid HTML tag name, return it
|
|
if(FTagName(brTemp.m_pb, cbAttributeName))
|
|
goto Exit;
|
|
|
|
// if we found a matching but invalid substring, advance beyond it so we can search again
|
|
brTemp.Advance(cbAttributeName);
|
|
|
|
// if we have exhausted search range, return 'not found'
|
|
if(brTemp.IsNull())
|
|
goto NotFound;
|
|
}
|
|
|
|
Exit:
|
|
return brTemp.m_pb;
|
|
|
|
NotFound:
|
|
return NULL;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetTag
|
|
|
|
Returns a ptr to a tag name in a byte range; null if not found
|
|
|
|
Returns
|
|
ptr to tag name
|
|
Side effects
|
|
none
|
|
*/
|
|
BOOL
|
|
CTemplate::GetTag
|
|
(
|
|
CByteRange &brTags, // tags byte range
|
|
int nIndex
|
|
)
|
|
{
|
|
CByteRange brTemp = brTags; // local byte range, so we don't change tags byte range
|
|
int nTIndex = 0;
|
|
|
|
while(TRUE)
|
|
{
|
|
// locate the start of a tag by skipping past the script tag "<%" and any leading white space
|
|
//
|
|
while(!brTemp.IsNull())
|
|
{
|
|
if( *brTemp.m_pb == '<' ||
|
|
*brTemp.m_pb == '%' ||
|
|
*brTemp.m_pb == '@' ||
|
|
FWhiteSpace(*brTemp.m_pb))
|
|
{
|
|
brTemp.Advance(1);
|
|
brTags.Advance(1);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
// search for potential matches on tag name string, case-insensitive
|
|
//
|
|
while(!brTemp.IsNull())
|
|
{
|
|
if( *brTemp.m_pb == '=' || FWhiteSpace(*brTemp.m_pb))
|
|
{
|
|
nTIndex++;
|
|
break;
|
|
}
|
|
brTemp.Advance(1);
|
|
}
|
|
|
|
// if we did not find tag name string at all, return 'not found'
|
|
if(brTemp.IsNull())
|
|
goto NotFound;
|
|
|
|
// if it is a valid HTML tag name, return it
|
|
if(FTagName(brTags.m_pb, DIFF(brTemp.m_pb - brTags.m_pb)))
|
|
if(nTIndex >= nIndex)
|
|
goto Exit;
|
|
|
|
// position past named pair data and reset start and if end of byte range then
|
|
// goto NotFound
|
|
//
|
|
while(!brTemp.IsNull() && !FWhiteSpace(*brTemp.m_pb))
|
|
brTemp.Advance(1);
|
|
|
|
if(brTemp.IsNull())
|
|
goto NotFound;
|
|
else
|
|
brTags.Advance(DIFF(brTemp.m_pb - brTags.m_pb));
|
|
}
|
|
Exit:
|
|
return TRUE;
|
|
|
|
NotFound:
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::FTagHasValue
|
|
|
|
Do tags include tknTag=tknValue?
|
|
|
|
Returns
|
|
TRUE if tags include value, else FALSE
|
|
Side effects
|
|
none
|
|
*/
|
|
BOOLB
|
|
CTemplate::FTagHasValue
|
|
(
|
|
const CByteRange& brTags, // tags byte range to search
|
|
_TOKEN tknTag, // tag token
|
|
_TOKEN tknValue // value token
|
|
)
|
|
{
|
|
return (BrValueOfTag(brTags, tknTag) // byte range of value
|
|
.FMatchesSz(SZ_TOKEN(tknValue)));
|
|
}
|
|
|
|
/* =========================
|
|
CTemplate::CopySzAdv
|
|
|
|
Copies a string to a ptr and advances the ptr just beyond the copied string.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
advances ptr beyond copied string
|
|
*/
|
|
void
|
|
CTemplate::CopySzAdv
|
|
(
|
|
char* pchWrite, // write location ptr
|
|
LPSTR psz // string to copy
|
|
)
|
|
{
|
|
strcpy(pchWrite, psz);
|
|
pchWrite += strlen(psz);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::WriteTemplate
|
|
|
|
Writes the template out to a contiguous block of memory.
|
|
|
|
Returns:
|
|
nothing
|
|
Side effects:
|
|
Allocates, and possibly re-allocates, memory for the template.
|
|
|
|
HERE IS HOW IT WORKS
|
|
--------------------
|
|
- an 'offset' is the count of bytes from start-of-template to a location
|
|
within template memory
|
|
- at the start of the template are 3 USHORTs, the counts of script blocks,
|
|
object-infos and HTML blocks, respectively
|
|
- next are 4 ULONGs, each an offset to a block of offsets; in order, these are:
|
|
offset-to-offset to first script engine name
|
|
offset-to-offset to first script block (the script text itself)
|
|
offset-to-offset to first object-info
|
|
offset-to-offset to first HTML block
|
|
- next are a variable number of ULONGs, each an offset to a particular
|
|
template component. In order these ULONGs are:
|
|
Offsets to Count of offsets
|
|
---------- ----------------
|
|
script engine names cScriptBlocks
|
|
script blocks cScriptBlocks
|
|
object-infos cObjectInfos
|
|
HTML blocks cHTMLBlocks
|
|
- next are the template components themselves, stored sequentially
|
|
in the following order:
|
|
script engine names
|
|
script blocks
|
|
object-infos
|
|
HTML blocks
|
|
|
|
HERE IS HOW IT LOOKS
|
|
--------------------
|
|
|--|--|--| 3 template component counts (USHORTs)
|
|
|
|
|-- --|-- --| 4 offsets to template component offsets (ULONGs)
|
|
|
|
|-- --|-- --|-- --|-- --|-- --| template component offsets (ULONGs)
|
|
|-- --| ............... |-- --|
|
|
|-- --|-- --|-- --|-- --|-- --|
|
|
|
|
| ........................... | template components
|
|
| ........................... |
|
|
| ........................... |
|
|
| ........................... |
|
|
|
|
or, mnemonically:
|
|
|
|
cS cO cH 3 template component counts (USHORTs)
|
|
|
|
offSE offSB offOb offHT 4 offsets to template component offsets (ULONGs)
|
|
|
|
|-- --|-- --|-- --|-- --|-- --| template component offsets (ULONGs)
|
|
|-- --| ............... |-- --|
|
|
|-- --|-- --|-- --|-- --|-- --|
|
|
|
|
| ........................... | template components
|
|
| ........................... |
|
|
| ........................... |
|
|
| ........................... |
|
|
*/
|
|
void
|
|
CTemplate::WriteTemplate
|
|
(
|
|
CWorkStore& WorkStore, // working storage for source segments
|
|
CHitObj* pHitObj
|
|
)
|
|
{
|
|
USHORT i; // loop index
|
|
CByteRange brWrite; // general-purpose byte range for writing stuff out
|
|
|
|
USHORT cScriptBlocks = WorkStore.CRequiredScriptEngines(m_fGlobalAsa); // count of script blocks - 1 per required engine
|
|
USHORT cObjectInfos = WorkStore.m_ObjectInfoStore.Count(); // count of object-infos
|
|
USHORT cHTMLBlocks = WorkStore.m_bufHTMLSegments.Count(); // count of HTML segments
|
|
|
|
// Calc count of offset-to-offsets == total count of all scripts, objects, etc
|
|
// NOTE we keep separate offset-to-offsets for script engine names and script text, hence 2x
|
|
USHORT cBlockPtrs = (2 * cScriptBlocks) + cObjectInfos + cHTMLBlocks;
|
|
|
|
// Calc total memory required
|
|
// NOTE header includes counts and ptr-ptrs
|
|
UINT cbRequiredHeader = (C_COUNTS_IN_HEADER * sizeof(USHORT)) + (C_OFFOFFS_IN_HEADER * sizeof(BYTE**));
|
|
UINT cbRequiredBlockPtrs = cBlockPtrs * sizeof(BYTE*);
|
|
|
|
// Init write-offset locations
|
|
// offset to location for writing the next header information; header is at start of template
|
|
UINT cbHeaderOffset = 0;
|
|
// offset to location for writing the next offset-to-offset; immediately follows header
|
|
UINT cbOffsetToOffset = cbRequiredHeader;
|
|
// offset to location for writing the next block of data; immediately follows offset-to-offsets
|
|
UINT cbDataOffset = cbOffsetToOffset + cbRequiredBlockPtrs;
|
|
|
|
// offset in source file (for html blocks)
|
|
ULONG cbSourceOffset;
|
|
// source filename (only if include file)
|
|
BYTE *pbIncFilename;
|
|
ULONG cbIncFilename;
|
|
|
|
// Allocate memory and init start-ptr; bail on fail
|
|
// NOTE here we init template member variables m_pbStart and m_cbTemplate
|
|
if(NULL == (m_pbStart = (BYTE*) CTemplate::LargeMalloc(m_cbTemplate = CB_TEMPLATE_DEFAULT)))
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
// write out template header
|
|
WriteHeader(cScriptBlocks, cObjectInfos, cHTMLBlocks, &cbHeaderOffset, &cbOffsetToOffset);
|
|
|
|
// Reset offset-to-offset ptr to beginning of its section
|
|
cbOffsetToOffset = cbRequiredHeader;
|
|
|
|
// write script engine names and prog lang ids at current of data section
|
|
for(i = 0; i < WorkStore.m_ScriptStore.CountPreliminaryEngines(); i++)
|
|
{
|
|
// bug 933: only write non-empty script engines
|
|
if(WorkStore.FScriptEngineRequired(i, m_fGlobalAsa))
|
|
{
|
|
WorkStore.m_ScriptStore.m_bufEngineNames.GetItem(i, brWrite);
|
|
WriteByteRangeAdv(brWrite, TRUE, &cbDataOffset, &cbOffsetToOffset);
|
|
MemCpyAdv(&cbDataOffset, &(WorkStore.m_ScriptStore.m_rgProgLangId[i]), sizeof(PROGLANG_ID), sizeof(DWORD));
|
|
}
|
|
}
|
|
|
|
// write the script blocks for each engine at current of data section
|
|
// NOTE we sequence this after script engine names (rather than interleave)
|
|
USHORT idEngine = 0;
|
|
for(i = 0; i < WorkStore.m_ScriptStore.CountPreliminaryEngines(); i++)
|
|
{
|
|
// bug 933: only write non-empty script engines
|
|
if(WorkStore.FScriptEngineRequired(i, m_fGlobalAsa))
|
|
{
|
|
// bug 933: we need to pass both 'preliminary' engine id (i) and id of instantiated engine (idEngine)
|
|
WriteScriptBlockOfEngine(i, idEngine, WorkStore, &cbDataOffset, &cbOffsetToOffset, pHitObj);
|
|
idEngine++;
|
|
}
|
|
}
|
|
|
|
// Write object-infos at current of data section
|
|
for(i = 0; i < cObjectInfos; i++)
|
|
{
|
|
// get i-th object info from work store
|
|
WorkStore.m_ObjectInfoStore.m_bufObjectNames.GetItem(i, brWrite);
|
|
// write object name
|
|
WriteByteRangeAdv(brWrite, TRUE, &cbDataOffset, &cbOffsetToOffset);
|
|
// write clsid, scope and model
|
|
/* CONSIDER include if we need to byte-align clsid
|
|
// NOTE byte-align clsid (16-byte), which then byte-aligns scope and model (both 4-byte)
|
|
MemCpyAdv(&cbDataOffset, &(WorkStore.m_ObjectInfoStore.m_pObjectInfos[i].m_clsid), sizeof(CLSID), TRUE); */
|
|
MemCpyAdv(&cbDataOffset, &(WorkStore.m_ObjectInfoStore.m_pObjectInfos[i].m_clsid), sizeof(CLSID), sizeof(DWORD));
|
|
MemCpyAdv(&cbDataOffset, &(WorkStore.m_ObjectInfoStore.m_pObjectInfos[i].m_scope), sizeof(CompScope));
|
|
MemCpyAdv(&cbDataOffset, &(WorkStore.m_ObjectInfoStore.m_pObjectInfos[i].m_model), sizeof(CompModel));
|
|
}
|
|
|
|
// if other than globals template, write HTML blocks at current of data section
|
|
if(!m_fGlobalAsa)
|
|
for(i = 0; i < cHTMLBlocks; i++)
|
|
{
|
|
// write byterange with html code
|
|
WorkStore.m_bufHTMLSegments.GetItem(i, brWrite);
|
|
WriteByteRangeAdv(brWrite, TRUE, &cbDataOffset, &cbOffsetToOffset);
|
|
|
|
// source offset and include file
|
|
cbSourceOffset = 0;
|
|
pbIncFilename = NULL;
|
|
cbIncFilename = 0;
|
|
|
|
if (brWrite.m_pfilemap)
|
|
{
|
|
// calculate offset from filemap
|
|
CFileMap *pFileMap = (CFileMap *)brWrite.m_pfilemap;
|
|
if (pFileMap->m_pbStartOfFile) // mapped?
|
|
{
|
|
cbSourceOffset = DIFF(brWrite.m_pb - pFileMap->m_pbStartOfFile) + 1;
|
|
|
|
if (pFileMap->GetParent() != NULL && // is include file?
|
|
pFileMap->m_szPathInfo) // path exists
|
|
{
|
|
pbIncFilename = (BYTE *)pFileMap->m_szPathInfo;
|
|
cbIncFilename = _tcslen(pFileMap->m_szPathInfo)*sizeof(TCHAR);
|
|
}
|
|
}
|
|
}
|
|
|
|
// write them
|
|
MemCpyAdv(&cbDataOffset, &cbSourceOffset, sizeof(ULONG));
|
|
MemCpyAdv(&cbDataOffset, &cbIncFilename, sizeof(ULONG));
|
|
if (cbIncFilename > 0)
|
|
MemCpyAdv(&cbDataOffset, pbIncFilename, cbIncFilename+sizeof(TCHAR));
|
|
}
|
|
|
|
// trim template memory to exactly what we used
|
|
// NOTE cbDataOffset now contains maximum reach we have written to
|
|
if(NULL == (m_pbStart = (BYTE*) CTemplate::LargeReAlloc(m_pbStart, m_cbTemplate = cbDataOffset)))
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::WriteHeader
|
|
|
|
Writes template header, and writes vesrion stamp and source file name into
|
|
template data region.
|
|
|
|
Returns
|
|
nothing
|
|
Side effects
|
|
none
|
|
*/
|
|
void
|
|
CTemplate::WriteHeader
|
|
(
|
|
USHORT cScriptBlocks, // count of script blocks
|
|
USHORT cObjectInfos, // count of object-infos
|
|
USHORT cHTMLBlocks, // count of HTML blocks
|
|
UINT* pcbHeaderOffset, // ptr to offset value for header write location
|
|
UINT* pcbOffsetToOffset // ptr to offset value for offset-to-offset write location
|
|
)
|
|
{
|
|
// Write template component counts out at start of header
|
|
WriteShortAdv(cScriptBlocks, pcbHeaderOffset);
|
|
WriteShortAdv(cObjectInfos, pcbHeaderOffset);
|
|
WriteShortAdv(cHTMLBlocks, pcbHeaderOffset);
|
|
|
|
// Write offsets-to-offset to script engine names, script blocks, object-infos, HTML blocks
|
|
// NOTE counts of script engine names and script blocks are identical
|
|
WriteOffsetToOffset(cScriptBlocks, pcbHeaderOffset, pcbOffsetToOffset);
|
|
WriteOffsetToOffset(cScriptBlocks, pcbHeaderOffset, pcbOffsetToOffset);
|
|
WriteOffsetToOffset(cObjectInfos, pcbHeaderOffset, pcbOffsetToOffset);
|
|
WriteOffsetToOffset(cHTMLBlocks, pcbHeaderOffset, pcbOffsetToOffset);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::WriteScriptBlockOfEngine
|
|
|
|
Writes out script block for idEngine-th script engine.
|
|
NOTE segment buffer [0] contains primary script segments
|
|
segment buffer [1] contains tagged script segments of default engine
|
|
segment buffer [i] contains tagged script segments of (i-1)th engine, for i >= 2
|
|
|
|
Returns
|
|
nothing
|
|
Side effects
|
|
none
|
|
*/
|
|
void
|
|
CTemplate::WriteScriptBlockOfEngine
|
|
(
|
|
USHORT idEnginePrelim, // preliminary script engine id (assigned during template pre-processing)
|
|
USHORT idEngine, // actual script engine id (written into compiled template)
|
|
CWorkStore& WorkStore, // working storage for source segments
|
|
UINT* pcbDataOffset, // ptr to write location offset value
|
|
UINT* pcbOffsetToOffset, // ptr to offset-to-offset offset value
|
|
CHitObj* pHitObj
|
|
)
|
|
{
|
|
// NOTE works for all id's - see comment above
|
|
USHORT iTSegBuffer = idEnginePrelim + 1; // index of tagged segment buffer
|
|
CByteRange brSegment; // current script segment
|
|
UINT i; // loop index
|
|
UINT cbScriptBlockOffset; // offset to script block write-location
|
|
// count of tagged script segments
|
|
UINT cTaggedSegments = WorkStore.m_ScriptStore.m_ppbufSegments[iTSegBuffer]->Count();
|
|
|
|
// Byte-align data offset location, since next thing we will write there is script block length
|
|
// NOTE we use brSegment.m_cb generically; we really want CByteRange::m_cb
|
|
ByteAlignOffset(pcbDataOffset, sizeof(brSegment.m_cb));
|
|
// Cache current data offset location as offset to start of script block
|
|
cbScriptBlockOffset = *pcbDataOffset;
|
|
// Write offset to start of script block at current offset-to-offset offset
|
|
WriteLongAdv(cbScriptBlockOffset, pcbOffsetToOffset);
|
|
// advance data ptr (by init'ing script length value to 0)
|
|
WriteLongAdv(0, pcbDataOffset);
|
|
// reset counter that AppendSourceInfo uses
|
|
m_cbTargetOffsetPrevT = 0;
|
|
|
|
// if other than globals template and this is default script engine (prelim engine 0),
|
|
// write primary script procedure at current of data section
|
|
if(!m_fGlobalAsa)
|
|
if(idEnginePrelim == 0)
|
|
WritePrimaryScriptProcedure(0, WorkStore, pcbDataOffset, cbScriptBlockOffset + sizeof(long));
|
|
|
|
// write out tagged script segments at current of data section
|
|
for(i = 0; i < cTaggedSegments; i++)
|
|
{
|
|
WorkStore.m_ScriptStore.m_ppbufSegments[iTSegBuffer]->GetItem(i, brSegment);
|
|
WriteScriptSegment(
|
|
idEngine,
|
|
m_rgpSegmentFilemaps[brSegment.m_idSequence],
|
|
brSegment,
|
|
pcbDataOffset,
|
|
cbScriptBlockOffset + sizeof(long),
|
|
FALSE /* fAllowExprWrite - disallowed for tagged script */
|
|
);
|
|
}
|
|
|
|
// Write out null terminator
|
|
MemCpyAdv(pcbDataOffset, SZ_NULL, 1);
|
|
|
|
// convert script text to unicode, so script engine won't have to do this at runtime
|
|
|
|
// ptr to start of script is:
|
|
// ptr start of template + offset to script + size of script length
|
|
LPSTR szScript = (LPSTR) m_pbStart + cbScriptBlockOffset + sizeof(ULONG);
|
|
/* script block length is:
|
|
== EndOfScriptText - StartOfScriptText
|
|
== (current of data ptr - length of null ) - (start of primary script byte range + length of br.m_cb)
|
|
*/
|
|
ULONG cbScript = (*pcbDataOffset - sizeof(BYTE)) - (cbScriptBlockOffset + sizeof(ULONG));
|
|
|
|
/* bug 887: we append one extra "pseudo-line" to the end of source-infos array
|
|
to cover the case where the script engine reports back an error line number
|
|
that falls after end of script. We always want the "pseudo-line" to point to the
|
|
main file, so that the debugger can display something reasonable, so we pass
|
|
m_rgpFilemaps[0] as the source file, which is the main file.
|
|
*/
|
|
AppendSourceInfo(idEngine, m_rgpFilemaps[0],
|
|
NULL, // Don't calculate line #
|
|
UINT_MAX, // Don't care & calculation is expensive
|
|
UINT_MAX, // Start of script blocks
|
|
UINT_MAX, // Really don't care
|
|
0, // zero characters exist past EOF
|
|
TRUE); // Line is HTML (bogus)
|
|
|
|
// get wide string version of script text, using hitobj's code page
|
|
// NOTE we may slightly over-allocate space for wstrScript by using cbScript (e.g. if script contains DBCS).
|
|
// However, this won't matter since we call MultiByteToWideChar with -1, telling it to calc length of szScript
|
|
LPOLESTR wstrScript = NULL;
|
|
DWORD cbConvert = ( cbScript + 1 ) * 2;
|
|
|
|
STACK_BUFFER( tempScript, 2048 );
|
|
|
|
if (!tempScript.Resize(cbConvert)) {
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
|
|
wstrScript = (LPOLESTR)tempScript.QueryPtr();
|
|
|
|
MultiByteToWideChar( m_wCodePage, 0, szScript, -1, wstrScript, (cbScript + 1) );
|
|
|
|
// reset data offset location to start of script
|
|
*pcbDataOffset = cbScriptBlockOffset + sizeof(ULONG);
|
|
// write wide string script text over top of ansi version
|
|
MemCpyAdv(pcbDataOffset, wstrScript, sizeof(WCHAR) * cbScript);
|
|
// write wide string null terminator
|
|
MemCpyAdv(pcbDataOffset, WSTR_NULL, sizeof(WCHAR));
|
|
|
|
// write script length at start of script byte range
|
|
// NOTE we do this here because script length was initially unknown
|
|
WriteLongAdv(sizeof(WCHAR) * cbScript, &cbScriptBlockOffset);
|
|
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::WritePrimaryScriptProcedure
|
|
|
|
Writes out default-engine primary script procedure.
|
|
If VBScript is default-engine, the primary script procedure contains
|
|
interleaved script commands and HTML block-writes, like this:
|
|
Sub Main
|
|
...
|
|
[script segment]
|
|
Response.WriteBlock([HTML block id])
|
|
...
|
|
[script segment]
|
|
Response.WriteBlock([HTML block id])
|
|
...
|
|
[script segment]
|
|
Response.WriteBlock([HTML block id])
|
|
...
|
|
End Sub
|
|
|
|
NOTE segment buffer [0] == primary script segments
|
|
|
|
Returns
|
|
nothing
|
|
Side effects
|
|
none
|
|
*/
|
|
void
|
|
CTemplate::WritePrimaryScriptProcedure
|
|
(
|
|
USHORT idEngine, // script engine id
|
|
CWorkStore& WorkStore, // working storage for source segments
|
|
UINT* pcbDataOffset, // ptr to write location offset value
|
|
UINT cbScriptBlockOffset // ptr to start of script engine code
|
|
)
|
|
{
|
|
USHORT cScriptSegmentsProcessed = 0; // count of script blocks processed
|
|
USHORT cHTMLBlocksProcessed = 0; // count of HTML blocks processed
|
|
CByteRange brScriptNext; // next script block to write out
|
|
CByteRange brHTMLNext; // next HTML block to write out
|
|
char szHTMLBlockID[6]; // sz representation of HTML block ID - NOTE limited to 5 digits
|
|
// count of primary script segments
|
|
USHORT cPrimaryScriptSegments = WorkStore.m_ScriptStore.m_ppbufSegments[0]->Count();
|
|
// count of HTML blocks
|
|
USHORT cHTMLBlocks = WorkStore.m_bufHTMLSegments.Count();
|
|
CFileMap* pfilemap; // file where HTML segment lives in
|
|
|
|
// get initial script segment and initial html segment
|
|
if(cPrimaryScriptSegments)
|
|
WorkStore.m_ScriptStore.m_ppbufSegments[0]->GetItem(0, brScriptNext);
|
|
if(cHTMLBlocks)
|
|
WorkStore.m_bufHTMLSegments.GetItem(0, brHTMLNext);
|
|
|
|
// While HTML block(s) or primary script segment(s) remain to be processed ...
|
|
while((cHTMLBlocksProcessed < cHTMLBlocks) || (cScriptSegmentsProcessed < cPrimaryScriptSegments))
|
|
{
|
|
// If HTML block(s) remain to be processed ...
|
|
if(cHTMLBlocksProcessed < cHTMLBlocks)
|
|
while (TRUE)
|
|
{
|
|
// Write out write-block command for each HTML segment earlier in source than next script segment
|
|
if(brHTMLNext.FEarlierInSourceThan(brScriptNext) || (cScriptSegmentsProcessed >= cPrimaryScriptSegments))
|
|
{
|
|
// append source-info for the target script line we just manufactured
|
|
pfilemap = m_rgpSegmentFilemaps[brHTMLNext.m_idSequence];
|
|
AppendSourceInfo(idEngine, pfilemap,
|
|
NULL, // Don't calculate line #
|
|
DIFF(brHTMLNext.m_pb - pfilemap->m_pbStartOfFile), // line offset
|
|
cbScriptBlockOffset,
|
|
*pcbDataOffset - cbScriptBlockOffset, // character offset in target script
|
|
CharAdvDBCS((WORD)m_wCodePage, // length of the segment
|
|
reinterpret_cast<char *>(brHTMLNext.m_pb),
|
|
reinterpret_cast<char *>(brHTMLNext.m_pb + brHTMLNext.m_cb),
|
|
INFINITE, NULL),
|
|
TRUE); // Line is HTML text
|
|
|
|
// Get block number as an sz
|
|
_itoa(cHTMLBlocksProcessed, szHTMLBlockID, 10);
|
|
// Write out write-block opener
|
|
WriteSzAsBytesAdv(WorkStore.m_szWriteBlockOpen, pcbDataOffset);
|
|
// Write out block number
|
|
WriteSzAsBytesAdv(szHTMLBlockID, pcbDataOffset);
|
|
// Write out write-block closer and newline
|
|
WriteSzAsBytesAdv(WorkStore.m_szWriteBlockClose, pcbDataOffset);
|
|
WriteSzAsBytesAdv(SZ_NEWLINE, pcbDataOffset);
|
|
|
|
if(++cHTMLBlocksProcessed >= cHTMLBlocks)
|
|
break;
|
|
|
|
// Get next HTML block
|
|
WorkStore.m_bufHTMLSegments.GetItem(cHTMLBlocksProcessed, brHTMLNext);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
// if primary script segment(s) remain to be processed ...
|
|
if(cScriptSegmentsProcessed < cPrimaryScriptSegments)
|
|
while (TRUE)
|
|
{
|
|
// Write out each primary script segment earlier in the source file than the next HTML block
|
|
if(brScriptNext.FEarlierInSourceThan(brHTMLNext) || (cHTMLBlocksProcessed >= cHTMLBlocks))
|
|
{
|
|
WriteScriptSegment(
|
|
idEngine,
|
|
m_rgpSegmentFilemaps[brScriptNext.m_idSequence],
|
|
brScriptNext,
|
|
pcbDataOffset,
|
|
cbScriptBlockOffset,
|
|
TRUE /* fAllowExprWrite - allowed for primary script */
|
|
);
|
|
|
|
if(++cScriptSegmentsProcessed >= cPrimaryScriptSegments)
|
|
break;
|
|
|
|
// Get next script segment
|
|
WorkStore.m_ScriptStore.m_ppbufSegments[0]->GetItem(cScriptSegmentsProcessed, brScriptNext);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::WriteScriptSegment
|
|
|
|
Writes a script segment to template memory line-by-line.
|
|
NOTE a 'script segment' is a piece (possibly all) of a 'script block'
|
|
|
|
Returns
|
|
nothing
|
|
Side effects
|
|
none
|
|
*/
|
|
void
|
|
CTemplate::WriteScriptSegment
|
|
(
|
|
USHORT idEngine, // script engine id
|
|
CFileMap* pfilemap, // ptr to source file map
|
|
CByteRange& brScript, // byte range containing script segment
|
|
UINT* pcbDataOffset, // ptr to write location offset value
|
|
UINT cbScriptBlockOffset,// ptr to beginning of the script text
|
|
BOOL fAllowExprWrite // allow short-hand expression write?
|
|
)
|
|
{
|
|
CByteRange brLine; // byte range containing next line
|
|
UINT cbPtrOffset = 0; // ptr offset - 0 tells WriteByteRangeAdv 'ignore this'
|
|
BOOL fExpression = FALSE; // is current line an expression?
|
|
BOOL fCalcLineNumber = TRUE; // calc source line number?
|
|
BOOL fFirstLine = TRUE; // first line in script segment?
|
|
|
|
if(FByteRangeIsWhiteSpace(brScript))
|
|
return;
|
|
|
|
// trim white space from beginning of script segment
|
|
if (FIsLangVBScriptOrJScript(idEngine))
|
|
LTrimWhiteSpace(brScript);
|
|
|
|
while(!(brScript.IsNull()))
|
|
{
|
|
// fetch next line from byte range
|
|
// NOTE LineFromByteRangeAdv advances through brScript until brScript is null
|
|
LineFromByteRangeAdv(brScript, brLine);
|
|
|
|
if(FByteRangeIsWhiteSpace(brLine))
|
|
{
|
|
// if line is blank, don't process it; simply force calc of line number on next non-blank line
|
|
fCalcLineNumber = TRUE;
|
|
continue;
|
|
}
|
|
|
|
// line is non-blank; trim its white space
|
|
if (FIsLangVBScriptOrJScript(idEngine))
|
|
LTrimWhiteSpace(brLine);
|
|
RTrimWhiteSpace(brLine);
|
|
|
|
// append source-info to array; if flag is set, calc line number
|
|
// from location in source file; else, simply increment previous line number (NULL indicates this)
|
|
AppendSourceInfo(idEngine, pfilemap,
|
|
fCalcLineNumber? brLine.m_pb : NULL, // info to calc line #
|
|
DIFF(brLine.m_pb - pfilemap->m_pbStartOfFile), // line offset
|
|
cbScriptBlockOffset,
|
|
*pcbDataOffset - cbScriptBlockOffset, // character offset in target script
|
|
CharAdvDBCS((WORD)m_wCodePage, // statement length
|
|
reinterpret_cast<char *>(brLine.m_pb),
|
|
reinterpret_cast<char *>(brLine.m_pb + brLine.m_cb),
|
|
INFINITE, NULL),
|
|
FALSE); // HTML?
|
|
|
|
/* if it's true, set calc-line-number flag false
|
|
NOTE this is purely an optimization, to make the call to AppendSourceInfo faster
|
|
on subsequent calls within a contiguous block of non-blank lines
|
|
*/
|
|
if(fCalcLineNumber)
|
|
fCalcLineNumber = FALSE;
|
|
|
|
if(fAllowExprWrite && fFirstLine)
|
|
{
|
|
// bug 912: test for remainder of script segment null on temp copy of script byte range, not on actual
|
|
CByteRange brTemp = brScript;
|
|
LTrimWhiteSpace(brTemp); // NOTE will nullify brScript if it is all white space
|
|
|
|
if(brTemp.IsNull())
|
|
{
|
|
/* if
|
|
a) expr-write is allowed AND
|
|
b) this is only script line in this segment (i.e. first line in segment and remainder of segment is null)
|
|
then, test this line to see if it is an expression.
|
|
NOTE test (b) fixes bug 785
|
|
|
|
if this line is an expression, create a script command that reads
|
|
Response.Write([line contents])
|
|
*/
|
|
if(fExpression = FExpression(brLine))
|
|
{
|
|
Assert(idEngine == 0); // =expr is only enabled for primary engine
|
|
WriteSzAsBytesAdv(m_pWorkStore->m_szWriteOpen, pcbDataOffset);
|
|
}
|
|
|
|
// in this case only, set actual script to (now null) temp copy, since brScript governs while loop termination
|
|
brScript = brTemp;
|
|
}
|
|
}
|
|
|
|
Assert(FImplies(fExpression, fFirstLine)); // if an expr, must be first line in segment
|
|
Assert(FImplies(fExpression, brScript.IsNull())); // if an expr, no more script lines remain
|
|
Assert(FImplies(!fFirstLine, !fExpression)); // if not first line in segment, line cannot be expr
|
|
Assert(FImplies(!brScript.IsNull(), !fExpression)); // if script lines remain, line cannot be expr
|
|
|
|
// write out line contents
|
|
WriteScriptMinusEscapeChars(brLine, pcbDataOffset, &cbPtrOffset);
|
|
|
|
// if this line is an expression, close script command
|
|
if(fExpression)
|
|
WriteSzAsBytesAdv(m_pWorkStore->m_szWriteClose, pcbDataOffset);
|
|
|
|
// write new-line and set first-line flag false
|
|
WriteSzAsBytesAdv(SZ_NEWLINE, pcbDataOffset);
|
|
fFirstLine = FALSE;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::WriteScriptMinusEscapeChars
|
|
Writes a script byte range to memory, minus its escape characters, if any.
|
|
|
|
Returns:
|
|
Nothing.
|
|
Side effects:
|
|
None.
|
|
*/
|
|
void
|
|
CTemplate::WriteScriptMinusEscapeChars
|
|
(
|
|
CByteRange brScript, // (ByVal) script byte range
|
|
UINT* pcbDataOffset, // offset where data will be written
|
|
UINT* pcbPtrOffset // offset where ptr will be written
|
|
)
|
|
{
|
|
BYTE* pbToken;
|
|
|
|
while(NULL != (pbToken = gm_pTokenList->GetToken(CTokenList::tknEscapedClosePrimaryScript, brScript, m_wCodePage)))
|
|
{
|
|
CByteRange brTemp = brScript;
|
|
|
|
// set temp range to source range up to escaped-token
|
|
brTemp.m_cb = DIFF(pbToken - brTemp.m_pb);
|
|
|
|
// write out temp range and actual-token - this replaces escaped-token with actual-token
|
|
WriteByteRangeAdv(brTemp, FALSE, pcbDataOffset, pcbPtrOffset);
|
|
WriteSzAsBytesAdv(SZ_TOKEN(CTokenList::tknClosePrimaryScript), pcbDataOffset);
|
|
|
|
//advance source range past escaped-token
|
|
brScript.Advance(DIFF(pbToken - brScript.m_pb) + CCH_TOKEN(CTokenList::tknEscapedClosePrimaryScript));
|
|
}
|
|
|
|
// write remainder of source range
|
|
WriteByteRangeAdv(brScript, FALSE, pcbDataOffset, pcbPtrOffset);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::FVbsComment
|
|
Determines whether a script line is a VBS comment.
|
|
NOTE caller must ensure that brLine is non-blank and has no leading white space
|
|
|
|
Returns
|
|
TRUE if the line is a VBS comment, else FALSE
|
|
Side effects
|
|
none
|
|
*/
|
|
BOOLB
|
|
CTemplate::FVbsComment(CByteRange& brLine)
|
|
{
|
|
// CONSIDER: SCRIPTLANG generic comment token
|
|
if(!_strnicmp((LPCSTR)brLine.m_pb, SZ_TOKEN(CTokenList::tknVBSCommentSQuote), CCH_TOKEN(CTokenList::tknVBSCommentSQuote)))
|
|
return TRUE;
|
|
if(!_strnicmp((LPCSTR)brLine.m_pb, SZ_TOKEN(CTokenList::tknVBSCommentRem), CCH_TOKEN(CTokenList::tknVBSCommentRem)))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::FExpression
|
|
|
|
Determines whether a script line is an expression, and if so returns
|
|
just the expression in brLine.
|
|
NOTE caller must ensure that brLine has no leading white space
|
|
|
|
Returns
|
|
TRUE if the line is an expression, else FALSE
|
|
Side effects
|
|
none
|
|
*/
|
|
BOOLB
|
|
CTemplate::FExpression(CByteRange& brLine)
|
|
{
|
|
// may be whitespace (other languages besides VB & JScript will have whitespace)
|
|
char *pchLine = reinterpret_cast<char *>(brLine.m_pb);
|
|
int cchLine = brLine.m_cb;
|
|
|
|
while (cchLine > 0 && FWhiteSpace(*pchLine))
|
|
{
|
|
--cchLine;
|
|
++pchLine;
|
|
}
|
|
|
|
// if line starts with =, it is an expression: bypass =, left-trim whitespace and return true
|
|
if(cchLine > 0 && *pchLine == '=')
|
|
{
|
|
brLine.Advance(1 + DIFF(reinterpret_cast<BYTE *>(pchLine) - brLine.m_pb)); // OK to advance past whitespace now.
|
|
LTrimWhiteSpace(brLine);
|
|
return TRUE;
|
|
}
|
|
|
|
// else return false
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
** In the following function names:
|
|
** 'Adv' == 'advance offset after writing'
|
|
**/
|
|
|
|
/* ============================================================================
|
|
CTemplate::WriteOffsetToOffset
|
|
|
|
Writes a offset-to-offset offset (0 if no blocks) into header,
|
|
and advances header offset and offset-to-offset.
|
|
|
|
Returns:
|
|
Nothing.
|
|
Side effects:
|
|
Advances offsets.
|
|
*/
|
|
void
|
|
CTemplate::WriteOffsetToOffset
|
|
(
|
|
USHORT cBlocks, // count of blocks
|
|
UINT* pcbHeaderOffset, // ptr to header offset value
|
|
UINT* pcbOffsetToOffset // ptr to offset-to-offset value
|
|
)
|
|
{
|
|
// if blocks of this type, write offset to first of them into header;
|
|
// if no blocks of this type, write 0 into header
|
|
WriteLongAdv((cBlocks > 0) ? *pcbOffsetToOffset : 0, pcbHeaderOffset);
|
|
|
|
// advance offset-to-offset offset
|
|
*pcbOffsetToOffset += cBlocks * sizeof(ULONG);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::WriteSzAsBytesAdv
|
|
|
|
Writes a null-terminated string as bytes, i.e. without its null terminator
|
|
and advances offset
|
|
|
|
Returns:
|
|
Nothing.
|
|
Side effects:
|
|
Advances offset.
|
|
*/
|
|
void
|
|
CTemplate::WriteSzAsBytesAdv
|
|
(
|
|
LPCSTR szSource, // source string
|
|
UINT* pcbDataOffset // ptr to offset value
|
|
)
|
|
{
|
|
if((szSource == NULL) || (*szSource == '\0'))
|
|
return;
|
|
MemCpyAdv(pcbDataOffset, (void*) szSource, strlen(szSource));
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::WriteByteRangeAdv
|
|
|
|
Writes a byte range to memory at template offset location *pcbDataOffset and, optionally,
|
|
writes a ptr to the written data at template offset location *pcbPtrOffset
|
|
(pass *pcbPtrOffset == 0 to avoid this)
|
|
|
|
fWriteAsBsz == FALSE --> write only byte range's data
|
|
fWriteAsBsz == TRUE --> write length, followed by data, followed by NULL
|
|
NOTE bsz == length-prefixed, null-terminated string
|
|
|
|
Returns:
|
|
Nothing.
|
|
Side effects:
|
|
Advances offset(s).
|
|
*/
|
|
void
|
|
CTemplate::WriteByteRangeAdv
|
|
(
|
|
CByteRange& brSource, // source data
|
|
BOOLB fWriteAsBsz, // write as bsz?
|
|
UINT* pcbDataOffset, // offset where data will be written
|
|
UINT* pcbPtrOffset // offset where ptr will be written
|
|
)
|
|
{
|
|
// bail if source is empty
|
|
if(brSource.IsNull())
|
|
return;
|
|
|
|
// If writing as a bsz, write length prefix
|
|
if(fWriteAsBsz)
|
|
WriteLongAdv(brSource.m_cb, pcbDataOffset);
|
|
|
|
// Write data
|
|
MemCpyAdv(pcbDataOffset, brSource.m_pb, brSource.m_cb);
|
|
|
|
// If writing as a bsz, write null terminator and advance target ptr
|
|
if(fWriteAsBsz)
|
|
MemCpyAdv(pcbDataOffset, SZ_NULL, 1);
|
|
|
|
// If caller passed a non-zero ptr offset, write offset to data there
|
|
if(*pcbPtrOffset > 0)
|
|
{
|
|
if(fWriteAsBsz)
|
|
/* if writing as a bsz ...
|
|
offset to start of data == current data offset
|
|
- null terminator
|
|
- data length
|
|
- sizeof length prefix
|
|
*/
|
|
WriteLongAdv(*pcbDataOffset - 1 - brSource.m_cb - sizeof(brSource.m_cb), pcbPtrOffset);
|
|
else
|
|
// else, offset to start of data == current data offset - data length
|
|
WriteLongAdv(*pcbDataOffset - brSource.m_cb, pcbPtrOffset);
|
|
}
|
|
}
|
|
|
|
/*===================================================================
|
|
CTemplate::MemCpyAdv
|
|
|
|
Copies from a memory location to a template offset location,
|
|
and advances offset.
|
|
|
|
Returns:
|
|
Nothing.
|
|
Side effects:
|
|
Advances offset.
|
|
Re-allocates memory if required.
|
|
*/
|
|
void
|
|
CTemplate::MemCpyAdv
|
|
(
|
|
UINT* pcbOffset, // ptr to offset value
|
|
void* pbSource, // ptr to source
|
|
ULONG cbSource, // length of source
|
|
UINT cbByteAlign // align bytes on short/long/dword boundary?
|
|
)
|
|
{
|
|
// byte-align offset location before write, if specified by caller
|
|
if(cbByteAlign > 0)
|
|
ByteAlignOffset(pcbOffset, cbByteAlign);
|
|
|
|
// calc number of bytes by which to grow allocated template memory:
|
|
// if projected reach exceeds current reach, we need to grow by the difference;
|
|
// else, no need to grow
|
|
if((*pcbOffset + cbSource) > m_cbTemplate)
|
|
{
|
|
// Reallocate space for storing local data - we grab twice what we had before
|
|
// or twice current growth requirement, whichever is more
|
|
m_cbTemplate = 2 * max(m_cbTemplate, (*pcbOffset + cbSource) - m_cbTemplate);
|
|
if(NULL == (m_pbStart = (BYTE*) CTemplate::LargeReAlloc(m_pbStart, m_cbTemplate)))
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
|
|
// copy source to template offset location
|
|
memcpy(m_pbStart + *pcbOffset, pbSource, cbSource);
|
|
// advance offset location
|
|
*pcbOffset += cbSource;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetAddress
|
|
Returns a ptr to the i-th object of type tcomp
|
|
*/
|
|
BYTE*
|
|
CTemplate::GetAddress
|
|
(
|
|
TEMPLATE_COMPONENT tcomp,
|
|
USHORT i
|
|
)
|
|
{
|
|
DWORD* pdwBase;
|
|
|
|
Assert(NULL != m_pbStart);
|
|
|
|
// refer to CTemplate::WriteTemplate comments for the structure of what this is dealing with
|
|
|
|
pdwBase = (DWORD*)(m_pbStart + (C_COUNTS_IN_HEADER * sizeof(USHORT)));
|
|
|
|
// tcomp types are ptr-to-ptrs
|
|
DWORD* pdwTcompBase = (DWORD *) (m_pbStart + pdwBase[tcomp]);
|
|
|
|
return m_pbStart + pdwTcompBase[i];
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::AppendSourceInfo
|
|
Appends a source line number for the current target line
|
|
NOTE if caller passes null source ptr, we append prev source line number + 1
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
allocates memory first time thru; may realloc
|
|
*/
|
|
void
|
|
CTemplate::AppendSourceInfo
|
|
(
|
|
USHORT idEngine, // script engine id
|
|
CFileMap* pfilemap, // ptr to source file map
|
|
BYTE* pbSource, // ptr to current location in source file
|
|
ULONG cbSourceOffset, // byte offset of line in source file
|
|
ULONG cbScriptBlockOffset, // pointer to start of script text
|
|
ULONG cbTargetOffset, // character offset of line in target file
|
|
ULONG cchSourceText, // # of characters in source text
|
|
BOOL fIsHTML // TRUE if manufactured line
|
|
)
|
|
{
|
|
UINT i; // loop index
|
|
CSourceInfo si; // temporary CSourceInfo structure
|
|
vector<CSourceInfo> *prgSourceInfos; // pointer to line mapping table for the engine
|
|
ULONG cchSourceOffset = 0;// cch corresponding to cbSourceOffset
|
|
HRESULT hr = S_OK;
|
|
|
|
// if arrays are not yet allocated, allocate them
|
|
if (m_rgrgSourceInfos == NULL)
|
|
{
|
|
// transfer count of script engines from workstore to template
|
|
m_cScriptEngines = m_pWorkStore->CRequiredScriptEngines(m_fGlobalAsa);
|
|
|
|
// one source-info array per engine
|
|
if ((m_rgrgSourceInfos = new vector<CSourceInfo>[m_cScriptEngines]) == NULL)
|
|
THROW (E_OUTOFMEMORY);
|
|
}
|
|
|
|
// new script engine must be allocated in IdEngineFromBr (way upstream of this point),
|
|
// so we assert that current engine must already be covered
|
|
Assert(idEngine < m_pWorkStore->CRequiredScriptEngines(m_fGlobalAsa));
|
|
|
|
/* set current target line's source line number (SLN):
|
|
a) if caller passed a source ptr, calc SLN from the source ptr;
|
|
b) else if caller passed a filemap ptr, set SLN to prev target line's SLN plus one;
|
|
c) else set SLN to 0
|
|
|
|
semantics:
|
|
a) we have a source file location, but must calc a line # for that location
|
|
b) caller tells us (by passing NULL source file location) that this target line
|
|
immediately follows prev target line. This is an optimization because
|
|
SourceLineNumberFromPb is very slow.
|
|
|
|
change:
|
|
caller used to pass NULL filemap ptr that target line is 'manufactured'
|
|
i.e. has no corresponding authored line in source file
|
|
|
|
HOWEVER - now filemap ptr must NOT be NULL because 'manufactured' lines
|
|
are also stored in the file map array
|
|
*/
|
|
|
|
Assert (pfilemap != NULL);
|
|
|
|
prgSourceInfos = &m_rgrgSourceInfos[idEngine];
|
|
|
|
if (pbSource == NULL)
|
|
{
|
|
if (prgSourceInfos->length() == 0)
|
|
si.m_idLine = 1;
|
|
else
|
|
si.m_idLine = (*prgSourceInfos)[prgSourceInfos->length() - 1].m_idLine + 1;
|
|
}
|
|
else
|
|
si.m_idLine = SourceLineNumberFromPb(pfilemap, pbSource);
|
|
|
|
// The EOF line does not have a source offset (caller passes -1 (UINT_MAX)). For this case, no
|
|
// DBCS calculations etc. should be done. (set cchSourceOffset to UINT_MAX).
|
|
if (cbSourceOffset == UINT_MAX)
|
|
cchSourceOffset = UINT_MAX;
|
|
else
|
|
{
|
|
// BUG 80901: Source offset needs to point to the beginning of leading white space on the line
|
|
// Adjust source length by one as we decrement source offset
|
|
// Note: whitepsace is never trailing byte, so loop will work with DBCS encoded character sets
|
|
while (cbSourceOffset > 0 && strchr(" \t\v\a\f", pfilemap->m_pbStartOfFile[cbSourceOffset - 1]))
|
|
{
|
|
--cbSourceOffset;
|
|
++cchSourceText;
|
|
}
|
|
|
|
// BUG 95859
|
|
// If the cursor is on the opening token of a script block (the "<%" part of a line), the
|
|
// BP is set in the previous HTML, not in the script block, as is desired.
|
|
//
|
|
// To correct this, if we are in a script block, scan back two characters, see if it is the open
|
|
// token. If it is, set the offset back two, and add two to the length.
|
|
//
|
|
if (!fIsHTML)
|
|
{
|
|
// Skip whitespace (including newlines -- the previous step did not skip newlines)
|
|
//
|
|
ULONG cbOpen = cbSourceOffset;
|
|
while (cbOpen > 0 && strchr(" \t\v\a\f\r\n", pfilemap->m_pbStartOfFile[cbOpen - 1]))
|
|
--cbOpen;
|
|
|
|
if (cbOpen >= 2 && strncmp(reinterpret_cast<char *>(&pfilemap->m_pbStartOfFile[cbOpen - 2]), "<%", 2) == 0)
|
|
{
|
|
cbOpen -= 2;
|
|
cchSourceText += cbSourceOffset - cbOpen;
|
|
cbSourceOffset = cbOpen;
|
|
}
|
|
|
|
// Look for trailing "%>" in this snippet, and if it exists then include the end delimiter in
|
|
// the length. NOTE: No DBCS awareness needed here - if we find a lead byte we just get out
|
|
// of the loop. We are looking for <whitespace>*"%>" which is totally SBCS chars.
|
|
//
|
|
ULONG cbClose = cbSourceOffset + cchSourceText;
|
|
ULONG cbFile = pfilemap->GetSize();
|
|
while (cbClose < cbFile && strchr(" \t\v\a\f\r\n", pfilemap->m_pbStartOfFile[cbClose]))
|
|
++cbClose;
|
|
|
|
if (cbClose < cbFile && strncmp(reinterpret_cast<char *>(&pfilemap->m_pbStartOfFile[cbClose]), "%>", 2) == 0)
|
|
cchSourceText += cbClose - (cbSourceOffset + cchSourceText) + 2;
|
|
}
|
|
|
|
// BUG 82222, 85584
|
|
// Compiler marks HTML segments starting with the newline on the previous line
|
|
// if the line ends with %>.
|
|
//
|
|
// This screws up the debugger, becasue when you press <F9>, the pointer is placed
|
|
// on the line above when it should point to the start of the whitespace on the next line.
|
|
if (fIsHTML)
|
|
{
|
|
UINT cbEOF = pfilemap->GetSize(), cbRover = cbSourceOffset;
|
|
|
|
// Skip initial whitespace
|
|
while (cbRover < cbEOF && strchr(" \t\a\f", pfilemap->m_pbStartOfFile[cbRover]))
|
|
++cbRover;
|
|
|
|
// If what's left is a CR/LF pair, then advance cbSourceOffset to next line
|
|
BOOL fCR = FALSE, fLF = FALSE;
|
|
if (cbRover < cbEOF && strchr("\r\n", pfilemap->m_pbStartOfFile[cbRover]))
|
|
{
|
|
fCR = pfilemap->m_pbStartOfFile[cbRover] == '\r';
|
|
fLF = pfilemap->m_pbStartOfFile[cbRover] == '\n';
|
|
|
|
++cbRover;
|
|
Assert (fCR || fLF);
|
|
}
|
|
|
|
// we allow either <CR>, <LF>, <CR><LF>, or <LF><CR> to terminate a line,
|
|
// so look for its opposite terminator if one is found (but don't require it)
|
|
|
|
if (fCR && cbRover < cbEOF && pfilemap->m_pbStartOfFile[cbRover] == '\n')
|
|
++cbRover;
|
|
|
|
if (fLF && cbRover < cbEOF && pfilemap->m_pbStartOfFile[cbRover] == '\r')
|
|
++cbRover;
|
|
|
|
// OK, adjust cbSourceOffset now
|
|
|
|
if ((fCR || fLF) && cbRover < cbEOF)
|
|
{
|
|
cchSourceText -= cbRover - cbSourceOffset; // adjust # of chars to select
|
|
cbSourceOffset = cbRover;
|
|
}
|
|
}
|
|
|
|
// Now that we have the source offset, calculate its CCH by finding
|
|
// the last time we sampled the value, then add that to the number
|
|
// of DBCS characters from that point to the current offset.
|
|
//
|
|
// For the case of includes, it's possible offset already exists
|
|
// (if the entry was previously generated by another instance of
|
|
// #include - therefore we have to search)
|
|
|
|
COffsetInfo *pOffsetInfoLE, *pOffsetInfoGE;
|
|
GetBracketingPair(
|
|
cbSourceOffset, // value to find
|
|
pfilemap->m_rgByte2DBCS.begin(), // beginning of array
|
|
pfilemap->m_rgByte2DBCS.end(), // end of array
|
|
CByteOffsetOrder(), // search for byte offset
|
|
&pOffsetInfoLE, &pOffsetInfoGE // return values
|
|
);
|
|
|
|
// If we find an equal match, don't insert any duplicates
|
|
if (pOffsetInfoLE == NULL || pOffsetInfoLE->m_cbOffset < cbSourceOffset)
|
|
{
|
|
// if pOffsetInfoLE is NULL, it means that the array is empty -
|
|
// create the mapping of offset 0 to offset 0.
|
|
//
|
|
// In the case of the first line of a file being an include directive,
|
|
// the first executable line from the file may not start at offset zero,
|
|
// so in this case we need to create this entry AND execute the next "if"
|
|
// block.
|
|
//
|
|
if (pOffsetInfoLE == NULL)
|
|
{
|
|
COffsetInfo oiZero; // ctor will init
|
|
if (FAILED(hr = pfilemap->m_rgByte2DBCS.append(oiZero)))
|
|
THROW(hr);
|
|
pOffsetInfoLE = pfilemap->m_rgByte2DBCS.begin();
|
|
Assert (pOffsetInfoLE != NULL);
|
|
}
|
|
|
|
// If cbSourceOffset is zero, we handled it above
|
|
if (cbSourceOffset != 0)
|
|
{
|
|
cchSourceOffset = pOffsetInfoLE->m_cchOffset +
|
|
CharAdvDBCS
|
|
(
|
|
(WORD)m_wCodePage,
|
|
reinterpret_cast<char *>(pfilemap->m_pbStartOfFile + pOffsetInfoLE->m_cbOffset),
|
|
reinterpret_cast<char *>(pfilemap->m_pbStartOfFile + cbSourceOffset),
|
|
INFINITE,
|
|
NULL
|
|
);
|
|
|
|
// Now add the value to the table
|
|
COffsetInfo oi;
|
|
|
|
oi.m_cchOffset = cchSourceOffset;
|
|
oi.m_cbOffset = cbSourceOffset;
|
|
|
|
if (pOffsetInfoGE == NULL) // No offset greater
|
|
hr = pfilemap->m_rgByte2DBCS.append(oi);
|
|
else
|
|
hr = pfilemap->m_rgByte2DBCS.insertAt(DIFF(pOffsetInfoGE - pfilemap->m_rgByte2DBCS.begin()), oi);
|
|
|
|
if (FAILED(hr))
|
|
THROW(hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we're not adding anything for the table, Assert it's because there's
|
|
// a duplicate item
|
|
Assert (cbSourceOffset == pOffsetInfoLE->m_cbOffset);
|
|
cchSourceOffset = pOffsetInfoLE->m_cchOffset;
|
|
}
|
|
}
|
|
|
|
UINT cchTargetOffset = UINT_MAX;
|
|
if (cbTargetOffset != UINT_MAX)
|
|
{
|
|
// ptr to start of script is:
|
|
// ptr start of template + offset to script + size of script length
|
|
LPSTR szScript = (LPSTR) m_pbStart + cbScriptBlockOffset;
|
|
|
|
// Calculate cchTargetOffset (have the cb). The cch is the number of characters since the
|
|
// last cch calculated in the end of the array.
|
|
//
|
|
if (prgSourceInfos->length() > 0)
|
|
cchTargetOffset = (*prgSourceInfos)[prgSourceInfos->length() - 1].m_cchTargetOffset;
|
|
else
|
|
cchTargetOffset = 0;
|
|
|
|
cchTargetOffset += CharAdvDBCS
|
|
(
|
|
(WORD) m_wCodePage,
|
|
&szScript[m_cbTargetOffsetPrevT],
|
|
&szScript[cbTargetOffset],
|
|
INFINITE,
|
|
NULL
|
|
);
|
|
|
|
// Keeps track of offsets during compilation
|
|
//
|
|
m_cbTargetOffsetPrevT = cbTargetOffset;
|
|
}
|
|
|
|
// Store this record and move on.
|
|
//
|
|
si.m_pfilemap = pfilemap;
|
|
si.m_fIsHTML = fIsHTML;
|
|
si.m_cchSourceOffset = cchSourceOffset;
|
|
si.m_cchTargetOffset = cchTargetOffset;
|
|
si.m_cchSourceText = cchSourceText;
|
|
|
|
if (FAILED(prgSourceInfos->append(si)))
|
|
THROW(hr);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::SourceLineNumberFromPb
|
|
Returns the starting source line number for the given source file location
|
|
*/
|
|
UINT
|
|
CTemplate::SourceLineNumberFromPb
|
|
(
|
|
CFileMap* pfilemap, // ptr to source file map
|
|
BYTE* pbSource // ptr to current location in source file
|
|
)
|
|
{
|
|
UINT cSourceLines = 1; // count of lines into source file
|
|
CByteRange brScan; // byte range to scan for newlines
|
|
CByteRange brSOL; // start-of-line ptr
|
|
|
|
if(pbSource == NULL || pfilemap == NULL)
|
|
return 0;
|
|
|
|
// Determine if there was a state stored on this ASP script earlier. If yes, then restore state.
|
|
// If the line being requested is prior to the saved state...Bail out and restart from the begining.
|
|
if(m_pWorkStore->m_cPrevSourceLines &&
|
|
(m_pWorkStore->m_pbPrevSource && (m_pWorkStore->m_pbPrevSource < pbSource)) &&
|
|
(m_pWorkStore->m_hPrevFile && (pfilemap->m_hFile==m_pWorkStore->m_hPrevFile)))
|
|
{
|
|
// The file handles match:with means that we are evaluating the current file. Restore state.
|
|
brScan.m_pb = m_pWorkStore->m_pbPrevSource;
|
|
brScan.m_cb = max(DIFF(pbSource - brScan.m_pb), 0);
|
|
cSourceLines = m_pWorkStore->m_cPrevSourceLines;
|
|
}
|
|
else
|
|
{
|
|
// set scan range to run from start-of-template to caller's ptr
|
|
brScan.m_pb = pfilemap->m_pbStartOfFile;
|
|
brScan.m_cb = max(DIFF(pbSource - brScan.m_pb), 0);
|
|
}
|
|
|
|
// get newlines in scan range
|
|
brSOL = BrNewLine(brScan);
|
|
|
|
while(!brSOL.IsNull())
|
|
{
|
|
// advance start-of-line ptr and scan byte range
|
|
brScan.Advance(DIFF((brSOL.m_pb + brSOL.m_cb) - brScan.m_pb));
|
|
|
|
// increment source line counter
|
|
cSourceLines++;
|
|
|
|
// find next newline
|
|
brSOL = BrNewLine(brScan);
|
|
}
|
|
|
|
// Store the state for the next call.
|
|
m_pWorkStore->m_pbPrevSource = pbSource;
|
|
m_pWorkStore->m_cPrevSourceLines = cSourceLines;
|
|
m_pWorkStore->m_hPrevFile = pfilemap->m_hFile;
|
|
|
|
return cSourceLines;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::RemoveFromIncFiles
|
|
Removes this template from inc-files on which it depends
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::RemoveFromIncFiles
|
|
(
|
|
)
|
|
{
|
|
// NOTE we loop from 1 to count, since 0-th filemap is for main file
|
|
for(UINT i = 1; i < m_cFilemaps; i++)
|
|
{
|
|
if(NULL != m_rgpFilemaps[i]->m_pIncFile)
|
|
m_rgpFilemaps[i]->m_pIncFile->RemoveTemplate(this);
|
|
}
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
IDebugDocumentProvider implementation
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetDocument
|
|
Return a pointer to the IDebugDocument implementation. (same object in this case)
|
|
|
|
Returns:
|
|
*ppDebugDoc is set to "this".
|
|
Notes:
|
|
always succeeds
|
|
*/
|
|
HRESULT CTemplate::GetDocument
|
|
(
|
|
IDebugDocument **ppDebugDoc
|
|
)
|
|
{
|
|
return QueryInterface(IID_IDebugDocument, reinterpret_cast<void **>(ppDebugDoc));
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetName
|
|
Return the various names of a document.
|
|
*/
|
|
|
|
HRESULT CTemplate::GetName
|
|
(
|
|
/* [in] */ DOCUMENTNAMETYPE doctype,
|
|
/* [out] */ BSTR *pbstrName
|
|
)
|
|
{
|
|
TCHAR *szPathInfo = m_rgpFilemaps[0]->m_szPathInfo;
|
|
switch (doctype) {
|
|
case DOCUMENTNAMETYPE_APPNODE:
|
|
case DOCUMENTNAMETYPE_FILE_TAIL:
|
|
case DOCUMENTNAMETYPE_TITLE:
|
|
// Skip application path portion of the filename
|
|
{
|
|
// Make sure the template remembers the virtual path
|
|
// from the same application (it could be different
|
|
// if template is shared between two applications)
|
|
//
|
|
int cch = _tcslen(m_szApplnVirtPath);
|
|
if (_tcsncicmp(szPathInfo, m_szApplnVirtPath, cch) == 0)
|
|
szPathInfo += cch;
|
|
|
|
// Strip leading '/'
|
|
if (*szPathInfo == _T('/'))
|
|
szPathInfo++;
|
|
#if UNICODE
|
|
*pbstrName = SysAllocString(szPathInfo);
|
|
if (*pbstrName == NULL)
|
|
return E_OUTOFMEMORY;
|
|
return S_OK;
|
|
#else
|
|
return SysAllocStringFromSz(szPathInfo, 0, pbstrName, m_wCodePage);
|
|
#endif
|
|
}
|
|
|
|
case DOCUMENTNAMETYPE_URL:
|
|
// prefix with the URL, use szPathInfo for the rest of the path
|
|
{
|
|
STACK_BUFFER( tempName, MAX_PATH );
|
|
|
|
int cbURLPrefix = DIFF(m_szApplnVirtPath - m_szApplnURL) * sizeof (TCHAR);
|
|
if (!tempName.Resize(cbURLPrefix + (_tcslen(szPathInfo)*sizeof(TCHAR)) + sizeof(TCHAR))) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
TCHAR *szURL = (TCHAR *)tempName.QueryPtr();
|
|
|
|
memcpy(szURL, m_szApplnURL, cbURLPrefix);
|
|
_tcscpy(&szURL[cbURLPrefix/sizeof(TCHAR)], szPathInfo);
|
|
|
|
#if UNICODE
|
|
*pbstrName = SysAllocString(szURL);
|
|
if (*pbstrName == NULL)
|
|
return E_OUTOFMEMORY;
|
|
return S_OK;
|
|
#else
|
|
return SysAllocStringFromSz(szURL, 0, pbstrName, m_wCodePage);
|
|
#endif
|
|
}
|
|
|
|
default:
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
IDebugDocumentText implementation
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetSize
|
|
Return the number of lines & characters in the document
|
|
*/
|
|
HRESULT CTemplate::GetSize
|
|
(
|
|
/* [out] */ ULONG *pcLines,
|
|
/* [out] */ ULONG *pcChars
|
|
)
|
|
{
|
|
/*
|
|
* NOTE: compilation is done in two phases.
|
|
* Errors are detected and reported in phase 1.
|
|
* The DBCS mapping is created in phase 2.
|
|
*
|
|
* If an error occurred during compilation, m_cChars will be equal to zero
|
|
* (Since zero length files are not compiled, m_cChars == 0 means "size
|
|
* is unknown", not "size is zero").
|
|
*/
|
|
if (m_rgpFilemaps[0]->m_cChars == 0)
|
|
{
|
|
// Likely need to remap the file, then count
|
|
BOOL fRemapTemplate = !m_rgpFilemaps[0]->FIsMapped();
|
|
if (fRemapTemplate)
|
|
TRY
|
|
m_rgpFilemaps[0]->RemapFile();
|
|
CATCH (dwException)
|
|
return E_FAIL;
|
|
END_TRY
|
|
|
|
m_rgpFilemaps[0]->CountChars((WORD)m_wCodePage);
|
|
|
|
if (fRemapTemplate)
|
|
TRY
|
|
m_rgpFilemaps[0]->UnmapFile();
|
|
CATCH (dwException)
|
|
return E_FAIL;
|
|
END_TRY
|
|
|
|
// let's hope client is not relying on # of lines - expensive to compute
|
|
|
|
*pcChars = m_rgpFilemaps[0]->m_cChars;
|
|
*pcLines = ULONG_MAX;
|
|
}
|
|
else
|
|
{
|
|
/* The last line in the line mapping array of each engine is the <<EOF>> line
|
|
* for that engine. Therefore, the # of lines is the largest <<EOF>> line
|
|
* number - 1. The EOF line always points into the main file, so there are no
|
|
* include file glitches here.
|
|
*/
|
|
ULONG cLinesMax = 0;
|
|
for (UINT i = 0; i < m_cScriptEngines; ++i)
|
|
{
|
|
ULONG cLinesCurrentEngine = m_rgrgSourceInfos[0][m_rgrgSourceInfos[0].length() - 1].m_idLine - 1;
|
|
if (cLinesCurrentEngine > cLinesMax)
|
|
cLinesMax = cLinesCurrentEngine;
|
|
}
|
|
|
|
*pcLines = cLinesMax;
|
|
*pcChars = m_rgpFilemaps[0]->m_cChars;
|
|
}
|
|
|
|
IF_DEBUG(SCRIPT_DEBUGGER) {
|
|
#if UNICODE
|
|
DBGPRINTF((DBG_CONTEXT, "GetSize(\"%S\") returns %lu characters (%lu lines)\n", m_rgpFilemaps[0]->m_szPathTranslated, *pcChars, *pcLines));
|
|
#else
|
|
DBGPRINTF((DBG_CONTEXT, "GetSize(\"%s\") returns %lu characters (%lu lines)\n", m_rgpFilemaps[0]->m_szPathTranslated, *pcChars, *pcLines));
|
|
#endif
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetDocumentAttributes
|
|
Return doc attributes
|
|
*/
|
|
HRESULT CTemplate::GetDocumentAttributes
|
|
(
|
|
/* [out] */ TEXT_DOC_ATTR *ptextdocattr
|
|
)
|
|
{
|
|
// Easy way to tell debugger that we don't support editing.
|
|
*ptextdocattr = TEXT_DOC_ATTR_READONLY;
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetPositionOfLine
|
|
From a line number, return the character offset of the beginning
|
|
*/
|
|
HRESULT CTemplate::GetPositionOfLine
|
|
(
|
|
/* [in] */ ULONG cLineNumber,
|
|
/* [out] */ ULONG *pcCharacterPosition
|
|
)
|
|
{
|
|
return GetPositionOfLine(m_rgpFilemaps[0], cLineNumber, pcCharacterPosition);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetLineOfPosition
|
|
From a character offset, return the line number and offset within the line
|
|
*/
|
|
HRESULT CTemplate::GetLineOfPosition
|
|
(
|
|
/* [in] */ ULONG cCharacterPosition,
|
|
/* [out] */ ULONG *pcLineNumber,
|
|
/* [out] */ ULONG *pcCharacterOffsetInLine
|
|
)
|
|
{
|
|
return GetLineOfPosition(m_rgpFilemaps[0], cCharacterPosition, pcLineNumber, pcCharacterOffsetInLine);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetText
|
|
From a character offset and length, return the document text
|
|
*/
|
|
HRESULT CTemplate::GetText
|
|
(
|
|
ULONG cchSourceOffset,
|
|
WCHAR *pwchText,
|
|
SOURCE_TEXT_ATTR *pTextAttr,
|
|
ULONG *pcChars,
|
|
ULONG cMaxChars
|
|
)
|
|
{
|
|
return m_rgpFilemaps[0]->GetText((WORD)m_wCodePage, cchSourceOffset, pwchText, pTextAttr, pcChars, cMaxChars);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetPositionOfContext
|
|
Decompose a document context into the document offset & length
|
|
*/
|
|
HRESULT CTemplate::GetPositionOfContext
|
|
(
|
|
/* [in] */ IDebugDocumentContext *pUnknownDocumentContext,
|
|
/* [out] */ ULONG *pcchSourceOffset,
|
|
/* [out] */ ULONG *pcchText
|
|
)
|
|
{
|
|
// Make sure that the context is one of ours
|
|
CTemplateDocumentContext *pDocumentContext;
|
|
if (FAILED(pUnknownDocumentContext->QueryInterface(IID_IDenaliTemplateDocumentContext, reinterpret_cast<void **>(&pDocumentContext))))
|
|
return E_FAIL;
|
|
|
|
if (pcchSourceOffset)
|
|
*pcchSourceOffset = pDocumentContext->m_cchSourceOffset;
|
|
|
|
if (pcchText)
|
|
*pcchText = pDocumentContext->m_cchText;
|
|
|
|
pDocumentContext->Release();
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetContextOfPosition
|
|
Given the character position & number of characters in the document,
|
|
encapsulate this into a document context object.
|
|
*/
|
|
HRESULT CTemplate::GetContextOfPosition
|
|
(
|
|
/* [in] */ ULONG cchSourceOffset,
|
|
/* [in] */ ULONG cchText,
|
|
/* [out] */ IDebugDocumentContext **ppDocumentContext
|
|
)
|
|
{
|
|
if (
|
|
(*ppDocumentContext = new CTemplateDocumentContext(this, cchSourceOffset, cchText))
|
|
== NULL
|
|
)
|
|
return E_OUTOFMEMORY;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
IConnectionPointContainer implementation
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CTemplate::FindConnectionPoint
|
|
From a character offset and length, return the document text
|
|
*/
|
|
HRESULT CTemplate::FindConnectionPoint
|
|
(
|
|
const GUID &uidConnection,
|
|
IConnectionPoint **ppCP
|
|
)
|
|
{
|
|
if (uidConnection == IID_IDebugDocumentTextEvents)
|
|
return m_CPTextEvents.QueryInterface(IID_IConnectionPoint, reinterpret_cast<void **>(ppCP));
|
|
else
|
|
{
|
|
*ppCP = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::AttachTo
|
|
attach this to the debugger UI tree view.
|
|
*/
|
|
HRESULT CTemplate::AttachTo
|
|
(
|
|
CAppln *pAppln
|
|
)
|
|
{
|
|
if (!m_fDontAttach && pAppln->FDebuggable())
|
|
{
|
|
// If we are already attached to this application, then ignore 2nd request
|
|
CDblLink *pNodeCurr = m_listDocNodes.PNext();
|
|
while (pNodeCurr != &m_listDocNodes)
|
|
{
|
|
if (pAppln == static_cast<CDocNodeElem *>(pNodeCurr)->m_pAppln)
|
|
return S_OK;
|
|
|
|
pNodeCurr = pNodeCurr->PNext();
|
|
}
|
|
|
|
// Create the node and store it in the linked list.
|
|
HRESULT hr;
|
|
IDebugApplicationNode *pDocRoot;
|
|
CDocNodeElem *pDocNodeElem;
|
|
|
|
// Create a document tree, showing the include file hierarchy
|
|
if (FAILED(hr = CreateDocumentTree(m_rgpFilemaps[0], &pDocRoot)))
|
|
return hr;
|
|
|
|
if (FAILED(hr = pDocRoot->Attach(pAppln->PAppRoot())))
|
|
return hr;
|
|
|
|
if ((pDocNodeElem = new CDocNodeElem(pAppln, pDocRoot)) == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
pDocNodeElem->AppendTo(m_listDocNodes);
|
|
pDocRoot->Release();
|
|
m_fDebuggable = TRUE;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::DetachFrom
|
|
detach this from the debugger UI tree view.
|
|
*/
|
|
HRESULT CTemplate::DetachFrom
|
|
(
|
|
CAppln *pAppln
|
|
)
|
|
{
|
|
// Enter the CS to prevent Detach() from detaching while we are scanning
|
|
// the list (causes application ptr to be deleted twice if this occurs)
|
|
DBG_ASSERT(m_fDebuggerDetachCSInited);
|
|
EnterCriticalSection(&m_csDebuggerDetach);
|
|
|
|
// Look for the node that has this application
|
|
CDblLink *pNodeCurr = m_listDocNodes.PNext();
|
|
while (pNodeCurr != &m_listDocNodes)
|
|
{
|
|
if (pAppln == static_cast<CDocNodeElem *>(pNodeCurr)->m_pAppln)
|
|
break;
|
|
|
|
pNodeCurr = pNodeCurr->PNext();
|
|
}
|
|
|
|
// If not found (pNodeCurr points back to head), then fail
|
|
if (pNodeCurr == &m_listDocNodes)
|
|
{
|
|
LeaveCriticalSection(&m_csDebuggerDetach);
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Detach the node by deleting the current element
|
|
delete pNodeCurr;
|
|
|
|
// Turn off "Debuggable" flag if last application is detached
|
|
m_fDebuggable = !m_listDocNodes.FIsEmpty();
|
|
|
|
// At this point CS not needed
|
|
LeaveCriticalSection(&m_csDebuggerDetach);
|
|
|
|
// If we have just removed ourselves from the last application,
|
|
// then we call Detach(), to remove all cached script engines now.
|
|
if (!m_fDebuggable)
|
|
Detach();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::Detach
|
|
detach this from the debugger UI tree view.
|
|
*/
|
|
HRESULT CTemplate::Detach
|
|
(
|
|
)
|
|
{
|
|
// Enter the CS to prevent DetachFrom() from detaching while we are clearing
|
|
// the list (causes application ptr to be deleted twice if this occurs)
|
|
if (m_fDebuggerDetachCSInited)
|
|
EnterCriticalSection(&m_csDebuggerDetach);
|
|
|
|
// Detach all nodes
|
|
while (! m_listDocNodes.FIsEmpty())
|
|
delete m_listDocNodes.PNext();
|
|
|
|
// Done with CS
|
|
if (m_fDebuggerDetachCSInited)
|
|
LeaveCriticalSection(&m_csDebuggerDetach);
|
|
|
|
// Since we are not debuggable now, remove any script engines we may
|
|
// be holding on to. If we are detaching from change notification
|
|
// thread, queue engines to be released from debugger thread.
|
|
//
|
|
if (m_rgpDebugScripts)
|
|
{
|
|
Assert (g_dwDebugThreadId != 0);
|
|
BOOL fCalledFromDebugActivity = GetCurrentThreadId() == g_dwDebugThreadId;
|
|
|
|
for (UINT i = 0; i < m_cScriptEngines; i++)
|
|
{
|
|
CActiveScriptEngine *pEngine = m_rgpDebugScripts[i];
|
|
if (pEngine)
|
|
{
|
|
if (fCalledFromDebugActivity)
|
|
{
|
|
pEngine->FinalRelease();
|
|
}
|
|
else
|
|
{
|
|
g_ApplnMgr.AddEngine(pEngine);
|
|
pEngine->Release();
|
|
}
|
|
}
|
|
}
|
|
delete[] m_rgpDebugScripts;
|
|
m_rgpDebugScripts = NULL;
|
|
}
|
|
|
|
m_fDebuggable = FALSE;
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CreateDocumentTree
|
|
Traverse the tree that we have embedded in the filemap structures,
|
|
and use it to create the include file structure
|
|
*/
|
|
HRESULT CTemplate::CreateDocumentTree
|
|
(
|
|
CFileMap *pfilemapRoot,
|
|
IDebugApplicationNode **ppDocRoot
|
|
)
|
|
{
|
|
IDebugApplicationNode *pDocNode;
|
|
HRESULT hr = S_OK;
|
|
|
|
if (pfilemapRoot == NULL || ppDocRoot == NULL)
|
|
return E_POINTER;
|
|
|
|
// Create the root node
|
|
if (FAILED(hr = g_pDebugApp->CreateApplicationNode(ppDocRoot)))
|
|
return hr;
|
|
|
|
// From the filemap information, match it up with the correct provider
|
|
// "This" is the provider for the root document, others come from Inc file cache
|
|
if (pfilemapRoot == m_rgpFilemaps[0])
|
|
{
|
|
if (FAILED(hr = (*ppDocRoot)->SetDocumentProvider(this)))
|
|
return hr;
|
|
}
|
|
else
|
|
{
|
|
CIncFile *pIncFile;
|
|
if (FAILED(hr = g_IncFileMap.GetIncFile(pfilemapRoot->m_szPathTranslated, &pIncFile)))
|
|
return hr;
|
|
|
|
if (FAILED(hr = (*ppDocRoot)->SetDocumentProvider(pIncFile)))
|
|
return hr;
|
|
|
|
// SetDocumentProvider AddRef'ed
|
|
pIncFile->Release();
|
|
}
|
|
|
|
// Create a node from all of the children and attach it to this node
|
|
CFileMap *pfilemapChild = pfilemapRoot->m_pfilemapChild;
|
|
while (pfilemapChild != NULL)
|
|
{
|
|
IDebugApplicationNode *pDocChild;
|
|
if (FAILED(hr = CreateDocumentTree(pfilemapChild, &pDocChild)))
|
|
return hr;
|
|
|
|
if (FAILED(hr = pDocChild->Attach(*ppDocRoot)))
|
|
return hr;
|
|
|
|
pfilemapChild = pfilemapChild->m_fHasSibling? pfilemapChild->m_pfilemapSibling : NULL;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::End
|
|
|
|
Place template in non-usable state (after this is called, last ref. should
|
|
be the any currently executing scripts. The count will naturally vanish
|
|
as the scripts finish. The template should never be recycled in cache after
|
|
this call.)
|
|
|
|
REF COUNTING NOTE:
|
|
Since debugging client has a reference to the template, the template needs
|
|
to dis-associate with the debugger at a point in time before destruction.
|
|
Otherwise, the reference will never go to zero.
|
|
*/
|
|
ULONG
|
|
CTemplate::End
|
|
(
|
|
)
|
|
{
|
|
// Flag template as non-usable (for debugging)
|
|
m_fIsValid = FALSE;
|
|
|
|
Detach();
|
|
|
|
if (!m_CPTextEvents.FIsEmpty() && g_pDebugApp != NULL)
|
|
{
|
|
IEnumConnections *pConnIterator;
|
|
if (SUCCEEDED(m_CPTextEvents.EnumConnections(&pConnIterator)))
|
|
{
|
|
CONNECTDATA ConnectData;
|
|
while (pConnIterator->Next(1, &ConnectData, NULL) == S_OK)
|
|
{
|
|
IDebugDocumentTextEvents *pTextEventSink;
|
|
if (SUCCEEDED(ConnectData.pUnk->QueryInterface(IID_IDebugDocumentTextEvents, reinterpret_cast<void **>(&pTextEventSink))))
|
|
{
|
|
InvokeDebuggerWithThreadSwitch(g_pDebugApp, DEBUGGER_ON_DESTROY, pTextEventSink);
|
|
pTextEventSink->Release();
|
|
}
|
|
ConnectData.pUnk->Release();
|
|
}
|
|
|
|
pConnIterator->Release();
|
|
}
|
|
}
|
|
|
|
return Release();
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::NotifyDebuggerOnPageEvent
|
|
Let debugger know about page start/end
|
|
*/
|
|
HRESULT
|
|
CTemplate::NotifyDebuggerOnPageEvent
|
|
(
|
|
BOOL fStart // TRUE = StartPage, FALSE = EndPage
|
|
)
|
|
{
|
|
CTemplateDocumentContext *pDebugContext = new CTemplateDocumentContext(this, 0, 0);
|
|
if (pDebugContext == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (g_pDebugApp)
|
|
hr = InvokeDebuggerWithThreadSwitch
|
|
(
|
|
g_pDebugApp,
|
|
fStart ? DEBUGGER_EVENT_ON_PAGEBEGIN : DEBUGGER_EVENT_ON_PAGEEND,
|
|
static_cast<IUnknown *>(pDebugContext)
|
|
);
|
|
|
|
pDebugContext->Release();
|
|
return hr;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::ReleaseTypeLibs
|
|
Release all typelibs collected from metadata
|
|
*/
|
|
void
|
|
CTemplate::ReleaseTypeLibs()
|
|
{
|
|
if (m_rgpTypeLibs.length() > 0)
|
|
{
|
|
for (UINT i = 0; i < m_rgpTypeLibs.length(); i++)
|
|
{
|
|
m_rgpTypeLibs[i]->Release();
|
|
}
|
|
|
|
m_rgpTypeLibs.reshape(0);
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::WrapTypeLibs
|
|
Wrap all typelibs collected from metadata into single IDispatch *
|
|
*/
|
|
void
|
|
CTemplate::WrapTypeLibs(CHitObj *pHitObj)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
Assert(m_pdispTypeLibWrapper == NULL);
|
|
|
|
if (m_rgpTypeLibs.length() > 0)
|
|
{
|
|
hr = ::WrapTypeLibs
|
|
(
|
|
m_rgpTypeLibs.begin(),
|
|
m_rgpTypeLibs.length(),
|
|
&m_pdispTypeLibWrapper
|
|
);
|
|
|
|
ReleaseTypeLibs();
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
m_pbErrorLocation = NULL;
|
|
m_idErrMsg = IDE_TEMPLATE_WRAP_TYPELIB_FAILED;
|
|
ProcessSpecificError(*(m_rgpFilemaps[0]), pHitObj);
|
|
THROW(E_TEMPLATE_COMPILE_FAILED_DONT_CACHE);
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::Release449
|
|
Release all 449-echo-cookie objects collected from metadata
|
|
*/
|
|
void
|
|
CTemplate::Release449()
|
|
{
|
|
if (m_rgp449.length() > 0)
|
|
{
|
|
for (UINT i = 0; i < m_rgp449.length(); i++)
|
|
{
|
|
m_rgp449[i]->Release();
|
|
}
|
|
|
|
m_rgp449.reshape(0);
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::Do449Processing
|
|
Generate 449 response in cookie negotiations with IE when needed
|
|
*/
|
|
HRESULT
|
|
CTemplate::Do449Processing
|
|
(
|
|
CHitObj *pHitObj
|
|
)
|
|
{
|
|
if (m_rgp449.length() == 0 || pHitObj->F449Done())
|
|
return S_OK;
|
|
|
|
HRESULT hr = ::Do449Processing
|
|
(
|
|
pHitObj,
|
|
m_rgp449.begin(),
|
|
m_rgp449.length()
|
|
);
|
|
|
|
pHitObj->Set449Done();
|
|
return hr;
|
|
}
|
|
#if 0
|
|
/* ============================================================================
|
|
CTemplate::OutputDebugTables
|
|
print the debugging data structures to the debug window
|
|
*/
|
|
void
|
|
CTemplate::OutputDebugTables()
|
|
{
|
|
unsigned i, j;
|
|
wchar_t wszDebugLine[256];
|
|
CWCharToMBCS convTarget;
|
|
CWCharToMBCS convSource;
|
|
|
|
// print line mapping table
|
|
|
|
DBGPRINTF((DBG_CONTEXT, "\nEngine HTML? Line# SourceOffset Length TargetOffset TargetText__________ SourceText__________ File\n"));
|
|
|
|
for (i = 0; i < m_cScriptEngines; ++i)
|
|
for (j = 0; j < m_rgrgSourceInfos[i].length(); ++j)
|
|
{
|
|
wchar_t wszSourceText[SNIPPET_SIZE + 1], wszTargetText[SNIPPET_SIZE + 1];
|
|
CSourceInfo *pSourceInfo = &m_rgrgSourceInfos[i][j];
|
|
|
|
// DON'T display sample script text on last line of each engine
|
|
if (j == m_rgrgSourceInfos[i].length() - 1)
|
|
{
|
|
wszTargetText[0] = 0;
|
|
wszSourceText[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
// Get source & target text sample
|
|
GetScriptSnippets(
|
|
pSourceInfo->m_cchSourceOffset, pSourceInfo->m_pfilemap,
|
|
pSourceInfo->m_cchTargetOffset, i,
|
|
wszSourceText, wszTargetText
|
|
);
|
|
|
|
// Actually display each line
|
|
#if 0
|
|
#ifndef _NO_TRACING_
|
|
convTarget.Init(wszTargetText);
|
|
convSource.Init(wszSourceText);
|
|
|
|
DBGINFO((DBG_CONTEXT,
|
|
"%-6d %-5s %-5d %-12d %-6d %-12d %-20s %-20s %s\n",
|
|
i,
|
|
pSourceInfo->m_fIsHTML? "Yes" : "No",
|
|
pSourceInfo->m_idLine,
|
|
pSourceInfo->m_cchSourceOffset,
|
|
pSourceInfo->m_cchSourceText,
|
|
pSourceInfo->m_cchTargetOffset,
|
|
convTarget.GetString(),
|
|
convSource.GetString(),
|
|
pSourceInfo->m_pfilemap->m_szPathTranslated));
|
|
#else
|
|
CMBCSToWChar convPath;
|
|
convPath.Init(pSourceInfo->m_pfilemap->m_szPathTranslated);
|
|
wsprintfW(
|
|
wszDebugLine,
|
|
L"%-6d %-5s %-5d %-12d %-6d %-12d %-20s %-20s %s\n",
|
|
i,
|
|
pSourceInfo->m_fIsHTML? L"Yes" : L"No",
|
|
pSourceInfo->m_idLine,
|
|
pSourceInfo->m_cchSourceOffset,
|
|
pSourceInfo->m_cchSourceText,
|
|
pSourceInfo->m_cchTargetOffset,
|
|
wszTargetText,
|
|
wszSourceText,
|
|
convPath.GetString());
|
|
|
|
OutputDebugStringW(wszDebugLine);
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
|
|
OutputDebugStringA("\n\n");
|
|
|
|
for (i = 0; i < m_cFilemaps; ++i)
|
|
{
|
|
CFileMap *pFilemap = m_rgpFilemaps[i];
|
|
|
|
#if UNICODE
|
|
DBGPRINTF((DBG_CONTEXT, "DBCS mapping table for File %S:\n", pFilemap->m_szPathTranslated));
|
|
#else
|
|
DBGPRINTF((DBG_CONTEXT, "DBCS mapping table for File %s:\n", pFilemap->m_szPathTranslated));
|
|
#endif
|
|
DBGPRINTF((DBG_CONTEXT, "ByteOffset CharOffset\n"));
|
|
|
|
for (COffsetInfo *pOffsetInfo = pFilemap->m_rgByte2DBCS.begin();
|
|
pOffsetInfo < pFilemap->m_rgByte2DBCS.end();
|
|
++pOffsetInfo)
|
|
DebugPrintf("%-10d %-10d\n", pOffsetInfo->m_cbOffset, pOffsetInfo->m_cchOffset);
|
|
|
|
DBGPRINTF((DBG_CONTEXT, "\n\n"));
|
|
}
|
|
|
|
DBGPRINTF((DBG_CONTEXT, "Include File Hierarchy\n"));
|
|
OutputIncludeHierarchy(m_rgpFilemaps[0], 0);
|
|
DBGPRINTF((DBG_CONTEXT, "\n"));
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::OutputIncludeHierarchy
|
|
print the lineage information that we keep around for include files.
|
|
Print all nodes on one level at the current indentation, then descend for
|
|
nested includes.
|
|
*/
|
|
|
|
void
|
|
CTemplate::OutputIncludeHierarchy
|
|
(
|
|
CFileMap* pfilemap,
|
|
int cchIndent
|
|
)
|
|
{
|
|
TCHAR szDebugString[256], *pchEnd;
|
|
|
|
for (;;)
|
|
{
|
|
pchEnd = szDebugString;
|
|
for (int i = 0; i < cchIndent; ++i)
|
|
*pchEnd++ = _T(' ');
|
|
|
|
pchEnd = strcpyEx(pchEnd, pfilemap->m_szPathTranslated);
|
|
*pchEnd++ = _T('\n');
|
|
*pchEnd = _T('\0');
|
|
|
|
DBGPRINTF((DBG_CONTEXT, szDebugString));
|
|
|
|
// Print anything that this file includes
|
|
if (pfilemap->m_pfilemapChild)
|
|
OutputIncludeHierarchy(pfilemap->m_pfilemapChild, cchIndent + 3);
|
|
|
|
// Stop when there are no more siblings on this level
|
|
if (! pfilemap->m_fHasSibling)
|
|
break;
|
|
|
|
// Advance to next sibling
|
|
pfilemap = pfilemap->m_pfilemapSibling;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::OutputScriptSnippets
|
|
print some script from both the source offset & its corresponding target.
|
|
Good way to visually see if the offset conversions are working.
|
|
*/
|
|
|
|
void
|
|
CTemplate::GetScriptSnippets
|
|
(
|
|
ULONG cchSourceOffset,
|
|
CFileMap *pFilemapSource,
|
|
ULONG cchTargetOffset,
|
|
ULONG idTargetEngine,
|
|
wchar_t *wszSourceText,
|
|
wchar_t *wszTargetText
|
|
)
|
|
{
|
|
// Get target text sample
|
|
if (wszTargetText)
|
|
{
|
|
char *szEngineName;
|
|
PROGLANG_ID *pProgLangID;
|
|
const wchar_t *wszScriptText;
|
|
|
|
GetScriptBlock(idTargetEngine, &szEngineName, &pProgLangID, &wszScriptText);
|
|
wszScriptText += cchTargetOffset;
|
|
int cch = wcslen(wszScriptText);
|
|
wcsncpy(wszTargetText, wszScriptText, min(cch, SNIPPET_SIZE) + 1);
|
|
wszTargetText[min(cch, SNIPPET_SIZE)] = 0;
|
|
|
|
// Convert newlines to space
|
|
wchar_t *pwch = wszTargetText;
|
|
while (*pwch != 0)
|
|
if (iswspace(*pwch++))
|
|
pwch[-1] = ' ';
|
|
}
|
|
|
|
// Get source text sample
|
|
if (wszSourceText)
|
|
{
|
|
ULONG cchMax = 0;
|
|
pFilemapSource->GetText((WORD)m_wCodePage, cchSourceOffset, wszSourceText, NULL, &cchMax, SNIPPET_SIZE);
|
|
wszSourceText[cchMax] = 0;
|
|
|
|
// Convert newlines to space
|
|
wchar_t *pwch = wszSourceText;
|
|
while (*pwch != 0)
|
|
if (iswspace(*pwch++))
|
|
pwch[-1] = ' ';
|
|
}
|
|
}
|
|
#endif
|
|
/* ============================================================================
|
|
CTemplate::BuildPersistedDACL
|
|
|
|
Builds a DACL based on the SECURITY_DESCRIPTOR already
|
|
associated with the template. The PersistedDACL is modified to include
|
|
full access for administrators and delete access for everyone.
|
|
*/
|
|
|
|
HRESULT CTemplate::BuildPersistedDACL(PACL *ppRetDACL)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL bDaclPresent;
|
|
BOOL bDaclDefaulted;
|
|
PACL pSrcDACL = NULL;
|
|
EXPLICIT_ACCESS ea;
|
|
SID_IDENTIFIER_AUTHORITY WorldAuthority = SECURITY_WORLD_SID_AUTHORITY;
|
|
|
|
*ppRetDACL = NULL;
|
|
|
|
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
|
|
|
|
ea.grfAccessPermissions = SYNCHRONIZE | DELETE;
|
|
ea.grfAccessMode = GRANT_ACCESS;
|
|
ea.grfInheritance= SUB_CONTAINERS_AND_OBJECTS_INHERIT;
|
|
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
|
|
|
if (m_rgpFilemaps[0]->m_pSecurityDescriptor == NULL) {
|
|
return S_OK;
|
|
}
|
|
|
|
if (!AllocateAndInitializeSid(&WorldAuthority,
|
|
1,
|
|
SECURITY_WORLD_RID,
|
|
0,0,0,0,0,0,0,
|
|
(PSID *)(&ea.Trustee.ptstrName)))
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
else if (!GetSecurityDescriptorDacl(m_rgpFilemaps[0]->m_pSecurityDescriptor,
|
|
&bDaclPresent,
|
|
&pSrcDACL,
|
|
&bDaclDefaulted))
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
else if ((hr = SetEntriesInAcl(1,
|
|
&ea,
|
|
bDaclPresent ? pSrcDACL : NULL,
|
|
ppRetDACL)) != ERROR_SUCCESS)
|
|
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
|
|
if (ea.Trustee.ptstrName)
|
|
FreeSid(ea.Trustee.ptstrName);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::PersistData
|
|
Attempts to write the contents of the template memory to disk. Note that
|
|
the memory isn't freed here but later when the template ref count falls to
|
|
1 (indicating that the only reference to the template is the one that the
|
|
cache has on it).
|
|
*/
|
|
|
|
HRESULT CTemplate::PersistData(char *pszTempFilePath)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD winErr = 0;
|
|
HANDLE hFile = NULL;
|
|
DWORD dwWritten;
|
|
HANDLE hImpersonationToken = NULL;
|
|
HANDLE hThread;
|
|
PACL pPersistDACL = NULL;
|
|
|
|
#if DBG_PERSTEMPL
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CTemplate::PersistData() enterred.\n\tTemplate is %s\n\tPersistTempName is %s\n",
|
|
GetSourceFileName(),
|
|
m_szPersistTempName ? m_szPersistTempName : "<none>"));
|
|
#endif
|
|
|
|
// if for some reason this template has been marked as invalid, then it is
|
|
// not persistable
|
|
|
|
if (m_fIsValid == FALSE) {
|
|
hr = E_FAIL;
|
|
goto end;
|
|
}
|
|
|
|
// if it is already persisted, there is nothing to do
|
|
|
|
if (m_fIsPersisted) {
|
|
goto end;
|
|
}
|
|
|
|
// check to see if we already have a persist temp name. If a template moves
|
|
// from the persisted cache back to the memory cache, then the persisted flag
|
|
// will have been lifted but the cache name will remain as an optimization for
|
|
// future persisting.
|
|
|
|
if (m_szPersistTempName == NULL) {
|
|
|
|
hThread = GetCurrentThread();
|
|
|
|
if (OpenThreadToken( hThread,
|
|
TOKEN_READ | TOKEN_IMPERSONATE,
|
|
TRUE,
|
|
&hImpersonationToken )) {
|
|
|
|
RevertToSelf();
|
|
}
|
|
|
|
// allocate memory for this temp path
|
|
|
|
if (!(m_szPersistTempName = (LPSTR)CTemplate::LargeMalloc(MAX_PATH))) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
// create the temp file. The location of the temp directory was passed
|
|
// in as an argument. The resulting tempfile name in m_szPersistTempName
|
|
// will include this path.
|
|
|
|
else if (GetTempFileNameA(pszTempFilePath,
|
|
"ASPTemplate",
|
|
0,
|
|
m_szPersistTempName) == 0) {
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
// build a security descriptor to use with this persisted file. It is
|
|
// comprised of the .asp's security descriptor plus a couple of DACLs
|
|
// to allow administrators full access and everyone delete access.
|
|
|
|
else if (FAILED(hr = BuildPersistedDACL(&pPersistDACL)));
|
|
|
|
else if (pPersistDACL
|
|
&& (winErr = SetNamedSecurityInfoA((LPSTR)m_szPersistTempName,
|
|
SE_FILE_OBJECT,
|
|
DACL_SECURITY_INFORMATION,
|
|
NULL,
|
|
NULL,
|
|
pPersistDACL,
|
|
NULL)))
|
|
hr = HRESULT_FROM_WIN32(winErr);
|
|
|
|
// create the file
|
|
|
|
else if ((hFile = CreateFileA(m_szPersistTempName,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL)) == INVALID_HANDLE_VALUE) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
// slam out the entire contents of the template memory to the file
|
|
|
|
else if (WriteFile(hFile,
|
|
m_pbStart,
|
|
m_cbTemplate,
|
|
&dwWritten,
|
|
NULL) == 0) {
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
// close
|
|
|
|
else if (CloseHandle(hFile) == 0) {
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
else {
|
|
hFile = NULL;
|
|
}
|
|
if (FAILED(hr));
|
|
|
|
// make sure that the entire amount was written out
|
|
|
|
else if (dwWritten != m_cbTemplate) {
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
if (hImpersonationToken) {
|
|
SetThreadToken(&hThread, hImpersonationToken);
|
|
CloseHandle(hImpersonationToken);
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr));
|
|
|
|
else {
|
|
|
|
// if successfull, then note that the template is now persisted.
|
|
// Do an AddRef and Release as a safe way to check to see if the
|
|
// template memory can be freed.
|
|
|
|
m_fIsPersisted = TRUE;
|
|
AddRef();
|
|
Release();
|
|
}
|
|
|
|
// if errors occurred, clean up any resources.
|
|
|
|
if (hr != S_OK) {
|
|
if (hFile)
|
|
CloseHandle(hFile);
|
|
if (m_szPersistTempName)
|
|
CTemplate::LargeFree(m_szPersistTempName);
|
|
m_szPersistTempName = NULL;
|
|
}
|
|
|
|
// free the persisted SECURITY_DESCRIPTOR if allocated
|
|
|
|
if (pPersistDACL) {
|
|
LocalFree(pPersistDACL);
|
|
}
|
|
|
|
end:
|
|
|
|
#if DBG_PERSTEMPL
|
|
if (hr == S_OK) {
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"Persist Successful. TempName is %s\n",
|
|
m_szPersistTempName));
|
|
}
|
|
else {
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"Persist failed. hr = %x",
|
|
hr));
|
|
}
|
|
#endif
|
|
|
|
return hr;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::UnPersistData
|
|
Restores the template memory from disk.
|
|
*/
|
|
|
|
HRESULT CTemplate::UnPersistData()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HANDLE hFile = NULL;
|
|
DWORD dwRead;
|
|
HANDLE hImpersonationToken = NULL;
|
|
HANDLE hThread;
|
|
|
|
#if DEB_PERSTEMPL
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CTemplate::UnPersistData() enterred.\n\tTemplate is %s\n\tTempName is %s\n",
|
|
m_rgpFilemaps[0]->m_szPathTranslated,
|
|
m_szPersistTempName));
|
|
#endif
|
|
|
|
// check to see if the template is already loaded into memory. If so, then
|
|
// all this routine needs to do is lift the IsPersisted flag.
|
|
|
|
if (m_pbStart != NULL) {
|
|
m_fIsPersisted = FALSE;
|
|
goto end;
|
|
}
|
|
|
|
hThread = GetCurrentThread();
|
|
|
|
if (OpenThreadToken( hThread,
|
|
TOKEN_READ | TOKEN_IMPERSONATE,
|
|
TRUE,
|
|
&hImpersonationToken )) {
|
|
|
|
RevertToSelf();
|
|
}
|
|
|
|
// open the temp file for read
|
|
|
|
if ((hFile = CreateFileA(m_szPersistTempName,
|
|
GENERIC_READ,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL)) == INVALID_HANDLE_VALUE) {
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
// allocate the template memory
|
|
|
|
else if (!(m_pbStart = (BYTE *)CTemplate::LargeMalloc(m_cbTemplate))) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
// read in the entire file
|
|
|
|
else if (ReadFile(hFile,
|
|
m_pbStart,
|
|
m_cbTemplate,
|
|
&dwRead,
|
|
NULL) == 0) {
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
// we're done with the file
|
|
|
|
else if (CloseHandle(hFile) == 0) {
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
else {
|
|
hFile = NULL;
|
|
}
|
|
|
|
if (FAILED(hr));
|
|
|
|
// check to make sure we got everything
|
|
|
|
else if (m_cbTemplate != dwRead) {
|
|
hr = E_FAIL;
|
|
}
|
|
else {
|
|
|
|
// if not, pretend like this is no longer persisted. Prevents errors
|
|
// in the future.
|
|
|
|
m_fIsPersisted = FALSE;
|
|
}
|
|
|
|
if (hr != S_OK) {
|
|
|
|
// make sure that the file handle was cleaned up
|
|
|
|
if (hFile)
|
|
CloseHandle(hFile);
|
|
}
|
|
end:
|
|
|
|
if (hImpersonationToken) {
|
|
SetThreadToken(&hThread, hImpersonationToken);
|
|
CloseHandle(hImpersonationToken);
|
|
}
|
|
|
|
#if DBG_PERSTEMPL
|
|
if (hr == S_OK) {
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"UnPersist Successful\n"));
|
|
}
|
|
else {
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"UnPersist failed. hr = %x",
|
|
hr));
|
|
}
|
|
#endif
|
|
|
|
return hr;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::PersistCleanup
|
|
Cleans up the temp file and the memory holding the temp file name.
|
|
*/
|
|
|
|
HRESULT CTemplate::PersistCleanup()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HANDLE hImpersonationToken = NULL;
|
|
HANDLE hThread;
|
|
|
|
if (m_szPersistTempName == NULL) {
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
hThread = GetCurrentThread();
|
|
|
|
if (OpenThreadToken( hThread,
|
|
TOKEN_READ | TOKEN_IMPERSONATE,
|
|
TRUE,
|
|
&hImpersonationToken )) {
|
|
|
|
RevertToSelf();
|
|
}
|
|
|
|
if (DeleteFileA(m_szPersistTempName) == 0) {
|
|
hr = GetLastError();
|
|
}
|
|
else {
|
|
m_fIsPersisted = FALSE;
|
|
CTemplate::LargeFree(m_szPersistTempName);
|
|
m_szPersistTempName = NULL;
|
|
}
|
|
|
|
if (hImpersonationToken) {
|
|
SetThreadToken(&hThread, hImpersonationToken);
|
|
CloseHandle(hImpersonationToken);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::CreateTransServiceConfig
|
|
Creates the ServicesConfig object for a transacted page
|
|
*/
|
|
|
|
HRESULT CTemplate::CreateTransServiceConfig(BOOL fEnableTracker)
|
|
{
|
|
HRESULT hr;
|
|
IServiceInheritanceConfig *pIInheritConfig = NULL;
|
|
IServiceTransactionConfig *pITransConfig = NULL;
|
|
IServiceTrackerConfig *pITracker = NULL;
|
|
|
|
// see if there is any reason to create the object...
|
|
|
|
if ((fEnableTracker == FALSE) && (m_ttTransacted == ttUndefined)) {
|
|
return S_OK;
|
|
}
|
|
|
|
hr = CoCreateInstance(CLSID_CServiceConfig,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IUnknown,
|
|
(void **)&m_pServicesConfig);
|
|
if (FAILED(hr)) {
|
|
DBGWARN((DBG_CONTEXT, "CTemplate::CreateTransServiceConfig() - Could not CCI ServicesConfig, hr = %#08x\n", hr));
|
|
goto LCleanup;
|
|
}
|
|
|
|
hr = m_pServicesConfig->QueryInterface(IID_IServiceInheritanceConfig, (void **)&pIInheritConfig);
|
|
if (FAILED(hr)) {
|
|
DBGWARN((DBG_CONTEXT, "CTemplate::CreateTransServiceConfig() - Could not QI for IServiceInheritanceConfig, hr = %#08x\n", hr));
|
|
goto LCleanup;
|
|
}
|
|
|
|
hr = pIInheritConfig->ContainingContextTreatment(CSC_Inherit);
|
|
if (FAILED(hr)) {
|
|
DBGWARN((DBG_CONTEXT, "CTemplate::CreateTransServiceConfig() - Could not set Inherit mode, hr = %#08x\n", hr));
|
|
goto LCleanup;
|
|
}
|
|
|
|
if (m_ttTransacted != ttUndefined) {
|
|
|
|
CSC_TransactionConfig transConfig;
|
|
|
|
switch (m_ttTransacted) {
|
|
case ttNotSupported:
|
|
transConfig = CSC_NoTransaction;
|
|
break;
|
|
case ttSupported:
|
|
transConfig = CSC_IfContainerIsTransactional;
|
|
break;
|
|
case ttRequired:
|
|
transConfig = CSC_CreateTransactionIfNecessary;
|
|
break;
|
|
case ttRequiresNew:
|
|
transConfig = CSC_NewTransaction;
|
|
break;
|
|
}
|
|
|
|
hr = m_pServicesConfig->QueryInterface(IID_IServiceTransactionConfig, (void **)&pITransConfig);
|
|
if (FAILED(hr)) {
|
|
DBGWARN((DBG_CONTEXT, "CTemplate::CreateTransServiceConfig() - Could not QI for IID_IServiceTransactionConfig, hr = %#08x\n", hr));
|
|
goto LCleanup;
|
|
}
|
|
|
|
hr = pITransConfig->ConfigureTransaction(transConfig);
|
|
if (FAILED(hr)) {
|
|
DBGWARN((DBG_CONTEXT, "CTemplate::CreateTransServiceConfig() - Could not set transaction type, hr = %#08x\n", hr));
|
|
goto LCleanup;
|
|
}
|
|
}
|
|
|
|
if (fEnableTracker) {
|
|
hr = m_pServicesConfig->QueryInterface(IID_IServiceTrackerConfig, (void **)&pITracker);
|
|
if (FAILED(hr)) {
|
|
DBGWARN((DBG_CONTEXT, "CTemplate::CreateTransServiceConfig() - Could not QI for IID_IServiceTrackerConfig, hr = %#08x\n", hr));
|
|
goto LCleanup;
|
|
}
|
|
LPWSTR pwszAppURL;
|
|
LPWSTR pwszASPName;
|
|
#if UNICODE
|
|
pwszAppURL = m_szApplnVirtPath;
|
|
pwszASPName = GetSourceFileName(SOURCEPATHTYPE_VIRTUAL);
|
|
#else
|
|
CMBCSToWChar convAppln;
|
|
CMBCSToWChar convASPName;
|
|
|
|
hr = convAppln.Init(m_szApplnVirtPath);
|
|
if (FAILED(hr)) {
|
|
goto LCleanup;
|
|
}
|
|
hr = convASPName.Init(GetSourceFileName(SOURCEPATHTYPE_VIRTUAL));
|
|
if (FAILED(hr)) {
|
|
goto LCleanup;
|
|
}
|
|
pwszAppURL = convAppln.GetString();
|
|
pwszASPName = convASPName.GetString();
|
|
#endif
|
|
pwszASPName += wcslen(pwszAppURL) + 1;
|
|
|
|
hr = pITracker->TrackerConfig(CSC_UseTracker, pwszAppURL, pwszASPName);
|
|
if (FAILED(hr)) {
|
|
DBGWARN((DBG_CONTEXT, "CTemplate::CreateTransServiceConfig() - Could not set Inherit mode, hr = %#08x\n", hr));
|
|
goto LCleanup;
|
|
}
|
|
}
|
|
LCleanup:
|
|
|
|
if (pIInheritConfig)
|
|
pIInheritConfig->Release();
|
|
|
|
if (pITransConfig)
|
|
pITransConfig->Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
CIncFile member functions
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CIncFile::CIncFile
|
|
Constructor
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
CIncFile::CIncFile
|
|
(
|
|
)
|
|
: m_szIncFile(NULL),
|
|
m_fCsInited(FALSE),
|
|
m_CPTextEvents(this, IID_IDebugDocumentTextEvents),
|
|
m_cRefs(0)
|
|
{ }
|
|
|
|
/* ============================================================================
|
|
CIncFile::Init
|
|
Inits the CIncFile object
|
|
|
|
Returns:
|
|
HRESULT
|
|
Side effects:
|
|
None
|
|
*/
|
|
HRESULT
|
|
CIncFile::Init
|
|
(
|
|
const TCHAR* szIncFile // file name
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
WIN32_FILE_ATTRIBUTE_DATA fad; // win32 file attributes data structure
|
|
|
|
ErrInitCriticalSection(&m_csUpdate, hr);
|
|
m_fCsInited = TRUE;
|
|
|
|
if(NULL == (m_szIncFile = (LPTSTR) CTemplate::SmallMalloc((_tcslen(szIncFile) + 1)*sizeof(TCHAR)))) {
|
|
hr = E_OUTOFMEMORY;
|
|
goto LExit;
|
|
}
|
|
|
|
_tcscpy(m_szIncFile, szIncFile);
|
|
|
|
// init hash table element base class
|
|
if(FAILED(hr = CLinkElem::Init(m_szIncFile, _tcslen(m_szIncFile)*sizeof(TCHAR))))
|
|
goto LExit;
|
|
|
|
LExit:
|
|
return hr;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::~CIncFile
|
|
Destructor
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
CIncFile::~CIncFile
|
|
(
|
|
)
|
|
{
|
|
#if UNICODE
|
|
DBGPRINTF((DBG_CONTEXT, "Include file deleted: %S\n", m_szIncFile));
|
|
#else
|
|
DBGPRINTF((DBG_CONTEXT, "Include file deleted: %s\n", m_szIncFile));
|
|
#endif
|
|
Assert(m_cRefs == 0);
|
|
SmallTemplateFreeNullify((void**) &m_szIncFile);
|
|
if(m_fCsInited)
|
|
DeleteCriticalSection(&m_csUpdate);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::GetTemplate
|
|
Get i'th template user from CIncFile
|
|
|
|
Returns:
|
|
NULL if "iTemplate" is out of range, m_rgpTemplates[iTemplate] otherwise
|
|
|
|
Side effects:
|
|
None
|
|
*/
|
|
CTemplate*
|
|
CIncFile::GetTemplate
|
|
(
|
|
int iTemplate
|
|
)
|
|
{
|
|
if (iTemplate < 0 || iTemplate >= (signed int) m_rgpTemplates.length())
|
|
return NULL;
|
|
|
|
else
|
|
return m_rgpTemplates[iTemplate];
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::QueryInterface
|
|
Provides QueryInterface implementation for CIncFile
|
|
|
|
NOTE: It is arbitrary which vtable we return for IDebugDocument & IDebugDocumentInfo.
|
|
*/
|
|
HRESULT
|
|
CIncFile::QueryInterface(const GUID &uidInterface, void **ppvObj)
|
|
{
|
|
if (uidInterface == IID_IUnknown || uidInterface == IID_IDebugDocumentProvider)
|
|
*ppvObj = static_cast<IDebugDocumentProvider *>(this);
|
|
|
|
else if (uidInterface == IID_IDebugDocument || uidInterface == IID_IDebugDocumentInfo || uidInterface == IID_IDebugDocumentText)
|
|
*ppvObj = static_cast<IDebugDocumentText *>(this);
|
|
|
|
else if (uidInterface == IID_IConnectionPointContainer)
|
|
*ppvObj = static_cast<IConnectionPointContainer *>(this);
|
|
|
|
else
|
|
*ppvObj = NULL;
|
|
|
|
if (*ppvObj)
|
|
{
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
else
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::AddRef
|
|
Adds a ref to this IncFile, thread-safely
|
|
*/
|
|
ULONG
|
|
CIncFile::AddRef()
|
|
{
|
|
InterlockedIncrement(&m_cRefs);
|
|
return m_cRefs;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::Release
|
|
Releases a ref to this IncFile, thread-safely
|
|
*/
|
|
ULONG
|
|
CIncFile::Release()
|
|
{
|
|
if (InterlockedDecrement(&m_cRefs) == 0)
|
|
{
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
return m_cRefs;
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
IDebugDocumentProvider implementation for includes
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CIncFile::GetDocument
|
|
Return a pointer to the IDebugDocument implementation. (same object in this case)
|
|
|
|
Returns:
|
|
*ppDebugDoc is set to "this".
|
|
Notes:
|
|
always succeeds
|
|
*/
|
|
HRESULT CIncFile::GetDocument
|
|
(
|
|
IDebugDocument **ppDebugDoc
|
|
)
|
|
{
|
|
return QueryInterface(IID_IDebugDocument, reinterpret_cast<void **>(ppDebugDoc));
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::GetName
|
|
Return the various names of a document.
|
|
*/
|
|
|
|
HRESULT CIncFile::GetName
|
|
(
|
|
/* [in] */ DOCUMENTNAMETYPE doctype,
|
|
/* [out] */ BSTR *pbstrName
|
|
)
|
|
{
|
|
switch (doctype) {
|
|
case DOCUMENTNAMETYPE_APPNODE:
|
|
case DOCUMENTNAMETYPE_FILE_TAIL:
|
|
case DOCUMENTNAMETYPE_TITLE:
|
|
// Use the name of the include file (char after last back-slash) converted to lower case.
|
|
{
|
|
TCHAR *szFilePart = _tcsrchr(m_szIncFile, _T('\\'));
|
|
Assert (szFilePart != NULL);
|
|
|
|
#if UNICODE
|
|
*pbstrName = SysAllocString(szFilePart + 1);
|
|
if (*pbstrName == NULL) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
#else
|
|
if (FAILED(SysAllocStringFromSz(szFilePart + 1, 0, pbstrName, CP_ACP)))
|
|
return E_FAIL;
|
|
#endif
|
|
if (*pbstrName != NULL)
|
|
_wcslwr(*pbstrName);
|
|
return S_OK;
|
|
}
|
|
|
|
case DOCUMENTNAMETYPE_URL:
|
|
// prefix with the URL, use szPathInfo for the rest of the path
|
|
{
|
|
CTemplate::CFileMap *pFilemap = GetFilemap();
|
|
if (pFilemap->FHasVirtPath()) {
|
|
STACK_BUFFER( tempName, MAX_PATH );
|
|
|
|
CTemplate *pTemplate = m_rgpTemplates[0];
|
|
int cbURLPrefix = DIFF(pTemplate->m_szApplnVirtPath - pTemplate->m_szApplnURL)*sizeof(TCHAR);
|
|
|
|
if (!tempName.Resize(cbURLPrefix + ((_tcslen(pFilemap->m_szPathInfo) + 1)*sizeof(TCHAR)))) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
TCHAR *szURL = (TCHAR *)tempName.QueryPtr();
|
|
|
|
memcpy(szURL, pTemplate->m_szApplnURL, cbURLPrefix);
|
|
_tcscpy(&szURL[cbURLPrefix/sizeof(TCHAR)], pFilemap->m_szPathInfo);
|
|
#if UNICODE
|
|
*pbstrName = SysAllocString(szURL);
|
|
if (*pbstrName == NULL) {
|
|
return (E_OUTOFMEMORY);
|
|
}
|
|
return S_OK;
|
|
#else
|
|
return SysAllocStringFromSz(szURL, 0, pbstrName, pTemplate->m_wCodePage);
|
|
#endif
|
|
}
|
|
else {
|
|
*pbstrName = NULL;
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
|
|
default:
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
IDebugDocumentText implementation
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CIncFile::GetSize
|
|
Return the number of lines & characters in the document
|
|
*/
|
|
HRESULT CIncFile::GetSize
|
|
(
|
|
/* [out] */ ULONG *pcLines,
|
|
/* [out] */ ULONG *pcChars
|
|
)
|
|
{
|
|
CTemplate::CFileMap *pfilemap = GetFilemap();
|
|
|
|
*pcLines = ULONG_MAX;
|
|
*pcChars = pfilemap->m_cChars;
|
|
#if UNICODE
|
|
DBGPRINTF((DBG_CONTEXT, "GetSize(\"%S\") returns %lu characters (%lu lines)\n", pfilemap->m_szPathTranslated, *pcChars, *pcLines));
|
|
#else
|
|
DBGPRINTF((DBG_CONTEXT, "GetSize(\"%s\") returns %lu characters (%lu lines)\n", pfilemap->m_szPathTranslated, *pcChars, *pcLines));
|
|
#endif
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::GetDocumentAttributes
|
|
Return doc attributes
|
|
*/
|
|
HRESULT CIncFile::GetDocumentAttributes
|
|
(
|
|
/* [out] */ TEXT_DOC_ATTR *ptextdocattr
|
|
)
|
|
{
|
|
// Easy way to tell debugger that we don't support editing.
|
|
*ptextdocattr = TEXT_DOC_ATTR_READONLY;
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::GetPositionOfLine
|
|
From a line number, return the character offset of the beginning
|
|
|
|
I don't think we need this function. It is meant to support line oriented
|
|
debuggers, of which Caesar is not one.
|
|
*/
|
|
HRESULT CIncFile::GetPositionOfLine
|
|
(
|
|
/* [in] */ ULONG cLineNumber,
|
|
/* [out] */ ULONG *pcCharacterPosition
|
|
)
|
|
{
|
|
return m_rgpTemplates[0]->GetPositionOfLine(GetFilemap(), cLineNumber, pcCharacterPosition);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::GetLineOfPosition
|
|
From a character offset, return the line number and offset within the line
|
|
|
|
I don't think we need this function. It is meant to support line oriented
|
|
debuggers, of which Caesar is not one.
|
|
*/
|
|
HRESULT CIncFile::GetLineOfPosition
|
|
(
|
|
/* [in] */ ULONG cCharacterPosition,
|
|
/* [out] */ ULONG *pcLineNumber,
|
|
/* [out] */ ULONG *pcCharacterOffsetInLine
|
|
)
|
|
{
|
|
return m_rgpTemplates[0]->GetLineOfPosition(GetFilemap(), cCharacterPosition, pcLineNumber, pcCharacterOffsetInLine);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::GetText
|
|
From a character offset and length, return the document text
|
|
*/
|
|
HRESULT CIncFile::GetText
|
|
(
|
|
ULONG cchSourceOffset,
|
|
WCHAR *pwchText,
|
|
SOURCE_TEXT_ATTR *pTextAttr,
|
|
ULONG *pcChars,
|
|
ULONG cMaxChars
|
|
)
|
|
{
|
|
return GetFilemap()->GetText((WORD)m_rgpTemplates[0]->m_wCodePage, cchSourceOffset, pwchText, pTextAttr, pcChars, cMaxChars);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::GetPositionOfContext
|
|
Decompose a document context into the document offset & length
|
|
*/
|
|
HRESULT CIncFile::GetPositionOfContext
|
|
(
|
|
/* [in] */ IDebugDocumentContext *pUnknownDocumentContext,
|
|
/* [out] */ ULONG *pcchSourceOffset,
|
|
/* [out] */ ULONG *pcchText
|
|
)
|
|
{
|
|
// Make sure that the context is one of ours
|
|
CIncFileDocumentContext *pDocumentContext;
|
|
if (FAILED(pUnknownDocumentContext->QueryInterface(IID_IDenaliIncFileDocumentContext, reinterpret_cast<void **>(&pDocumentContext))))
|
|
return E_FAIL;
|
|
|
|
if (pcchSourceOffset)
|
|
*pcchSourceOffset = pDocumentContext->m_cchSourceOffset;
|
|
|
|
if (pcchText)
|
|
*pcchText = pDocumentContext->m_cchText;
|
|
|
|
pDocumentContext->Release();
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::GetContextOfPosition
|
|
Given the character position & number of characters in the document,
|
|
encapsulate this into a document context object.
|
|
*/
|
|
HRESULT CIncFile::GetContextOfPosition
|
|
(
|
|
/* [in] */ ULONG cchSourceOffset,
|
|
/* [in] */ ULONG cchText,
|
|
/* [out] */ IDebugDocumentContext **ppDocumentContext
|
|
)
|
|
{
|
|
if (
|
|
(*ppDocumentContext = new CIncFileDocumentContext(this, cchSourceOffset, cchText))
|
|
== NULL
|
|
)
|
|
return E_OUTOFMEMORY;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
IConnectionPointContainer implementation
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CIncFile::FindConnectionPoint
|
|
From a character offset and length, return the document text
|
|
*/
|
|
HRESULT CIncFile::FindConnectionPoint
|
|
(
|
|
const GUID &uidConnection,
|
|
IConnectionPoint **ppCP
|
|
)
|
|
{
|
|
if (uidConnection == IID_IDebugDocumentTextEvents)
|
|
return m_CPTextEvents.QueryInterface(IID_IConnectionPoint, reinterpret_cast<void **>(ppCP));
|
|
else
|
|
{
|
|
*ppCP = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::GetFilemap
|
|
Returns a CFileMap pointer for this include file. (Note: There are several
|
|
CFileMaps that may be used, corresponding to each template. This function
|
|
selects one of them.)
|
|
|
|
Returns:
|
|
Corresponding CFileMap
|
|
Side effects:
|
|
None
|
|
*/
|
|
CTemplate::CFileMap *
|
|
CIncFile::GetFilemap
|
|
(
|
|
)
|
|
{
|
|
// Get pointer to first template's filemaps
|
|
CTemplate::CFileMap **ppFilemapInc = &m_rgpTemplates[0]->m_rgpFilemaps[1];
|
|
BOOL fFoundInc = FALSE;
|
|
|
|
// Look for the filemap whose name corresponds to this IncFile. It had better exist
|
|
// in all template filemaps.
|
|
// NOTE: Start searching at position 1, because position 0 is the template itself.
|
|
//
|
|
for (unsigned i = 1; i < m_rgpTemplates[0]->m_cFilemaps && !fFoundInc; ++i)
|
|
if (_tcscmp(m_szIncFile, (*ppFilemapInc++)->m_szPathTranslated) == 0)
|
|
fFoundInc = TRUE;
|
|
|
|
Assert (fFoundInc);
|
|
return ppFilemapInc[-1];
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::AddTemplate
|
|
Adds a template to the list of templates that include this inc-file
|
|
|
|
Returns:
|
|
HRESULT
|
|
Side effects:
|
|
None
|
|
*/
|
|
HRESULT
|
|
CIncFile::AddTemplate
|
|
(
|
|
CTemplate* pTemplate
|
|
)
|
|
{
|
|
EnterCriticalSection(&m_csUpdate);
|
|
|
|
// Add the template to the list only if it does not exist
|
|
if (m_rgpTemplates.find(pTemplate) == -1)
|
|
{
|
|
if (FAILED(m_rgpTemplates.append(pTemplate)))
|
|
{
|
|
LeaveCriticalSection(&m_csUpdate);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
// Notify the debugger that template dependency has changed
|
|
// (Ignore failure)
|
|
//
|
|
if (g_pDebugApp)
|
|
{
|
|
IF_DEBUG(SCRIPT_DEBUGGER)
|
|
DBGPRINTF((DBG_CONTEXT, "AddTemplate: Notifying debugger to refresh breakpoints\n"));
|
|
|
|
InvokeDebuggerWithThreadSwitch
|
|
(
|
|
g_pDebugApp,
|
|
DEBUGGER_EVENT_ON_REFRESH_BREAKPOINT,
|
|
static_cast<IDebugDocument *>(this)
|
|
);
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&m_csUpdate);
|
|
return S_OK;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::RemoveTemplate
|
|
Removes a template from the template list
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
Compresses the removed template's ptr out of template ptrs array (see "back-copy", below)
|
|
Decrements template count
|
|
*/
|
|
void
|
|
CIncFile::RemoveTemplate
|
|
(
|
|
CTemplate* pTemplate
|
|
)
|
|
{
|
|
EnterCriticalSection(&m_csUpdate);
|
|
|
|
// find the template in list
|
|
int i = m_rgpTemplates.find(pTemplate);
|
|
|
|
// Remove the element (If we found it - possible that this is 2nd instance of #include and was previously removed)
|
|
if (i != -1)
|
|
{
|
|
m_rgpTemplates.removeAt(i);
|
|
|
|
// Notify the debugger that template dependency has changed
|
|
// (Ignore failure)
|
|
//
|
|
if (g_pDebugApp)
|
|
{
|
|
IF_DEBUG(SCRIPT_DEBUGGER)
|
|
DBGPRINTF((DBG_CONTEXT, "RemoveTemplate: Notifying debugger to refresh breakpoints\n"));
|
|
|
|
InvokeDebuggerWithThreadSwitch
|
|
(
|
|
g_pDebugApp,
|
|
DEBUGGER_EVENT_ON_REFRESH_BREAKPOINT,
|
|
static_cast<IDebugDocument *>(this)
|
|
);
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&m_csUpdate);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::FlushTemplates
|
|
Flushes all of this inc-file's templates from the global template cache
|
|
|
|
Returns:
|
|
TRUE if all templates flushed, FALSE if some left
|
|
Side effects:
|
|
None
|
|
*/
|
|
BOOL
|
|
CIncFile::FlushTemplates
|
|
(
|
|
)
|
|
{
|
|
/* NOTE we have a cross-dependency with RemoveTemplate() because the following call chain
|
|
occurs when an inc-file gets flushed:
|
|
|
|
CIncFileMap::Flush
|
|
CIncFile::FlushTemplates
|
|
CTemplateCacheManager::Flush
|
|
CTemplate::RemoveFromIncFiles
|
|
CIncFile::RemoveTemplate
|
|
|
|
The problem is that RemoveTemplate() updates m_cTemplates and m_rgTemplates, so these members
|
|
will not be stable during the loop within FlushTemplates.
|
|
|
|
To get around this, we make a local copy of m_rgTemplates.
|
|
*/
|
|
EnterCriticalSection(&m_csUpdate);
|
|
|
|
STACK_BUFFER( tempTemplates, 128 );
|
|
|
|
STACK_BUFFER( tempFile, MAX_PATH );
|
|
|
|
UINT cTemplates = m_rgpTemplates.length();
|
|
|
|
if (!tempTemplates.Resize(cTemplates * sizeof(CTemplate*))) {
|
|
|
|
// failed to get memory. The best we can do is return FALSE to indicate
|
|
// that not all templates where flushed.
|
|
|
|
LeaveCriticalSection(&m_csUpdate);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
CTemplate** rgpTemplates = static_cast<CTemplate**> (tempTemplates.QueryPtr());
|
|
memcpy(rgpTemplates, m_rgpTemplates.vec(), sizeof(CTemplate *) * cTemplates);
|
|
UINT cTemplatesFlushed = 0;
|
|
|
|
for(UINT i = 0; i < cTemplates; i++)
|
|
{
|
|
// If the template is ready now, flush it
|
|
if(rgpTemplates[i]->m_fReadyForUse && !(rgpTemplates[i]->m_fDontCache))
|
|
{
|
|
// bug 917: make a local copy of template file name, since the member gets freed part way through g_TemplateCache.Flush
|
|
TCHAR* szTemp = NULL;
|
|
szTemp = rgpTemplates[i]->GetSourceFileName();
|
|
if (szTemp)
|
|
{
|
|
|
|
if (!tempFile.Resize((_tcslen(szTemp) + 1)*sizeof(TCHAR))) {
|
|
|
|
// failed on this one. Continue and try to flush as many
|
|
// as we can.
|
|
continue;
|
|
}
|
|
TCHAR *szTemplateFile = (TCHAR *)tempFile.QueryPtr();
|
|
_tcscpy(szTemplateFile, szTemp);
|
|
g_TemplateCache.Flush(szTemplateFile, MATCH_ALL_INSTANCE_IDS);
|
|
cTemplatesFlushed++;
|
|
}
|
|
}
|
|
|
|
// If the template was not ready, we don't flush. It will probably
|
|
// pick up the current include file anyway
|
|
}
|
|
|
|
LeaveCriticalSection(&m_csUpdate);
|
|
|
|
return (cTemplates == cTemplatesFlushed);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CIncFile::OnIncFileDecache
|
|
|
|
Callback which we use to call onDestroy events in the debugger just before
|
|
we are removed from the IncFile cache.
|
|
|
|
REF COUNTING NOTE:
|
|
Since debugging client has a reference to the IDebugDocument, the include needs
|
|
to dis-associate with the debugger at a point in time before destruction.
|
|
Otherwise, the reference will never go to zero.
|
|
*/
|
|
void
|
|
CIncFile::OnIncFileDecache
|
|
(
|
|
)
|
|
{
|
|
if (m_CPTextEvents.FIsEmpty() || g_pDebugApp == NULL)
|
|
return;
|
|
|
|
IEnumConnections *pConnIterator;
|
|
if (SUCCEEDED(m_CPTextEvents.EnumConnections(&pConnIterator)))
|
|
{
|
|
CONNECTDATA ConnectData;
|
|
while (pConnIterator->Next(1, &ConnectData, NULL) == S_OK)
|
|
{
|
|
IDebugDocumentTextEvents *pTextEventSink;
|
|
if (SUCCEEDED(ConnectData.pUnk->QueryInterface(IID_IDebugDocumentTextEvents, reinterpret_cast<void **>(&pTextEventSink))))
|
|
{
|
|
InvokeDebuggerWithThreadSwitch(g_pDebugApp, DEBUGGER_ON_DESTROY, pTextEventSink);
|
|
pTextEventSink->Release();
|
|
}
|
|
ConnectData.pUnk->Release();
|
|
}
|
|
|
|
pConnIterator->Release();
|
|
}
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
CTemplate::CBuffer member functions
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CTemplate::CBuffer::CBuffer
|
|
Ctor
|
|
*/
|
|
CTemplate::CBuffer::CBuffer()
|
|
:
|
|
m_pItems(NULL),
|
|
m_cSlots(0),
|
|
m_cItems(0),
|
|
m_pbData(NULL),
|
|
m_cbData(0),
|
|
m_cbDataUsed(0)
|
|
{
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CBuffer::~CBuffer
|
|
Dtor
|
|
*/
|
|
CTemplate::CBuffer::~CBuffer()
|
|
{
|
|
if(m_pItems)
|
|
CTemplate::SmallFree(m_pItems);
|
|
if(m_pbData)
|
|
CTemplate::LargeFree(m_pbData);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CBuffer::Init
|
|
Inits a CBuffer
|
|
*/
|
|
void
|
|
CTemplate::CBuffer::Init
|
|
(
|
|
USHORT cSlots,
|
|
ULONG cbData
|
|
)
|
|
{
|
|
m_cSlots = cSlots;
|
|
m_cbData = cbData;
|
|
|
|
// Allocate space for storing byte range items
|
|
if(!(m_pItems = (CByteRange*) CTemplate::SmallMalloc(m_cSlots * sizeof(CByteRange))))
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
// Allocate space for storing local data, if there is any
|
|
if(m_cbData > 0)
|
|
{
|
|
if(!(m_pbData = (BYTE*) CTemplate::LargeMalloc(m_cbData)))
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CBuffer::Append
|
|
Appends to a CBuffer
|
|
*/
|
|
void
|
|
CTemplate::CBuffer::Append
|
|
(
|
|
const CByteRange& br, // byte range to append
|
|
BOOL fLocal, // append local?
|
|
UINT idSequence, // segment sequence id
|
|
CFileMap* pfilemap,
|
|
BOOL fLocalString // append local as a string? (length-prefixed, null-terminated)
|
|
)
|
|
{
|
|
// calc bytes required to store byte range; allow for length prefix and null if a local string
|
|
ULONG cbRequired = (ULONG)(br.m_cb + (fLocalString ? sizeof(br.m_cb) + 1 : 0));
|
|
|
|
// If caller passed a non-local zero-length byte range, no-op and return;
|
|
// allows callers to ignore byte range size
|
|
// NOTE we store empty local byte ranges - required by token list
|
|
if(!fLocal && br.m_cb == 0)
|
|
return;
|
|
|
|
if(fLocal)
|
|
{
|
|
if((m_cbData - m_cbDataUsed) < cbRequired)
|
|
{
|
|
// Reallocate space for storing local data - we grab twice what we had before
|
|
// or twice current requirement, whichever is more
|
|
m_cbData = 2 * (m_cbData > cbRequired ? m_cbData : cbRequired);
|
|
if(!(m_pbData = (BYTE*) CTemplate::LargeReAlloc(m_pbData, m_cbData)))
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
|
|
// if appending as a local string, copy length-prefix to buffer
|
|
if(fLocalString)
|
|
{
|
|
memcpy(m_pbData + m_cbDataUsed, &(br.m_cb), sizeof(br.m_cb));
|
|
m_cbDataUsed += sizeof(br.m_cb);
|
|
}
|
|
|
|
// copy data to buffer
|
|
memcpy(m_pbData + m_cbDataUsed, br.m_pb, br.m_cb);
|
|
m_cbDataUsed += br.m_cb;
|
|
|
|
// if appending as a local string, copy null terminator to buffer
|
|
if(fLocalString)
|
|
*(m_pbData + m_cbDataUsed++) = NULL;
|
|
|
|
}
|
|
|
|
if(m_cItems >= m_cSlots)
|
|
{
|
|
// Reallocate space for storing byte range items - we grab twice what we had before
|
|
m_cSlots *= 2;
|
|
if(!(m_pItems = (CByteRange*) CTemplate::SmallReAlloc(m_pItems, m_cSlots * sizeof(*m_pItems))))
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
|
|
// Set the (new) last item to this byte range
|
|
SetItem(m_cItems++, br, fLocal, idSequence, pfilemap, fLocalString);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CBuffer::GetItem
|
|
Gets an item from a CBuffer, as a byte range
|
|
|
|
Returns:
|
|
Nothing
|
|
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::CBuffer::GetItem
|
|
(
|
|
UINT i, // index of item
|
|
CByteRange& br // byte range containing returned item (out-parameter)
|
|
)
|
|
{
|
|
Assert(i < m_cItems);
|
|
|
|
// for local data, ptr is offset only; must add it to base ptr
|
|
br.m_pb = m_pItems[i].m_pb + (m_pItems[i].m_fLocal ? (DWORD_PTR) m_pbData : 0);
|
|
|
|
br.m_cb = m_pItems[i].m_cb;
|
|
br.m_fLocal = m_pItems[i].m_fLocal;
|
|
br.m_idSequence = m_pItems[i].m_idSequence;
|
|
br.m_pfilemap = m_pItems[i].m_pfilemap;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CBuffer::SetItem
|
|
Sets a CBuffer item to a new value
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
Throws error on non-existent item index
|
|
*/
|
|
void
|
|
CTemplate::CBuffer::SetItem
|
|
(
|
|
UINT i,
|
|
const CByteRange& br, // byte range to set item to
|
|
BOOL fLocal, // is item local in buffer?
|
|
UINT idSequence, // segment sequence id
|
|
CFileMap * pfilemap, // file where segment came from
|
|
BOOL fLocalString // append local as a string? (length-prefixed, null-terminated)
|
|
)
|
|
{
|
|
// If buffer item i does not exist, fail
|
|
if(i >= m_cSlots)
|
|
THROW(E_FAIL);
|
|
|
|
// for local data, store ptr as offset only - avoids fixup after realloc
|
|
// NOTE offset == data used offset - length of data - null terminator (if local string)
|
|
m_pItems[i].m_pb = (fLocal
|
|
? (BYTE*)(m_cbDataUsed - br.m_cb -
|
|
(fLocalString
|
|
? sizeof(BYTE)
|
|
: 0
|
|
))
|
|
: (BYTE*)br.m_pb);
|
|
|
|
m_pItems[i].m_cb = br.m_cb;
|
|
m_pItems[i].m_fLocal = fLocal;
|
|
m_pItems[i].m_idSequence = idSequence;
|
|
m_pItems[i].m_pfilemap = pfilemap;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CBuffer::PszLocal
|
|
Gets i-th locally-buffered string within the buffer.
|
|
|
|
Returns:
|
|
Ptr to locally-buffered string; NULL if not found
|
|
Side effects:
|
|
None
|
|
*/
|
|
LPSTR
|
|
CTemplate::CBuffer::PszLocal
|
|
(
|
|
UINT i // index of item to retrieve
|
|
)
|
|
{
|
|
CByteRange br;
|
|
|
|
GetItem(i, br);
|
|
|
|
if(!br.m_fLocal)
|
|
return NULL;
|
|
|
|
return (LPSTR) br.m_pb;
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
CTemplate::CScriptStore member functions
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CTemplate::CScriptStore::~CScriptStore
|
|
Destructor - frees memory
|
|
|
|
Returns:
|
|
nothing
|
|
Side effects:
|
|
none
|
|
*/
|
|
CTemplate::CScriptStore::~CScriptStore()
|
|
{
|
|
UINT i;
|
|
|
|
for(i = 0; i < m_cSegmentBuffers; i++)
|
|
delete m_ppbufSegments[i];
|
|
|
|
if(m_ppbufSegments != NULL)
|
|
CTemplate::SmallFree(m_ppbufSegments);
|
|
if(m_rgProgLangId != NULL)
|
|
CTemplate::SmallFree(m_rgProgLangId);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CScriptStore::Init
|
|
Inits the script store
|
|
|
|
Returns:
|
|
nothing
|
|
Side effects:
|
|
allocates memory
|
|
*/
|
|
HRESULT
|
|
CTemplate::CScriptStore::Init
|
|
(
|
|
LPCSTR szDefaultScriptLanguage,
|
|
CLSID *pCLSIDDefaultEngine
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
UINT i;
|
|
CByteRange brDefaultScriptLanguage;
|
|
|
|
// Check for NULL pointers - can happen if Application has invalid default lang
|
|
if (szDefaultScriptLanguage == NULL || pCLSIDDefaultEngine == NULL)
|
|
return TYPE_E_ELEMENTNOTFOUND;
|
|
|
|
/* init segment buffers count based on:
|
|
- two for default engine (one primary, one tagged)
|
|
- one each for other engines (tagged only)
|
|
*/
|
|
m_cSegmentBuffers = C_SCRIPTENGINESDEFAULT + 1;
|
|
|
|
// init segments buffers
|
|
if(NULL == (m_ppbufSegments = (CBuffer**) CTemplate::SmallMalloc(m_cSegmentBuffers * sizeof(CBuffer*))))
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto LExit;
|
|
}
|
|
|
|
for(i = 0; i < m_cSegmentBuffers; i++)
|
|
{
|
|
if(NULL == (m_ppbufSegments[i] = new CBuffer))
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto LExit;
|
|
}
|
|
m_ppbufSegments[i]->Init((C_SCRIPTSEGMENTSDEFAULT), 0);
|
|
}
|
|
|
|
// Append default engine to script store
|
|
brDefaultScriptLanguage.m_cb = strlen(szDefaultScriptLanguage);
|
|
brDefaultScriptLanguage.m_pb = (unsigned char *)szDefaultScriptLanguage;
|
|
hr = AppendEngine(brDefaultScriptLanguage, pCLSIDDefaultEngine, /* idSequence */ 0);
|
|
|
|
LExit:
|
|
return hr;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CScriptStore::AppendEngine
|
|
Appends a script engine to the script store
|
|
|
|
Returns:
|
|
HRESULT
|
|
Side effects:
|
|
None
|
|
*/
|
|
HRESULT
|
|
CTemplate::CScriptStore::AppendEngine
|
|
(
|
|
CByteRange& brEngine, // engine name
|
|
PROGLANG_ID* pProgLangId, // ptr to prog lang id - pass NULL to have this function get proglangid from registry
|
|
UINT idSequence // segment sequence id
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
USHORT cEngines; // count of engines
|
|
|
|
TRY
|
|
// if no engines yet, init engine names buffer
|
|
if(CountPreliminaryEngines() == 0)
|
|
m_bufEngineNames.Init(C_SCRIPTENGINESDEFAULT, 0);
|
|
|
|
// Append engine name to buffer
|
|
m_bufEngineNames.Append(brEngine, FALSE, idSequence, NULL);
|
|
|
|
CATCH(hrException)
|
|
hr = hrException;
|
|
goto LExit;
|
|
END_TRY
|
|
|
|
Assert(CountPreliminaryEngines() >= 1);
|
|
|
|
// malloc or realloc prog lang ids array
|
|
if((cEngines = CountPreliminaryEngines()) == 1)
|
|
m_rgProgLangId = (PROGLANG_ID*) CTemplate::SmallMalloc(cEngines * sizeof(PROGLANG_ID));
|
|
else
|
|
m_rgProgLangId = (PROGLANG_ID*) CTemplate::SmallReAlloc(m_rgProgLangId, cEngines * sizeof(PROGLANG_ID));
|
|
|
|
if(NULL == m_rgProgLangId)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto LExit;
|
|
}
|
|
|
|
if(NULL == pProgLangId)
|
|
// caller passed null progid ptr - get prog id from registry
|
|
hr = GetProgLangId(brEngine, &(m_rgProgLangId[cEngines - 1]));
|
|
else
|
|
// caller passed non-null progid ptr - set prog id from it
|
|
m_rgProgLangId[cEngines - 1] = *pProgLangId;
|
|
|
|
LExit:
|
|
return hr;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CScriptStore::IdEngineFromBr
|
|
Determines the id of a script engine from its engine name
|
|
|
|
Returns:
|
|
id of script engine whose name is passed in
|
|
Side effects:
|
|
appends a new script engine name to engine names buffer
|
|
*/
|
|
USHORT
|
|
CTemplate::CScriptStore::IdEngineFromBr
|
|
(
|
|
CByteRange& brEngine, // engine name
|
|
UINT idSequence // segment sequence id
|
|
)
|
|
{
|
|
Assert(!brEngine.IsNull()); // NOTE we trap/error null engine name earlier
|
|
|
|
USHORT cKnownEngines = CountPreliminaryEngines();
|
|
|
|
// search existing names for a match; return id if found
|
|
for(USHORT i = 0; i < cKnownEngines; i++)
|
|
{
|
|
Assert(m_bufEngineNames[i]);
|
|
Assert(m_bufEngineNames[i]->m_pb);
|
|
if(FByteRangesAreEqual(*(m_bufEngineNames[i]), brEngine))
|
|
return i;
|
|
}
|
|
|
|
// if not found by name try to find by engine id
|
|
// (some engines with different names share the same id, like J[ava]Script)
|
|
|
|
if (cKnownEngines > 0)
|
|
{
|
|
PROGLANG_ID ProgLandId;
|
|
|
|
// we will get the prog lang id again inside AppendEngine() but
|
|
// because it's cached and this only happens when > 1 engine, it's alright
|
|
|
|
if (SUCCEEDED(GetProgLangId(brEngine, &ProgLandId)))
|
|
{
|
|
for(i = 0; i < cKnownEngines; i++)
|
|
{
|
|
// If matches don't append -- just return the index
|
|
if (m_rgProgLangId[i] == ProgLandId)
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if we did not find engine among those already buffered
|
|
- append engine to script store
|
|
- realloc segment buffers array if necessary
|
|
- return index of last engine (the one we just appended)
|
|
*/
|
|
|
|
// append engine to script store
|
|
HRESULT hr = AppendEngine(brEngine, NULL, idSequence);
|
|
|
|
if(hr == TYPE_E_ELEMENTNOTFOUND)
|
|
// if prog lang not found, throw bad prog lang error id
|
|
THROW(IDE_TEMPLATE_BAD_PROGLANG);
|
|
else if(FAILED(hr))
|
|
// other failure: re-throw hresult
|
|
THROW(hr);
|
|
|
|
// realloc segment buffers array if necessary
|
|
if(CountPreliminaryEngines() > (m_cSegmentBuffers - 1))
|
|
{
|
|
// increment count of segment buffers
|
|
m_cSegmentBuffers++;
|
|
Assert(CountPreliminaryEngines() == m_cSegmentBuffers - 1);
|
|
|
|
// realloc array of ptrs
|
|
if(NULL == (m_ppbufSegments = (CBuffer**) CTemplate::SmallReAlloc(m_ppbufSegments, m_cSegmentBuffers * sizeof(CBuffer*))))
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
// allocate the new buffer
|
|
if(NULL == (m_ppbufSegments[m_cSegmentBuffers - 1] = new CBuffer))
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
// init the new buffer
|
|
m_ppbufSegments[m_cSegmentBuffers - 1]->Init(C_SCRIPTSEGMENTSDEFAULT, 0);
|
|
}
|
|
|
|
// return index of last engine (the one we just appended)
|
|
return (CountPreliminaryEngines() - 1);
|
|
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CScriptStore::AppendScript
|
|
Appends a script/engine pair to the store.
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::CScriptStore::AppendScript
|
|
(
|
|
CByteRange& brScript, // script text
|
|
CByteRange& brEngine, // script engine name
|
|
BOOLB fPrimary, // primary or tagged script?
|
|
UINT idSequence, // segment sequence id
|
|
CFileMap* pfilemapCurrent
|
|
)
|
|
{
|
|
USHORT iBuffer; // buffer id
|
|
|
|
Assert(fPrimary || !brEngine.IsNull()); // NOTE we trap/error null engine name earlier
|
|
Assert(m_bufEngineNames[0]); // page's primary engine must be known by this point
|
|
Assert(m_bufEngineNames[0]->m_pb);
|
|
|
|
if(fPrimary)
|
|
// if primary script (not tagged), buffer id is 0
|
|
iBuffer = 0;
|
|
else if((!fPrimary) && FByteRangesAreEqual(brEngine, /* bug 1008: primary script engine name */ *(m_bufEngineNames[0])))
|
|
// if tagged script and engine is primary, buffer id is 1
|
|
iBuffer = 1;
|
|
else
|
|
// else, buffer id is engine id plus 1
|
|
iBuffer = IdEngineFromBr(brEngine, idSequence) + 1;
|
|
|
|
// append script segment to iBuffer-th segments buffer
|
|
m_ppbufSegments[iBuffer]->Append(brScript, FALSE, idSequence, pfilemapCurrent);
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
CTemplate::CObjectInfoStore member functions
|
|
*/
|
|
/* ============================================================================
|
|
CTemplate::CObjectInfoStore::~CObjectInfoStore
|
|
*/
|
|
CTemplate::CObjectInfoStore::~CObjectInfoStore
|
|
(
|
|
)
|
|
{
|
|
if(m_pObjectInfos)
|
|
CTemplate::SmallFree(m_pObjectInfos);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CObjectInfoStore::Init
|
|
Inits the object-info store
|
|
*/
|
|
void
|
|
CTemplate::CObjectInfoStore::Init()
|
|
{
|
|
m_bufObjectNames.Init(C_OBJECTINFOS_DEFAULT, 0);
|
|
|
|
// init object-infos array
|
|
if(NULL == (m_pObjectInfos = (CObjectInfo*) CTemplate::SmallMalloc(m_bufObjectNames.CountSlots() * sizeof(CObjectInfo))))
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CObjectInfoStore::AppendObject
|
|
Appends an object-info to the object-info store
|
|
*/
|
|
void
|
|
CTemplate::CObjectInfoStore::AppendObject
|
|
(
|
|
CByteRange& brObjectName,
|
|
CLSID clsid,
|
|
CompScope scope,
|
|
CompModel model,
|
|
UINT idSequence
|
|
)
|
|
{
|
|
|
|
USHORT iObject = m_bufObjectNames.Count();
|
|
if(iObject >= m_bufObjectNames.CountSlots())
|
|
{
|
|
// Reallocate space for storing object-infos - we grab twice what we had before
|
|
// NOTE we keep no object count in CObjectInfoStore, but instead use count in object names buffer
|
|
(m_pObjectInfos = (CObjectInfo*)CTemplate::SmallReAlloc(m_pObjectInfos,
|
|
2 * m_bufObjectNames.CountSlots() * sizeof(CObjectInfo)));
|
|
|
|
if (m_pObjectInfos == NULL)
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
|
|
m_pObjectInfos[iObject].m_clsid = clsid;
|
|
m_pObjectInfos[iObject].m_scope = scope;
|
|
m_pObjectInfos[iObject].m_model = model;
|
|
|
|
m_bufObjectNames.Append(brObjectName, FALSE, idSequence, NULL);
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
CTemplate::CWorkStore member functions
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CTemplate::CWorkStore::CWorkStore
|
|
Constructor
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
CTemplate::CWorkStore::CWorkStore
|
|
(
|
|
)
|
|
:
|
|
m_idCurSequence(0),
|
|
m_fPageCommandsExecuted(FALSE),
|
|
m_fPageCommandsAllowed(TRUE),
|
|
m_szWriteBlockOpen(g_szWriteBlockOpen),
|
|
m_szWriteBlockClose(g_szWriteBlockClose),
|
|
m_szWriteOpen(g_szWriteOpen),
|
|
m_szWriteClose(g_szWriteClose),
|
|
m_pbPrevSource(NULL),
|
|
m_cPrevSourceLines(0),
|
|
m_hPrevFile (NULL)
|
|
{ }
|
|
|
|
/* ============================================================================
|
|
CTemplate::CWorkStore::~CWorkStore
|
|
Destructor
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
CTemplate::CWorkStore::~CWorkStore
|
|
(
|
|
)
|
|
{
|
|
/* if language element ptrs are anything but their constant defaults or null,
|
|
they must have been allocated during compilation - free them now
|
|
*/
|
|
if(m_szWriteBlockOpen != g_szWriteBlockOpen && m_szWriteBlockOpen != NULL)
|
|
CTemplate::SmallFree(m_szWriteBlockOpen);
|
|
|
|
if(m_szWriteBlockClose != g_szWriteBlockClose && m_szWriteBlockClose != NULL)
|
|
CTemplate::SmallFree(m_szWriteBlockClose);
|
|
|
|
if(m_szWriteOpen != g_szWriteOpen && m_szWriteOpen != NULL)
|
|
CTemplate::SmallFree(m_szWriteOpen);
|
|
|
|
if(m_szWriteClose != g_szWriteClose && m_szWriteClose != NULL)
|
|
CTemplate::SmallFree(m_szWriteClose);
|
|
}
|
|
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::CWorkStore::Init
|
|
Inits the workstore
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::CWorkStore::Init
|
|
(
|
|
)
|
|
{
|
|
/*
|
|
NOTE init we the scriptstore separately from rest of workstore
|
|
because try-catch in CTemplate::Init() apparently doesn't work to detect
|
|
bogus script engine name; we need to get an hr back instead.
|
|
|
|
m_ScriptStore.Init(brDefaultEngine);
|
|
*/
|
|
m_ObjectInfoStore.Init();
|
|
m_bufHTMLSegments.Init(C_HTMLSEGMENTSDEFAULT, 0);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CWorkStore::CRequiredScriptEngines
|
|
Returns the count of script engines in the script store that are required
|
|
to run the template.
|
|
|
|
NOTE this function is part of the fix for bug 933
|
|
|
|
Returns:
|
|
Count of non-empty script engines
|
|
Side effects:
|
|
None
|
|
*/
|
|
USHORT
|
|
CTemplate::CWorkStore::CRequiredScriptEngines
|
|
(
|
|
BOOL fGlobalAsa // bug 1394: is template global.asa?
|
|
)
|
|
{
|
|
USHORT cPreliminaryEngines = m_ScriptStore.CountPreliminaryEngines();
|
|
USHORT cRequiredEngines = 0;
|
|
|
|
for(USHORT i = 0; i < cPreliminaryEngines; i++)
|
|
{
|
|
if(FScriptEngineRequired(i, fGlobalAsa))
|
|
cRequiredEngines++;
|
|
}
|
|
|
|
return cRequiredEngines;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CWorkStore::FScriptEngineRequired
|
|
Is a given preliminary script engine required to run the template?
|
|
|
|
NOTE this function is part of the fix for bug 933
|
|
|
|
Returns:
|
|
TRUE or FALSE
|
|
Side effects:
|
|
None
|
|
*/
|
|
BOOLB
|
|
CTemplate::CWorkStore::FScriptEngineRequired
|
|
(
|
|
USHORT idEnginePrelim,
|
|
BOOL fGlobalAsa // bug 1394: is template global.asa?
|
|
)
|
|
{
|
|
if(idEnginePrelim == 0)
|
|
return ( // primary engine (id 0) required if
|
|
(m_ScriptStore.m_ppbufSegments[0]->Count() > 0) // ... script buffer 0 has segments
|
|
|| (m_ScriptStore.m_ppbufSegments[1]->Count() > 0) // ... or script buffer 1 has segments
|
|
|| ((m_bufHTMLSegments.Count() > 0) && !fGlobalAsa) // ... or html buffer has segments and (bug 1394) template is not global.asa
|
|
);
|
|
|
|
// non-primary engine required if script buffer id+1 has segments
|
|
return (m_ScriptStore.m_ppbufSegments[idEnginePrelim + 1]->Count() > 0);
|
|
}
|
|
|
|
|
|
/* ****************************************************************************
|
|
CTemplate::CFileMap member functions
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::CFileMap
|
|
Constructor
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
CTemplate::CFileMap::CFileMap()
|
|
:
|
|
m_szPathInfo(NULL),
|
|
m_szPathTranslated(NULL),
|
|
m_pfilemapSibling(NULL),
|
|
m_pfilemapChild(NULL),
|
|
m_hFile(NULL),
|
|
m_hMap(NULL),
|
|
m_pbStartOfFile(NULL),
|
|
m_pIncFile(NULL),
|
|
m_pSecurityDescriptor(NULL),
|
|
m_dwSecDescSize(0),
|
|
m_cChars(0),
|
|
m_pDME(NULL)
|
|
{
|
|
m_ftLastWriteTime.dwLowDateTime = 0;
|
|
m_ftLastWriteTime.dwHighDateTime = 0;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::~CFileMap
|
|
Destructor
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
CTemplate::CFileMap::~CFileMap()
|
|
{
|
|
if (m_pDME)
|
|
{
|
|
m_pDME->Release();
|
|
m_pDME = NULL;
|
|
}
|
|
if(m_szPathInfo != NULL)
|
|
CTemplate::SmallFree(m_szPathInfo);
|
|
if(m_szPathTranslated != NULL)
|
|
CTemplate::SmallFree(m_szPathTranslated);
|
|
if(m_pSecurityDescriptor != NULL)
|
|
CTemplate::SmallFree(m_pSecurityDescriptor);
|
|
if (m_pIncFile != NULL)
|
|
m_pIncFile->Release();
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::MapFile
|
|
Memory-maps a file.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
Throws **overloaded** exception on error: exception code can sometimes be
|
|
an error message id, sometimes a true exception. Caller must handle.
|
|
*/
|
|
void
|
|
CTemplate::CFileMap::MapFile
|
|
(
|
|
LPCTSTR szFileSpec, // file spec for this file
|
|
LPCTSTR szApplnPath, // application path (in case its global.asa)
|
|
CFileMap* pfilemapParent, // ptr to filemap of parent file
|
|
BOOL fVirtual, // is file spec virtual or relative?
|
|
CHitObj* pHitObj, // ptr to template's hit object
|
|
BOOL fGlobalAsa // is this file the global.asa file?
|
|
)
|
|
{
|
|
BOOL fMustNormalize = TRUE;
|
|
BOOL fImpersonatedUser = FALSE;
|
|
HANDLE hVirtIncImpToken = NULL;
|
|
HANDLE hCurImpToken = NULL;
|
|
|
|
Assert((pfilemapParent != NULL) || (pHitObj->PIReq() != NULL) || fGlobalAsa);
|
|
|
|
/* three possible cases:
|
|
1) we are processing global.asa file
|
|
2) we are processing the "main" .asp file
|
|
3) we are processing an include file
|
|
*/
|
|
if(fGlobalAsa)
|
|
{
|
|
// case 1) we are processing global.asa file
|
|
Assert(pHitObj->GlobalAspPath());
|
|
|
|
DWORD cchPathTranslated = _tcslen(pHitObj->GlobalAspPath());
|
|
m_szPathTranslated = (TCHAR *)CTemplate::SmallMalloc((cchPathTranslated+1)*sizeof(TCHAR));
|
|
if (!m_szPathTranslated)
|
|
THROW(E_OUTOFMEMORY);
|
|
_tcscpy(m_szPathTranslated, pHitObj->GlobalAspPath());
|
|
|
|
DWORD cchPathInfo = _tcslen(szApplnPath) + 11; // "/global.asa"
|
|
m_szPathInfo = (TCHAR *)CTemplate::SmallMalloc((cchPathInfo+1) * sizeof(TCHAR));
|
|
if (!m_szPathInfo)
|
|
THROW(E_OUTOFMEMORY);
|
|
_tcscpy(strcpyEx(m_szPathInfo, szApplnPath), _T("/global.asa"));
|
|
|
|
// no need to normalize in this case, since global.asa path is already normalized
|
|
Assert(IsNormalized((const TCHAR*)m_szPathTranslated));
|
|
fMustNormalize = FALSE;
|
|
m_fHasVirtPath = TRUE;
|
|
}
|
|
else if(pfilemapParent == NULL)
|
|
{
|
|
// case 2) we are processing the "main" .asp file: get path-info and path-tran from ecb
|
|
Assert(pHitObj->PIReq());
|
|
|
|
TCHAR *szVirtPath = pHitObj->PSzCurrTemplateVirtPath();
|
|
TCHAR *szPhysPath = pHitObj->PSzCurrTemplatePhysPath();
|
|
|
|
m_szPathInfo = static_cast<LPTSTR>(CTemplate::SmallMalloc((_tcslen(szVirtPath) + 1)*sizeof(TCHAR)));
|
|
m_szPathTranslated = static_cast<LPTSTR>(CTemplate::SmallMalloc((_tcslen(szPhysPath) + 1)*sizeof(TCHAR)));
|
|
if (!m_szPathInfo || !m_szPathTranslated)
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
_tcscpy(m_szPathInfo, szVirtPath);
|
|
_tcscpy(m_szPathTranslated, szPhysPath);
|
|
|
|
// no need to normalize in this case, since ecb's path-tran is already normalized
|
|
Assert(IsNormalized((const TCHAR*)m_szPathTranslated));
|
|
fMustNormalize = FALSE;
|
|
m_fHasVirtPath = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* case 3) we are processing an include file: resolve filespec into path-info and path-tran
|
|
based on whether file was included with VIRTUAL tag or FILE tag
|
|
*/
|
|
Assert(szFileSpec);
|
|
|
|
// in this case, we don't know path lengths up front so we alloc the max and realloc below
|
|
m_szPathInfo = static_cast<LPTSTR> (CTemplate::SmallMalloc((MAX_PATH + 1)*sizeof(TCHAR)));
|
|
m_szPathTranslated = static_cast<LPTSTR> (CTemplate::SmallMalloc((MAX_PATH + 1)*sizeof(TCHAR)));
|
|
if (!m_szPathInfo || !m_szPathTranslated)
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
STACK_BUFFER(tempPathT, (MAX_PATH+1)*sizeof(TCHAR) );
|
|
|
|
if (!tempPathT.Resize((_tcslen(szFileSpec) + 1)*sizeof(TCHAR))) {
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
|
|
LPTSTR szPathTranslatedT = (TCHAR *)tempPathT.QueryPtr(); // temp path-tran
|
|
|
|
if(fVirtual) {
|
|
DWORD dwSzLength = tempPathT.QuerySize(); // length of path string buffer
|
|
|
|
if (_tcslen(szFileSpec) > MAX_PATH)
|
|
THROW(E_FAIL);
|
|
|
|
// VIRTUAL: path-info is simply virtual filespec
|
|
_tcscpy(m_szPathInfo, szFileSpec);
|
|
|
|
// VIRTUAL: path-tran is translation of path-info
|
|
_tcscpy(szPathTranslatedT, m_szPathInfo);
|
|
|
|
if (!pHitObj->PIReq()->MapUrlToPath(szPathTranslatedT, &dwSzLength))
|
|
THROW(E_FAIL);
|
|
|
|
// Check the translated path for a UNC specified path
|
|
|
|
if ((dwSzLength >= (2*sizeof(TCHAR)))
|
|
&& (szPathTranslatedT[0] == _T('\\'))
|
|
&& (szPathTranslatedT[1] == _T('\\'))) {
|
|
|
|
// if UNC, then ask WAM for the impersonation token for
|
|
// this UNC VRoot. Silently fail.
|
|
|
|
if (pHitObj->PIReq()->ServerSupportFunction(
|
|
HSE_REQ_GET_VIRTUAL_PATH_TOKEN,
|
|
(void *)szFileSpec,
|
|
(DWORD *) &hVirtIncImpToken,
|
|
NULL)) {
|
|
|
|
// set the impersonation token and note that we did so
|
|
// NOTE - there is intentionally no error checking. The
|
|
// assumption being that we are doing best effort at the
|
|
// impersonation because throwing an error here could be
|
|
// tricky for the user to interpret the problem. However,
|
|
// if the impersonation fails, and ASP can still open the
|
|
// file (e.g. passthru authentication), then everyone's
|
|
// happy.
|
|
|
|
AspDoRevertHack(&hCurImpToken);
|
|
|
|
fImpersonatedUser = ImpersonateLoggedOnUser(hVirtIncImpToken)
|
|
? TRUE
|
|
: FALSE;
|
|
|
|
if (!fImpersonatedUser)
|
|
{
|
|
AspUndoRevertHack(&hCurImpToken);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_fHasVirtPath = TRUE;
|
|
}
|
|
else
|
|
{
|
|
TCHAR szParentDir[MAX_PATH], *szT;
|
|
_tcscpy(szParentDir, pfilemapParent->m_szPathInfo);
|
|
if ((szT = _tcsrchr(szParentDir, _T('/'))) != NULL)
|
|
*szT = _T('\0');
|
|
|
|
// If we don't allow parent paths, we can save lots of time (Always have a valid virtual path)
|
|
if (!pHitObj->QueryAppConfig()->fEnableParentPaths())
|
|
{
|
|
int strlen_szParentDir = (int)(szT - szParentDir);
|
|
if ((strlen_szParentDir + 1 + _tcslen(szFileSpec)) > MAX_PATH)
|
|
THROW(E_FAIL);
|
|
|
|
strcpyEx(strcpyEx(strcpyEx(m_szPathInfo, szParentDir), _T("/")), szFileSpec);
|
|
m_fHasVirtPath = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// NOTE: If we must translate ".." paths, there is no need to verify them (by remapping)
|
|
// because: If the file does not exist, that case will show up when the file is mapped
|
|
// If we ".." ourselves out of the vroot space, (out of the app or into another app)
|
|
// DotPathToPath will detect this.
|
|
//
|
|
if (DotPathToPath(m_szPathInfo, szFileSpec, szParentDir))
|
|
m_fHasVirtPath = TRUE;
|
|
else
|
|
{
|
|
GetPathFromParentAndFilespec(pfilemapParent->m_szPathTranslated, szFileSpec, &m_szPathInfo);
|
|
m_fHasVirtPath = FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
GetPathFromParentAndFilespec(pfilemapParent->m_szPathTranslated, szFileSpec, &szPathTranslatedT);
|
|
}
|
|
|
|
// bug 1214: get canonical path-tran, without . and ..
|
|
// CONSIDER check for . or .. in name before calling GetFullPathName? UNCs? what else?
|
|
GetFullPathName(
|
|
szPathTranslatedT, // LPCSTR lpFileName, // address of name of file to find path for
|
|
MAX_PATH + 1, // DWORD nBufferLength, // size, in characters, of path buffer
|
|
m_szPathTranslated, // LPSTR lpBuffer, // address of path buffer
|
|
NULL // LPSTR *lpFilePart // address of filename in path
|
|
);
|
|
|
|
// realloc path strings to only use required memory (see note above)
|
|
m_szPathInfo = static_cast<LPTSTR> (CTemplate::SmallReAlloc(m_szPathInfo, (_tcslen(m_szPathInfo) + 1)*sizeof(TCHAR)));
|
|
m_szPathTranslated = static_cast<LPTSTR> (CTemplate::SmallReAlloc(m_szPathTranslated, (_tcslen(m_szPathTranslated) + 1)*sizeof(TCHAR)));
|
|
if (!m_szPathInfo || !m_szPathTranslated) {
|
|
|
|
if (fImpersonatedUser)
|
|
AspUndoRevertHack(&hCurImpToken);
|
|
if (hVirtIncImpToken)
|
|
CloseHandle(hVirtIncImpToken);
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
}
|
|
|
|
// if required, normalize path-tran so that
|
|
// a) cyclic include check can ignore case; b) inc-file cache lookups will work
|
|
if(fMustNormalize)
|
|
Normalize(m_szPathTranslated);
|
|
|
|
Assert(IsNormalized(m_szPathTranslated));
|
|
|
|
// Bug 99071: Attempt to open the file **BEFORE** we add it to the tree of file
|
|
// dependencies. Otherwise if it fails to open, we will have
|
|
// dangling references. Since FCyclicInclude depends on us adding
|
|
// to the tree, if it is cyclic, we need to unmap then. Since that
|
|
// is a very uncommon error case, the extra overhead is probably OK
|
|
//
|
|
// RemapFile will throw if it fails. If the exception is that the source file is empty
|
|
// and we are trying to process an include file, we will handle the exception here.
|
|
// in all other cases, rethrow the exception. We do this so that an empty include file
|
|
// will be harmless, but an empty primary file will fail.
|
|
TRY
|
|
|
|
RemapFile();
|
|
|
|
CATCH(hrException)
|
|
|
|
if (hrException != E_SOURCE_FILE_IS_EMPTY || pfilemapParent == NULL) {
|
|
if (fImpersonatedUser)
|
|
AspUndoRevertHack(&hCurImpToken);
|
|
if (hVirtIncImpToken)
|
|
CloseHandle(hVirtIncImpToken);
|
|
THROW(hrException);
|
|
}
|
|
|
|
END_TRY
|
|
|
|
if (fImpersonatedUser)
|
|
AspUndoRevertHack(&hCurImpToken);
|
|
if (hVirtIncImpToken)
|
|
CloseHandle(hVirtIncImpToken);
|
|
|
|
// Create the tree structure for this file
|
|
if (pfilemapParent != NULL)
|
|
{
|
|
// See if this file is already included once on this level. (Don't show duplicates in the
|
|
// debugger tree view)
|
|
//
|
|
BOOL fDuplicateExists = FALSE;
|
|
CFileMap *pFilemap = pfilemapParent->m_pfilemapChild;
|
|
while (pFilemap != NULL && !fDuplicateExists)
|
|
{
|
|
if (_tcscmp(pFilemap->m_szPathTranslated, m_szPathTranslated) == 0)
|
|
fDuplicateExists = TRUE;
|
|
|
|
pFilemap = pFilemap->m_fHasSibling? pFilemap->m_pfilemapSibling : NULL;
|
|
}
|
|
|
|
// If the include file is #include'd more than once, don't add it as a sibling.
|
|
// Rather orphan the pfilemap and just set the parent pointer.
|
|
//
|
|
if (!fDuplicateExists)
|
|
{
|
|
if (pfilemapParent->m_pfilemapChild == NULL)
|
|
pfilemapParent->m_pfilemapChild = this;
|
|
else
|
|
pfilemapParent->m_pfilemapChild->AddSibling(this);
|
|
}
|
|
}
|
|
|
|
// in both of the above code paths, we are always added as the LAST child, (or we are an orphan node)
|
|
// so it is safe to set the parent without calling SetParent()
|
|
m_fHasSibling = FALSE; // Paranoia
|
|
m_pfilemapParent = pfilemapParent;
|
|
|
|
// hurl if this file is being included by itself (perhaps indirectly)
|
|
if(FCyclicInclude(m_szPathTranslated)) {
|
|
UnmapFile();
|
|
THROW(IDE_TEMPLATE_CYCLIC_INCLUDE);
|
|
}
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::RemapFile
|
|
map a file that was previously mapped.
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
Throws **overloaded** exception on error: exception code can sometimes be
|
|
an error message id, sometimes a true exception. Caller must handle.
|
|
|
|
Does not decrypt EASPs on remapping. Caller must decrypt if required. This
|
|
function is called by the debugger, and the debugger does not allow access
|
|
to decrypted files, so decryption is a waste of time.
|
|
*/
|
|
void
|
|
CTemplate::CFileMap::RemapFile
|
|
(
|
|
)
|
|
{
|
|
WIN32_FILE_ATTRIBUTE_DATA fad; // win32 file attributes data structure
|
|
|
|
if (FIsMapped())
|
|
return;
|
|
|
|
if(INVALID_HANDLE_VALUE == (m_hFile =
|
|
CreateFile(
|
|
m_szPathTranslated, // file name
|
|
GENERIC_READ, // access (read-write) mode
|
|
FILE_SHARE_READ, // share mode
|
|
NULL, // pointer to security descriptor
|
|
OPEN_EXISTING, // how to create
|
|
FILE_ATTRIBUTE_NORMAL, // file attributes
|
|
NULL // handle to file with attributes to copy
|
|
)))
|
|
{
|
|
DWORD dwLastError = GetLastError();
|
|
if(dwLastError == ERROR_ACCESS_DENIED)
|
|
{
|
|
// typically, we end up here if the user has no permissions on the file
|
|
// bug 1007: however, we also end up here if the user gave us a directory name, instead of a file name
|
|
|
|
if(FAILED(AspGetFileAttributes(m_szPathTranslated, &fad)))
|
|
{
|
|
// bug 1495: file in a secured directory will end up here - we need to re-GetLastError to see if access is denied
|
|
dwLastError = GetLastError();
|
|
if(dwLastError == ERROR_ACCESS_DENIED)
|
|
{
|
|
THROW(E_USER_LACKS_PERMISSIONS);
|
|
}
|
|
// GetFileAttributes call failed; don't know why
|
|
THROW(E_FAIL);
|
|
}
|
|
else if(FILE_ATTRIBUTE_DIRECTORY & fad.dwFileAttributes)
|
|
{
|
|
// bug 1007: the user gave us a directory name
|
|
#if UNICODE
|
|
DBGPRINTF((DBG_CONTEXT, "Failed to open file %S because it is a directory.\n", m_szPathTranslated));
|
|
#else
|
|
DBGPRINTF((DBG_CONTEXT, "Failed to open file %s because it is a directory.\n", m_szPathTranslated));
|
|
#endif
|
|
THROW(E_COULDNT_OPEN_SOURCE_FILE);
|
|
}
|
|
else
|
|
{
|
|
THROW(E_USER_LACKS_PERMISSIONS);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if DBG
|
|
char szError[128];
|
|
if (!FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL,
|
|
dwLastError,
|
|
0L, // lang ID - defaults to LANG_NEUTRAL
|
|
szError,
|
|
sizeof szError,
|
|
NULL) )
|
|
{
|
|
sprintf(szError, "%d", dwLastError);
|
|
}
|
|
#if UNICODE
|
|
DBGPRINTF((DBG_CONTEXT, "Failed to open file %S\n", m_szPathTranslated));
|
|
#else
|
|
DBGPRINTF((DBG_CONTEXT, "Failed to open file %s\n", m_szPathTranslated));
|
|
#endif
|
|
DBGPRINTF((DBG_CONTEXT, " The error returned was: %s\n", szError));
|
|
#endif
|
|
THROW(E_COULDNT_OPEN_SOURCE_FILE);
|
|
}
|
|
}
|
|
|
|
// Get the file's last access time. Only do this for NT
|
|
if (Glob(fWinNT))
|
|
{
|
|
if (SUCCEEDED(AspGetFileAttributes(m_szPathTranslated, &fad)))
|
|
{
|
|
m_ftLastWriteTime.dwLowDateTime = fad.ftLastWriteTime.dwLowDateTime;
|
|
m_ftLastWriteTime.dwHighDateTime = fad.ftLastWriteTime.dwHighDateTime;
|
|
}
|
|
}
|
|
|
|
// get file's security descriptor
|
|
if(!GetSecurityDescriptor())
|
|
THROW(E_COULDNT_OPEN_SOURCE_FILE);
|
|
|
|
// map the file
|
|
if(NULL == (m_hMap =
|
|
CreateFileMapping(
|
|
m_hFile, // handle to file to map
|
|
NULL, // optional security attributes
|
|
PAGE_READONLY, // protection for mapping object
|
|
0, // high-order 32 bits of object size
|
|
0, // low-order 32 bits of object size
|
|
NULL // name of file-mapping object
|
|
)))
|
|
{
|
|
if (SUCCEEDED(AspGetFileAttributes(m_szPathTranslated, &fad)))
|
|
{
|
|
if(fad.nFileSizeHigh == 0 && fad.nFileSizeLow == 0)
|
|
{
|
|
#if UNICODE
|
|
DBGPRINTF((DBG_CONTEXT, "Empty source file %S\n", m_szPathTranslated));
|
|
#else
|
|
DBGPRINTF((DBG_CONTEXT, "Empty source file %s\n", m_szPathTranslated));
|
|
#endif
|
|
THROW(E_SOURCE_FILE_IS_EMPTY);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
THROW(E_COULDNT_OPEN_SOURCE_FILE);
|
|
}
|
|
}
|
|
|
|
// set file's start-of-file ptr
|
|
if(NULL == (m_pbStartOfFile =
|
|
(PBYTE) MapViewOfFile(
|
|
m_hMap, // file-mapping object to map into address space
|
|
FILE_MAP_READ, // access mode
|
|
0, // high-order 32 bits of file offset
|
|
0, // low-order 32 bits of file offset
|
|
0 // number of bytes to map
|
|
)))
|
|
THROW(E_COULDNT_OPEN_SOURCE_FILE);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::SetParent
|
|
Set the parent for this filemap
|
|
*/
|
|
void
|
|
CTemplate::CFileMap::SetParent
|
|
(
|
|
CFileMap* pfilemapParent
|
|
)
|
|
{
|
|
CFileMap *pfilemap = this;
|
|
|
|
while (pfilemap->m_fHasSibling)
|
|
pfilemap = pfilemap->m_pfilemapSibling;
|
|
|
|
pfilemap->m_pfilemapParent = pfilemapParent;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::GetParent
|
|
Get the parent for this filemap
|
|
*/
|
|
CTemplate::CFileMap*
|
|
CTemplate::CFileMap::GetParent
|
|
(
|
|
)
|
|
{
|
|
register CFileMap *pfilemap = this;
|
|
|
|
while (pfilemap->m_fHasSibling)
|
|
pfilemap = pfilemap->m_pfilemapSibling;
|
|
|
|
return pfilemap->m_pfilemapParent;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::AddSibling
|
|
Add a new node as a sibling of this
|
|
*/
|
|
void
|
|
CTemplate::CFileMap::AddSibling
|
|
(
|
|
register CFileMap* pfilemapSibling
|
|
)
|
|
{
|
|
register CFileMap *pfilemap = this;
|
|
|
|
while (pfilemap->m_fHasSibling)
|
|
pfilemap = pfilemap->m_pfilemapSibling;
|
|
|
|
pfilemapSibling->m_fHasSibling = FALSE;
|
|
pfilemapSibling->m_pfilemapParent = pfilemap->m_pfilemapParent;
|
|
|
|
pfilemap->m_fHasSibling = TRUE;
|
|
pfilemap->m_pfilemapSibling = pfilemapSibling;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::FCyclicInclude
|
|
Is a file among this filemap's ancestors? (i.e. does it occur anywhere
|
|
in the filemap's parent chain?)
|
|
|
|
Returns
|
|
TRUE or FALSE
|
|
Side effects
|
|
None
|
|
*/
|
|
BOOL
|
|
CTemplate::CFileMap::FCyclicInclude
|
|
(
|
|
LPCTSTR szPathTranslated
|
|
)
|
|
{
|
|
CFileMap *pfilemapParent = GetParent();
|
|
|
|
if(pfilemapParent == NULL)
|
|
return FALSE;
|
|
|
|
// NOTE we ignore case because path-tran was normalized
|
|
if(_tcscmp(szPathTranslated, pfilemapParent->m_szPathTranslated) == 0)
|
|
return TRUE;
|
|
|
|
return pfilemapParent->FCyclicInclude(szPathTranslated);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::GetSecurityDescriptor
|
|
Gets a file's security descriptor
|
|
|
|
Returns
|
|
TRUE if we got security descriptor, else FALSE
|
|
Side effects
|
|
allocates memory
|
|
*/
|
|
BOOL
|
|
CTemplate::CFileMap::GetSecurityDescriptor
|
|
(
|
|
)
|
|
// ACLs: the following code should in future be shared with IIS (see creatfil.cxx in IIS project)
|
|
{
|
|
BOOL fRet = TRUE; // return value
|
|
BOOL fGotSecurityDescriptor; // did we get a security descriptor?
|
|
DWORD dwSecDescSizeNeeded = 0; // required size of security descriptor
|
|
DWORD dwLastError; // last error code
|
|
const SECURITY_INFORMATION si = OWNER_SECURITY_INFORMATION // security info struct
|
|
| GROUP_SECURITY_INFORMATION
|
|
| DACL_SECURITY_INFORMATION;
|
|
|
|
// we dont support security on win95
|
|
if (!Glob(fWinNT))
|
|
return(TRUE);
|
|
|
|
// get required buffer size before malloc
|
|
// NOTE this costs us an extra system call, but means the cached template will often use less memory
|
|
// we must do this up front by passing 0 buffer size because when the call succeeds it returns
|
|
// dwSecDescSizeNeeded == 0 (i.e. we can't realloc to shrink after successful call)
|
|
GetKernelObjectSecurity(
|
|
m_hFile, // handle of object to query
|
|
si, // requested information
|
|
NULL, // address of security descriptor
|
|
0, // size of buffer for security descriptor
|
|
&dwSecDescSizeNeeded // address of required size of buffer
|
|
);
|
|
|
|
if((dwLastError = GetLastError()) != ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
// pretend everything's fine -- just NULL security descriptor
|
|
if(m_pSecurityDescriptor != NULL)
|
|
CTemplate::SmallFree(m_pSecurityDescriptor);
|
|
m_pSecurityDescriptor = NULL;
|
|
m_dwSecDescSize = 0;
|
|
if (dwLastError == ERROR_NOT_SUPPORTED)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
// set member buffer size to just enough chunks to accommodate security descriptor size needed
|
|
m_dwSecDescSize = ((dwSecDescSizeNeeded + SECURITY_DESC_GRANULARITY - 1) / SECURITY_DESC_GRANULARITY)
|
|
* SECURITY_DESC_GRANULARITY;
|
|
|
|
// allocate memory for security descriptor
|
|
// (Note: security descriptor may already be allocated if this is a remap)
|
|
if (m_pSecurityDescriptor == NULL)
|
|
if(NULL == (m_pSecurityDescriptor = (PSECURITY_DESCRIPTOR) CTemplate::SmallMalloc(m_dwSecDescSize)))
|
|
THROW(E_OUTOFMEMORY);
|
|
|
|
// try to get security descriptor until we succeed, or until we fail for some reason other than buffer-too-small
|
|
while(TRUE)
|
|
{
|
|
// attempt to get security descriptor
|
|
fGotSecurityDescriptor = GetKernelObjectSecurity(
|
|
m_hFile, // handle of object to query
|
|
si, // requested information
|
|
m_pSecurityDescriptor, // address of security descriptor
|
|
m_dwSecDescSize, // size of buffer for security descriptor
|
|
&dwSecDescSizeNeeded // address of required size of buffer
|
|
);
|
|
|
|
// get last error immediately after call
|
|
dwLastError = fGotSecurityDescriptor
|
|
? 0 // we got a security descriptor: set last error to 0
|
|
: GetLastError(); // we didn't get a security descriptor: get last error
|
|
|
|
if(fGotSecurityDescriptor)
|
|
// we got a security descriptor, so break
|
|
// NOTE we can't realloc m_pSecurityDescriptor to free its unused memory
|
|
// because dwSecDescSizeNeeded is 0 after successful call
|
|
break;
|
|
|
|
else if(dwLastError == ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
// we didn't get a security descriptor because buffer was too small: increase buffer size, realloc and continue.
|
|
Assert(m_dwSecDescSize < dwSecDescSizeNeeded);
|
|
|
|
// set member buffer size to just enough chunks to accommodate security descriptor size needed
|
|
m_dwSecDescSize = ((dwSecDescSizeNeeded + SECURITY_DESC_GRANULARITY - 1) / SECURITY_DESC_GRANULARITY)
|
|
* SECURITY_DESC_GRANULARITY;
|
|
|
|
if(NULL == (m_pSecurityDescriptor = (PSECURITY_DESCRIPTOR) CTemplate::SmallReAlloc(m_pSecurityDescriptor, m_dwSecDescSize)))
|
|
THROW(E_OUTOFMEMORY);
|
|
}
|
|
|
|
else
|
|
{
|
|
// we didn't get a security descriptor for some random reason: return failure
|
|
fRet = FALSE;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::UnmapFile
|
|
Unmaps a memory-mapped file
|
|
NOTE this leaves the filemap's path-info and path-tran members intact
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::CFileMap::UnmapFile
|
|
(
|
|
)
|
|
{
|
|
if(m_pbStartOfFile != NULL)
|
|
if(!UnmapViewOfFile(m_pbStartOfFile)) THROW(E_FAIL);
|
|
|
|
if(m_hMap!= NULL)
|
|
if(!CloseHandle(m_hMap)) THROW(E_FAIL);
|
|
|
|
if(m_hFile != NULL && m_hFile != INVALID_HANDLE_VALUE)
|
|
if(!CloseHandle(m_hFile)) THROW(E_FAIL);
|
|
|
|
// Null-ify ptr and handles, since MapFile checks for non-null
|
|
m_pbStartOfFile = NULL;
|
|
m_hMap = NULL;
|
|
m_hFile = NULL;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::CountChars
|
|
Count the number of characters in the (open) filemap
|
|
|
|
Returns:
|
|
# of characters in the file
|
|
*/
|
|
DWORD
|
|
CTemplate::CFileMap::CountChars
|
|
(
|
|
WORD wCodePage
|
|
)
|
|
{
|
|
// Bug 84284: Scripts containing object tags only do not have the DBCS table built
|
|
// (Because there is no line mapping table to base it from)
|
|
//
|
|
CTemplate::COffsetInfo *pOffsetInfoLast, oiZero;
|
|
pOffsetInfoLast = (m_rgByte2DBCS.length() == 0)
|
|
? &oiZero
|
|
: &m_rgByte2DBCS[m_rgByte2DBCS.length() - 1];
|
|
|
|
// If GetSize() fails don't count the remaining DBCS characters - otherwise an AV
|
|
DWORD cchFilemap = GetSize();
|
|
if (cchFilemap != 0xFFFFFFFF && cchFilemap != 0)
|
|
{
|
|
// Count DBCS characters
|
|
m_cChars = pOffsetInfoLast->m_cchOffset +
|
|
CharAdvDBCS(wCodePage,
|
|
reinterpret_cast<char *>(m_pbStartOfFile + pOffsetInfoLast->m_cbOffset),
|
|
reinterpret_cast<char *>(m_pbStartOfFile + cchFilemap),
|
|
INFINITE, NULL);
|
|
|
|
}
|
|
else
|
|
{
|
|
m_cChars = 0;
|
|
}
|
|
|
|
// Done counting DBCS characters
|
|
return m_cChars;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CFileMap::GetText
|
|
From a character offset and length, return the document text
|
|
|
|
File must be mapped
|
|
*/
|
|
HRESULT CTemplate::CFileMap::GetText
|
|
(
|
|
WORD wCodePage,
|
|
ULONG cchSourceOffset,
|
|
WCHAR *pwchText,
|
|
SOURCE_TEXT_ATTR *pTextAttr,
|
|
ULONG *pcChars,
|
|
ULONG cMaxChars
|
|
)
|
|
{
|
|
ULONG cCharsCopied;
|
|
if (pcChars == NULL)
|
|
pcChars = &cCharsCopied;
|
|
|
|
// Map the file (temporarily) if not mapped
|
|
BOOL fRemapFile = !FIsMapped();
|
|
TRY
|
|
RemapFile();
|
|
CATCH (dwException)
|
|
return E_FAIL;
|
|
END_TRY
|
|
|
|
/* Find the byte offset closest to cchSourceOffset. This will be
|
|
* the place where we start looping with CharNext() to get the full
|
|
* byte offset.
|
|
*/
|
|
COffsetInfo *pOffsetInfoLE = NULL, OffsetInfoT;
|
|
|
|
/*
|
|
* NOTE: compilation is done in two phases.
|
|
* Errors are detected and reported in phase 1.
|
|
* The DBCS mapping is created in phase 2.
|
|
*
|
|
* If an error occurred during compilation, the DBCS table does not exist.
|
|
* If there is no DBCS mapping table, then pretend like we found entry with
|
|
* nearest offset == 0. (unless this is SBCS in which case nearest
|
|
* offset == cchSourceOffset)
|
|
*/
|
|
if (m_rgByte2DBCS.length() == 0)
|
|
{
|
|
CPINFO CpInfo;
|
|
GetCPInfo(wCodePage, &CpInfo);
|
|
OffsetInfoT.m_cbOffset = OffsetInfoT.m_cchOffset = (CpInfo.MaxCharSize == 1)? cchSourceOffset : 0;
|
|
pOffsetInfoLE = &OffsetInfoT;
|
|
}
|
|
else
|
|
GetBracketingPair(
|
|
cchSourceOffset, // value to search for
|
|
m_rgByte2DBCS.begin(), // beginning of array to search
|
|
m_rgByte2DBCS.end(), // end of array
|
|
CCharOffsetOrder(), // order by character offsets
|
|
&pOffsetInfoLE, // desired offset
|
|
static_cast<COffsetInfo **>(NULL) // don't care
|
|
);
|
|
|
|
/* OK - pOffsetLE->cbOffset contains the closest offset not exceeding
|
|
* cchSourceOffset. Iterate over the remainder of the characters
|
|
* to convert the cch to a cb. It had better exist!
|
|
*/
|
|
Assert (pOffsetInfoLE != NULL);
|
|
|
|
// Advance over remaining characters
|
|
char *pchStart;
|
|
CharAdvDBCS(wCodePage,
|
|
reinterpret_cast<char *>(m_pbStartOfFile + pOffsetInfoLE->m_cbOffset),
|
|
reinterpret_cast<char *>(m_pbStartOfFile + GetSize()),
|
|
cchSourceOffset - pOffsetInfoLE->m_cchOffset,
|
|
&pchStart
|
|
);
|
|
|
|
// Compute # of Characters to copy
|
|
Assert (m_cChars >= cchSourceOffset);
|
|
*pcChars = min(cMaxChars, m_cChars - cchSourceOffset);
|
|
|
|
// Compute # of Bytes to copy
|
|
char *pchEnd;
|
|
CharAdvDBCS(wCodePage,
|
|
pchStart,
|
|
reinterpret_cast<char *>(m_pbStartOfFile + GetSize()),
|
|
*pcChars,
|
|
&pchEnd
|
|
);
|
|
|
|
if (pwchText)
|
|
MultiByteToWideChar(
|
|
(WORD)wCodePage,
|
|
0,
|
|
pchStart,
|
|
DIFF(pchEnd - pchStart),
|
|
pwchText,
|
|
cMaxChars
|
|
);
|
|
|
|
// We don't support syntax coloring, so set all the character attributes to
|
|
// default color (black)
|
|
if (pTextAttr)
|
|
memset(pTextAttr, 0, *pcChars);
|
|
|
|
// Unmap the file (but only if we previously remapped it)
|
|
if (fRemapFile)
|
|
TRY
|
|
UnmapFile();
|
|
CATCH (dwException)
|
|
return E_FAIL;
|
|
END_TRY
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
CTemplate::CTokenList member functions
|
|
*/
|
|
|
|
/* ============================================================================
|
|
CTemplate::CTokenList::Init
|
|
Populates token list with tokens
|
|
|
|
Returns
|
|
Nothing
|
|
Side effects
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::CTokenList::Init
|
|
(
|
|
)
|
|
{
|
|
// Init tokens buffer for local storage
|
|
m_bufTokens.Init(tkncAll, CB_TOKENS_DEFAULT);
|
|
|
|
// append tokens to buffer
|
|
// NOTE *** TOKENS MUST BE IN SAME ORDER AS ENUM TYPE VALUES ***
|
|
// NOTE 'superset' token must precede 'subset' token (e.g. <!--#INCLUDE before <!--)
|
|
AppendToken(tknOpenPrimaryScript, "<%");
|
|
AppendToken(tknOpenTaggedScript, "<SCRIPT");
|
|
AppendToken(tknOpenObject, "<OBJECT");
|
|
AppendToken(tknOpenHTMLComment, "<!--");
|
|
|
|
AppendToken(tknNewLine, SZ_NEWLINE);
|
|
|
|
AppendToken(tknClosePrimaryScript, "%>");
|
|
AppendToken(tknCloseTaggedScript, "</SCRIPT>");
|
|
AppendToken(tknCloseObject, "</OBJECT>");
|
|
AppendToken(tknCloseHTMLComment, "-->");
|
|
AppendToken(tknEscapedClosePrimaryScript, "%\\>");
|
|
|
|
AppendToken(tknCloseTag, ">");
|
|
|
|
AppendToken(tknCommandINCLUDE, "#INCLUDE");
|
|
|
|
AppendToken(tknTagRunat, "RUNAT");
|
|
AppendToken(tknTagLanguage, "LANGUAGE");
|
|
AppendToken(tknTagCodePage, "CODEPAGE");
|
|
AppendToken(tknTagCodePage, "LCID");
|
|
AppendToken(tknTagTransacted, "TRANSACTION");
|
|
AppendToken(tknTagSession, "ENABLESESSIONSTATE");
|
|
AppendToken(tknTagID, "ID");
|
|
AppendToken(tknTagClassID, "CLASSID");
|
|
AppendToken(tknTagProgID, "PROGID");
|
|
AppendToken(tknTagScope, "SCOPE");
|
|
AppendToken(tknTagVirtual, "VIRTUAL");
|
|
AppendToken(tknTagFile, "FILE");
|
|
AppendToken(tknTagMETADATA, "METADATA");
|
|
// AppendToken(tknTagSetPriScriptLang, "@");
|
|
AppendToken(tknTagName, "NAME");
|
|
AppendToken(tknValueTypeLib, "TYPELIB");
|
|
AppendToken(tknTagType, "TYPE");
|
|
AppendToken(tknTagUUID, "UUID");
|
|
AppendToken(tknTagVersion, "VERSION");
|
|
AppendToken(tknTagStartspan, "STARTSPAN");
|
|
AppendToken(tknTagEndspan, "ENDSPAN");
|
|
AppendToken(tknValueCookie, "COOKIE");
|
|
AppendToken(tknTagSrc, "SRC");
|
|
|
|
AppendToken(tknValueServer, "Server");
|
|
AppendToken(tknValueApplication, "Application");
|
|
AppendToken(tknValueSession, "Session");
|
|
AppendToken(tknValuePage, "Page");
|
|
|
|
AppendToken(tknVBSCommentSQuote, "'");
|
|
AppendToken(tknVBSCommentRem, "REM "); // NOTE ends with space character
|
|
AppendToken(tknTagFPBot, "webbot");
|
|
|
|
AppendToken(tknEOF, "");
|
|
|
|
AppendToken(tkncAll, "");
|
|
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CTokenList::AppendToken
|
|
Appends a string to tokens buffer
|
|
NOTE we keep the unused tkn parameter because it enforces consistency and
|
|
readability in CTemplate::CTokenList::Init(), e.g.
|
|
AppendToken(tknOpenPrimaryScript, "<%");
|
|
rather than
|
|
AppendToken("<%");
|
|
|
|
Returns:
|
|
Nothing
|
|
Side effects:
|
|
None
|
|
*/
|
|
void
|
|
CTemplate::CTokenList::AppendToken
|
|
(
|
|
_TOKEN tkn, // token value
|
|
char* sz // token string
|
|
)
|
|
{
|
|
// construct byte range from token string
|
|
CByteRange br;
|
|
br.m_pb = (BYTE*) sz;
|
|
br.m_cb = strlen(sz);
|
|
|
|
// append to tokens buffer as local string
|
|
m_bufTokens.Append(br, TRUE, 0, NULL, TRUE);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CTokenList::NextOpenToken
|
|
Returns value of next open token in search range
|
|
|
|
Returns
|
|
token value of next open token in search range; ptr to ptr to open token (out-parameter)
|
|
Side effects
|
|
None
|
|
*/
|
|
_TOKEN
|
|
CTemplate::CTokenList::NextOpenToken
|
|
(
|
|
CByteRange& brSearch, // search byte range
|
|
TOKEN* rgtknOpeners, // array of permitted open tokens
|
|
UINT ctknOpeners, // count of permitted open tokens
|
|
BYTE** ppbToken, // ptr to ptr to open token (out-parameter)
|
|
LONG lCodePage
|
|
)
|
|
{
|
|
BYTE* pbTemp = NULL; // temp pointer
|
|
_TOKEN tkn = tknEOF; // return value
|
|
USHORT i; // loop index
|
|
|
|
// Init caller's token ptr to null
|
|
*ppbToken = NULL;
|
|
|
|
// If input is empty, return
|
|
if (brSearch.IsNull())
|
|
return tkn;
|
|
|
|
// Prepare array of LPSTR pointers to tokens.
|
|
// Do it here once, because to get LPSTR is not free.
|
|
LPSTR rgszTokens[TOKEN_OPENERS_MAX];
|
|
UINT rgcchTokens[TOKEN_OPENERS_MAX];
|
|
Assert(ctknOpeners <= TOKEN_OPENERS_MAX);
|
|
|
|
for (i = 0; i < ctknOpeners; i++)
|
|
{
|
|
LPSTR pszStr = m_bufTokens.PszLocal((UINT)(rgtknOpeners[i]));
|
|
rgszTokens[i] = pszStr;
|
|
rgcchTokens[i] = (pszStr != NULL) ? strlen(pszStr) : 0;
|
|
}
|
|
|
|
// Call a method to find one of the strings in the range
|
|
UINT idToken;
|
|
pbTemp = brSearch.PbOneOfAspOpenerStringTokens(
|
|
rgszTokens, rgcchTokens, ctknOpeners, &idToken);
|
|
if (pbTemp != NULL)
|
|
{
|
|
*ppbToken = pbTemp;
|
|
tkn = rgtknOpeners[idToken];
|
|
}
|
|
|
|
// If we found no open token, position token pointer at end of search range
|
|
if (tkn == tknEOF)
|
|
*ppbToken = brSearch.m_pb + brSearch.m_cb;
|
|
|
|
return tkn;
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CTokenList::MovePastToken
|
|
Moves a byte range past a token contained within it
|
|
*/
|
|
void
|
|
CTemplate::CTokenList::MovePastToken
|
|
(
|
|
_TOKEN tkn,
|
|
BYTE* pbToken,
|
|
CByteRange& brSearch
|
|
)
|
|
{
|
|
Assert(pbToken >= brSearch.m_pb);
|
|
Assert(brSearch.m_cb >= (DIFF(pbToken - brSearch.m_pb) + CCH_TOKEN_X(tkn)));
|
|
brSearch.Advance(DIFF(pbToken - brSearch.m_pb) + CCH_TOKEN_X(tkn));
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::CTokenList::GetToken
|
|
Gets the next occurrence of a token within a byte range.
|
|
|
|
Returns:
|
|
ptr to token
|
|
Side effects
|
|
none
|
|
*/
|
|
BYTE*
|
|
CTemplate::CTokenList::GetToken
|
|
(
|
|
TOKEN tkn,
|
|
CByteRange& brSearch,
|
|
LONG lCodePage
|
|
)
|
|
{
|
|
return brSearch.PbString(m_bufTokens.PszLocal((UINT)tkn), lCodePage);
|
|
}
|
|
|
|
/* ============================================================================
|
|
The Big Three for CTemplateConnPt
|
|
|
|
NOTES:
|
|
Since this interface is embedded in CTemplate,
|
|
AddRef() and Release() delegate to the container object (because that
|
|
is the CTemplate pointer)
|
|
*/
|
|
HRESULT
|
|
CTemplateConnPt::QueryInterface(const GUID &uidInterface, void **ppvObj)
|
|
{
|
|
if (uidInterface == IID_IUnknown || uidInterface == IID_IConnectionPoint)
|
|
{
|
|
*ppvObj = this;
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
*ppvObj = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
ULONG
|
|
CTemplateConnPt::AddRef()
|
|
{
|
|
return m_pUnkContainer->AddRef();
|
|
}
|
|
|
|
ULONG
|
|
CTemplateConnPt::Release()
|
|
{
|
|
return m_pUnkContainer->Release();
|
|
}
|
|
|
|
/* ============================================================================
|
|
Constructor for CDocNode
|
|
*/
|
|
CTemplate::CDocNodeElem::CDocNodeElem(CAppln *pAppln, IDebugApplicationNode *pDocRoot)
|
|
{
|
|
Assert (pAppln != NULL);
|
|
Assert (pDocRoot != NULL);
|
|
|
|
(m_pAppln = pAppln)->AddRef();
|
|
(m_pDocRoot = pDocRoot)->AddRef();
|
|
}
|
|
|
|
/* ============================================================================
|
|
Destructor for CDocNode
|
|
*/
|
|
CTemplate::CDocNodeElem::~CDocNodeElem()
|
|
{
|
|
m_pAppln->Release();
|
|
DestroyDocumentTree(m_pDocRoot);
|
|
}
|
|
|
|
/* ============================================================================
|
|
CTemplate::fIsLangVBScriptOrJScript(USHORT idEngine)
|
|
|
|
This function returns T/F to determine if the requested script engine
|
|
is VBScript or JScript. This function is used as an indicator to determin
|
|
if spaces need to be preserved for non MS Scripting languages
|
|
|
|
There is an assumption here that the GUIDs for VBScript and JScript will not change
|
|
|
|
Inputs
|
|
Index to a script engine
|
|
|
|
Returns
|
|
BOOL
|
|
*/
|
|
BOOLB CTemplate::FIsLangVBScriptOrJScript(USHORT idEngine)
|
|
{
|
|
// {b54f3741-5b07-11cf-a4b0-00aa004a55e8} VBScript
|
|
static const GUID uid_VBScript = {0xb54f3741, 0x5b07, 0x11cf, {0xa4, 0xb0, 0x00, 0xaa, 0x00, 0x4a, 0x55, 0xe8}};
|
|
|
|
// {f414c260-6ac0-11cf-b6d1-00aa00bbbb58} JavaScript
|
|
static const GUID uid_JScript = {0xf414c260, 0x6ac0, 0x11cf, {0xb6, 0xd1, 0x00, 0xaa, 0x00, 0xbb, 0xbb, 0x58}};
|
|
|
|
// {b54f3743-5b07-11cf-a4b0-00aa004a55e8} VBScript.Encode
|
|
static const GUID uid_VBScriptEncode = {0xb54f3743, 0x5b07, 0x11cf, {0xa4, 0xb0, 0x00, 0xaa, 0x00, 0x4a, 0x55, 0xe8}};
|
|
|
|
// {f414c262-6ac0-11cf-b6d1-00aa00bbbb58} JavaScript.Encode
|
|
static const GUID uid_JScriptEncode = {0xf414c262, 0x6ac0, 0x11cf, {0xb6, 0xd1, 0x00, 0xaa, 0x00, 0xbb, 0xbb, 0x58}};
|
|
|
|
GUID &uidLang = m_pWorkStore->m_ScriptStore.m_rgProgLangId[idEngine];
|
|
return
|
|
uidLang == uid_VBScript || uidLang == uid_VBScriptEncode ||
|
|
uidLang == uid_JScript || uidLang == uid_JScriptEncode;
|
|
}
|
|
|
|
|
|
SIZE_T
|
|
_RoundUp(
|
|
SIZE_T dwBytes)
|
|
{
|
|
#if 1
|
|
// 16KB <= dwBytes? Round up to next multiple of 4KB
|
|
if (16*1024 <= dwBytes)
|
|
dwBytes = ((dwBytes + (1<<12) - 1) >> 12) << 12;
|
|
|
|
// 4KB <= dwBytes < 16KB? Round up to next multiple of 1KB
|
|
else if (4*1024 <= dwBytes)
|
|
dwBytes = ((dwBytes + (1<<10) - 1) >> 10) << 10;
|
|
|
|
// 1KB <= dwBytes < 4KB? Round up to next multiple of 256 bytes
|
|
else if (1024 <= dwBytes)
|
|
dwBytes = ((dwBytes + (1<<8) - 1) >> 8) << 8;
|
|
|
|
// dwBytes < 1KB? Round up to next multiple of 32 bytes
|
|
else
|
|
dwBytes = ((dwBytes + (1<<5) - 1) >> 5) << 5;
|
|
#endif
|
|
|
|
return dwBytes;
|
|
}
|
|
|
|
void*
|
|
CTemplate::SmallMalloc(SIZE_T dwBytes)
|
|
{
|
|
DBG_ASSERT(sm_hSmallHeap != NULL);
|
|
dwBytes = _RoundUp(dwBytes);
|
|
return ::HeapAlloc(sm_hSmallHeap, 0, dwBytes);
|
|
}
|
|
|
|
|
|
void*
|
|
CTemplate::SmallReAlloc(void* pvMem, SIZE_T dwBytes)
|
|
{
|
|
DBG_ASSERT(sm_hSmallHeap != NULL);
|
|
dwBytes = _RoundUp(dwBytes);
|
|
return ::HeapReAlloc(sm_hSmallHeap, 0, pvMem, dwBytes);
|
|
}
|
|
|
|
|
|
void
|
|
CTemplate::SmallFree(void* pvMem)
|
|
{
|
|
DBG_ASSERT(sm_hSmallHeap != NULL);
|
|
::HeapFree(sm_hSmallHeap, 0, pvMem);
|
|
}
|
|
|
|
void*
|
|
CTemplate::LargeMalloc(SIZE_T dwBytes)
|
|
{
|
|
DBG_ASSERT(sm_hLargeHeap != NULL);
|
|
dwBytes = _RoundUp(dwBytes);
|
|
return ::HeapAlloc(sm_hLargeHeap, 0, dwBytes);
|
|
}
|
|
|
|
|
|
void*
|
|
CTemplate::LargeReAlloc(void* pvMem, SIZE_T dwBytes)
|
|
{
|
|
DBG_ASSERT(sm_hLargeHeap != NULL);
|
|
dwBytes = _RoundUp(dwBytes);
|
|
return ::HeapReAlloc(sm_hLargeHeap, 0, pvMem, dwBytes);
|
|
}
|
|
|
|
|
|
void
|
|
CTemplate::LargeFree(void* pvMem)
|
|
{
|
|
DBG_ASSERT(sm_hLargeHeap != NULL);
|
|
::HeapFree(sm_hLargeHeap, 0, pvMem);
|
|
}
|