/*===================================================================
Microsoft Denali

Microsoft Confidential.
Copyright 1996 Microsoft Corporation. All Rights Reserved.

Component: Executor

Owner: DGottner

File: executor.cpp

This file contains the executor, whose job is to co-ordinate the
execution of Denali scripts.
===================================================================*/

#include "denpre.h"
#pragma hdrstop

#include "exec.h"
#include "response.h"
#include "request.h"
#include "perfdata.h"
#include "memchk.h"


// Local declarations
HRESULT ExecuteGlobal(CHitObj *pHitObj,
                        const CIntrinsicObjects &intrinsics,
                        ActiveEngineInfo *pEngineInfo);
HRESULT ExecuteRequest(CTemplate *pTemplate, CHitObj *pHitObj,
                        const CIntrinsicObjects &intrinsics,
                        ActiveEngineInfo *pEngineInfo);
HRESULT ReInitIntrinsics(CHitObj *pHitObj, const CIntrinsicObjects &intrinsics,
                            ActiveEngineInfo *pEngineInfo, BOOL fPostGlobal);
HRESULT AllocAndLoadEngines(CHitObj *pHitObj, CTemplate *pTemplate, ActiveEngineInfo *pEngineInfo,
                            CScriptingNamespace *pScriptingNamespace, BOOL fGlobalAsa);
VOID DeAllocAndFreeEngines(ActiveEngineInfo *pEngineInfo, CAppln *pAppln);
CScriptEngine *GetScriptEngine(int iScriptEngine, void *pvData);
HRESULT CallScriptFunctionOfEngine(ActiveEngineInfo &engineInfo, short iScriptBlock, wchar_t *strFunction, CASPObjectContext  *pASPObjectContext = NULL);
HRESULT CallScriptFunction(ActiveEngineInfo &engineInfo, wchar_t *strFunction);
HRESULT TestScriptFunction(ActiveEngineInfo &engineInfo, wchar_t *strFunction);

/*===================================================================
Execute

Execute a request:
    First determine if Global needs to be called
    then invoke actual requested template

Parameters:
    pTemplate       - pointer to loaded template (could be NULL)
    pHitObj         - pointer to the hit object
    intrinsics      - pointers to the intrinsic IUnknown pointers.
    fChild          - flag: TRUE when child request (Server.Execute())

Returns:
    S_OK on success
===================================================================*/
HRESULT Execute
(
CTemplate *pTemplate,
CHitObj *pHitObj,
const CIntrinsicObjects &intrinsics,
BOOL fChild
)
    {
    HRESULT hr = S_OK;
    ActiveEngineInfo engineInfo;
    BOOL fRanGlobal = FALSE;
    
    // The hit obj must be valid
    Assert(pHitObj != NULL);
    pHitObj->AssertValid();

	// Check for valid Session codepage.  We do it here, rather than in CSession::Init in
	// order to avoid generic "New Session Failed" message.
	if (pHitObj->GetCodePage() != CP_ACP && !IsValidCodePage(pHitObj->GetCodePage()))
		{
		HandleErrorMissingFilename(IDE_BAD_CODEPAGE_IN_MB, pHitObj);
		return E_FAIL;
		}

    // Give the engine list to the hitobject
    pHitObj->SetActiveEngineInfo(&engineInfo);
        
    /*
     * If there is a Global.ASA, call it
     */
    if (pHitObj->GlobalAspPath() && !fChild)
        {
        // Clear out the engine info
        engineInfo.cEngines = 0;
        engineInfo.cActiveEngines = 0;
        engineInfo.rgActiveEngines = NULL;

        // Init the intrinsics
        hr = ReInitIntrinsics(pHitObj, intrinsics, &engineInfo, /* fPostGlobal*/ FALSE);
        if (FAILED(hr))
            return(hr);

        hr = ExecuteGlobal(pHitObj, intrinsics, &engineInfo);

        if (intrinsics.PResponse() && intrinsics.PResponse()->FResponseAborted())
            {
            hr = S_OK;
            goto LExit;
            }

        if (E_SOURCE_FILE_IS_EMPTY == hr)
            // bug 977: silently ignore empty global.asa file
            hr = S_OK;
        else if (FAILED(hr))
            {
            // Bug 481: If global.asa fails due to Response.End (or Response.Redirect),
            //      then halt execution of the calling script.  If the
            //      script fails due to Response.End, then return OK status
            //
            if (hr == DISP_E_EXCEPTION)
                hr = S_OK;

            // In any case, blow out of here
            goto LExit;
            }

        // Running Global.asa added the scripting namespace to the hitobj.  This will cause us problems
        // later, remove it.
        pHitObj->RemoveScriptingNamespace();

        fRanGlobal = TRUE;
        }

    /*
     * If this is not a browser request, then we are done
     * For non-browser requests, we do want to run Global.asa (if any), but there is no real template to run.
     */
    if (!pHitObj->FIsBrowserRequest())
        {
        hr = S_OK;
        goto LExit;
        }

    // Clear out (or re-clear out) the engine info
    engineInfo.cEngines = 0;
    engineInfo.cActiveEngines = 0;
    engineInfo.rgActiveEngines = NULL;

    // Init or Re-Init the intrinsics
    ReInitIntrinsics(pHitObj, intrinsics, &engineInfo, fRanGlobal || fChild);

    if (!fChild)
        {
        // For non-child requests hand new Template to Response object
        // (for child requests already done)
        intrinsics.PResponse()->ReInitTemplate(pTemplate, pHitObj->PSzNewSessionCookie());
        }
    else
        {
        // For child requests hand new engine info to the response object
        intrinsics.PResponse()->SwapScriptEngineInfo(&engineInfo);
        }

    // Run the main template
    if (pTemplate->FScriptless() && !pHitObj->PAppln()->FDebuggable())
        {
        // special case scriptless pages
        hr = intrinsics.PResponse()->WriteBlock(0);
        }
    else
        {
        hr = ExecuteRequest(pTemplate, pHitObj, intrinsics, &engineInfo);
        }
        
LExit:
	intrinsics.PResponse()->SwapScriptEngineInfo(NULL);
	pHitObj->SetActiveEngineInfo(NULL);
    return hr;
    }

/*===================================================================
ExecRequest

Execute a request for an actual template (not Global.asa)

execute a request by
    - getting the script name
    - loading the script into memory
    - interpreting the opcodes

Parameters:
    pTemplate       - pointer to loaded template
    pHitObj         - pointer to the hit object
    intrinsics      - pointers to the intrinsic IUnknown pointers.
    pEngineInfo     - pointers to engine info

Returns:
    S_OK on success
===================================================================*/
HRESULT ExecuteRequest(CTemplate                *pTemplate,
                       CHitObj                  *pHitObj,
                       const CIntrinsicObjects  &intrinsics,
                       ActiveEngineInfo         *pEngineInfo)
{
    HRESULT hr = S_OK;
    BOOL    fAborted = FALSE;
    BOOLB   fDebuggerNotifiedOnStart = FALSE;
    BOOL    fServiceDomainEnterred = FALSE;
    CASPObjectContext   *pASPObjectContext = NULL;
    CASPObjectContext   *pPoppedASPObjectContext = NULL;

#ifndef PERF_DISABLE
    BOOLB fPerfTransPending = FALSE;
#endif      

    // The template must be valid
    Assert(pTemplate);

    // The hit obj must be valid
    Assert(pHitObj != NULL);
    pHitObj->AssertValid();

    // This function should never be called on a non-browser request
    Assert(pHitObj->FIsBrowserRequest());

    // Remember template's type library wrapper with the HitObj
    if (pTemplate->PTypeLibWrapper())
        pHitObj->SetTypeLibWrapper(pTemplate->PTypeLibWrapper());

    if (pTemplate->FTransacted()) {

#ifndef PERF_DISABLE
        g_PerfData.Incr_TRANSTOTAL();
        g_PerfData.Incr_TRANSPENDING();
        fPerfTransPending = TRUE;
#endif      
        pASPObjectContext = new CASPObjectContext();

        if (!pASPObjectContext) {
            hr = E_OUTOFMEMORY;
            goto LExit;
        }
        
        pPoppedASPObjectContext = pHitObj->SetASPObjectContext(pASPObjectContext);
    }

    if (pTemplate->PServicesConfig()) {
        hr = CoEnterServiceDomain(pTemplate->PServicesConfig());
        if (FAILED(hr)) {
            goto LExit;
        }
        fServiceDomainEnterred = TRUE;
    }

	// load script engines
	hr = AllocAndLoadEngines(pHitObj, pTemplate, pEngineInfo, intrinsics.PScriptingNamespace(), /* fGlobalAsa */FALSE);
	if (FAILED(hr)) {
	    pHitObj->SetCompilationFailed();
		goto LExit;
    }

    // If debugging, notify debugger ONPAGESTART
    // BUG 138773: Notify debugger AFTER scripts load
    //    (script must be in running state when AttachTo() is called because debugger may want a code context)
    //
    if (pHitObj->PAppln()->FDebuggable()) {
		pTemplate->AttachTo(pHitObj->PAppln());
        if (SUCCEEDED(pTemplate->NotifyDebuggerOnPageEvent(TRUE)))
            fDebuggerNotifiedOnStart = TRUE;
    }
		
	// bug 1009: if no script engines, do not attempt to do anything
	if(0 == pTemplate->CountScriptEngines())
		goto LExit;

    // run the script by calling primary script engine's global code
    hr = CallScriptFunctionOfEngine(*pEngineInfo,   // engine-info
                                    0,              // primary script engine
                                    NULL,           // call the engine's global code
                                    pHitObj->PASPObjectContext());

    if (fServiceDomainEnterred) {
        fServiceDomainEnterred = FALSE;
        CoLeaveServiceDomain(static_cast<ITransactionStatus *>(pHitObj->PASPObjectContext()));
    }


    if (pTemplate->FTransacted()) {
        fAborted = pHitObj->PASPObjectContext()->FAborted();
    }

    if (FAILED(hr)) {
        /*
         * The cryptically named CONTEXT_E_OLDREF error in this case means that
         * we are trying to run a transacted web page, but DTC isnt running.
         * CONTEXT_E_TMNOTAVAILABLE means the same thing.  God knows why
         */
        if (hr == CONTEXT_E_OLDREF || hr == CONTEXT_E_TMNOTAVAILABLE) {
            HandleErrorMissingFilename(IDE_EXECUTOR_DTC_NOT_RUNNING, pHitObj);
        }

        // Regardless of the error, exit
        goto LExit;
    }

    /*
     * If this is a transacted web page, then run either the OnTransactionCommit
     * or OnTransactionAbort method in the script, if any.
     *
     * If the script writer did an explicit SetAbort, or a component run by the script
     * did a SetAbort, then we run OnTransactionAbort, otherwise run OnTransactionCommit
     */
    if (pTemplate->FTransacted()) {

#ifndef PERF_DISABLE
        g_PerfData.Incr_TRANSPERSEC();
#endif

        if (fAborted) {

            hr = CallScriptFunction(*pEngineInfo, L"OnTransactionAbort");

#ifndef PERF_DISABLE
            g_PerfData.Incr_TRANSABORTED();
#endif
        }
        else {
            hr = CallScriptFunction(*pEngineInfo, L"OnTransactionCommit");

#ifndef PERF_DISABLE
            g_PerfData.Incr_TRANSCOMMIT();
#endif
        }

        // Ignore UNKNOWNNAME -- this means the author didnt write the method, which is fine
        if (hr == DISP_E_UNKNOWNNAME || hr == DISP_E_MEMBERNOTFOUND)
            hr = S_OK;
            
        if (FAILED(hr))
            goto LExit;
    }

LExit:

    if (fServiceDomainEnterred) {
        CoLeaveServiceDomain(NULL);
    }

#ifndef PERF_DISABLE
    if (fPerfTransPending)
        g_PerfData.Decr_TRANSPENDING();
#endif

    // Uninit the scripting namespace
    (VOID)intrinsics.PScriptingNamespace()->UnInit();

    // Return the engine(s) to cache
    DeAllocAndFreeEngines(pEngineInfo, pHitObj->PAppln());

    // If debugging, notify debugger ONPAGEDONE
    if (fDebuggerNotifiedOnStart) {
        Assert(pHitObj->PAppln()->FDebuggable());
        pTemplate->NotifyDebuggerOnPageEvent(FALSE);
    }

    if (pPoppedASPObjectContext)
        pHitObj->SetASPObjectContext(pPoppedASPObjectContext);

    if (pASPObjectContext)
        pASPObjectContext->Release();

    return hr;
}

/*===================================================================
ExecuteGlobal

UNDONE: handle script engine the same manner as mainline script engines
        with respect to debugging.

Execute code in Global.ASA as part of application or session start or end.

Parameters:
    pHitObj         - pointer to the hit object
    intrinsics      - pointers to the intrinsic IUnknown pointers.
    pEngineInfo     - pointers to engine info
    pfDeleteSession - true if global.asa failed, and therefore the caller should

Returns:
    S_OK on success
===================================================================*/
HRESULT ExecuteGlobal
(
CHitObj *pHitObj,
const CIntrinsicObjects &intrinsics,
ActiveEngineInfo *pEngineInfo
)
    {
    HRESULT hr = S_OK;
    CTemplate *pTemplate = NULL;
    WORD iEng;
    BOOLB fDebuggerNotifiedOnStart = FALSE;

    BOOL fUnHideRequestAndResponse = FALSE;

    BOOL fOnStartAppln = FALSE;
    BOOL fOnEndAppln = FALSE;
    BOOL fOnEndSession = FALSE;
    BOOL fGlobalAsaInCache;
    BOOL fApplnStarted = FALSE;
    BOOL fServiceDomainEnterred = FALSE;
    CASPObjectContext   *pASPObjectContext = NULL;
    CASPObjectContext   *pPoppedASPObjectContext = NULL;

    // The hit obj must be there, be valid, & have a global.asa name
    Assert(pHitObj != NULL);
    pHitObj->AssertValid();
    Assert(pHitObj->GlobalAspPath() != NULL && *(pHitObj->GlobalAspPath()) != '\0');

    // Other arg's must be right
    Assert(pEngineInfo != NULL);

    // Load the script - cache will AddRef
    // bug 1051: load template before possibly removing response object (in switch block, below),
    // so error reporting to browser will work
    hr = LoadTemplate(pHitObj->GlobalAspPath(), pHitObj, &pTemplate, intrinsics, /* fGlobalAsa */ TRUE, &fGlobalAsaInCache);
    if (FAILED(hr))
        goto LExit;

    Assert(pTemplate != NULL);

    // Remember GLOBAL.ASA's type library wrapper with the application
    // on the first request

    if (pHitObj->FStartApplication() && pTemplate->PTypeLibWrapper())
        {
        pHitObj->PAppln()->SetGlobTypeLibWrapper(pTemplate->PTypeLibWrapper());
        }

    if (pTemplate->FTransacted()) {

        pASPObjectContext = new CASPObjectContext();

        if (!pASPObjectContext) {
            hr = E_OUTOFMEMORY;
            goto LExit;
        }
        
        pPoppedASPObjectContext = pHitObj->SetASPObjectContext(pASPObjectContext);
    }

    if (pTemplate->PServicesConfig()) {
        hr = CoEnterServiceDomain(pTemplate->PServicesConfig());
        if (FAILED(hr)) {
            goto LExit;
        }
        fServiceDomainEnterred = TRUE;
    }
        
    Assert(pHitObj->FIsValidRequestType());

    // Figure out which events to trigger
    if (pHitObj->FIsBrowserRequest())
        {
        fOnStartAppln = pHitObj->FStartApplication();

        if (fOnStartAppln)
            {
            // Hide response and request intrinsics from namespace
            pHitObj->HideRequestAndResponseIntrinsics();

            // Flag that intrinsics need to be un-hidden back in.
            fUnHideRequestAndResponse = TRUE;
            }
        }
    else if (pHitObj->FIsSessionCleanupRequest())
        {
        fOnEndSession = TRUE;
        }
    else if (pHitObj->FIsApplnCleanupRequest())
        {
        fOnEndAppln = TRUE;
        }

    // If debugging, notify debugger ONPAGESTART
    if (pHitObj->PAppln()->FDebuggable())
        {
        if (SUCCEEDED(pTemplate->NotifyDebuggerOnPageEvent(TRUE)))
            fDebuggerNotifiedOnStart = TRUE;
        }
        
    hr = AllocAndLoadEngines(pHitObj, pTemplate, pEngineInfo, intrinsics.PScriptingNamespace(), /* fGlobalAsa */TRUE);
    if (FAILED(hr))
        goto LExit;

    // BUG 93991: Defer registration of new document with debugger until after script engines have
    //            been loaded
    //
    if (!fGlobalAsaInCache && pHitObj->PAppln()->FDebuggable())
        pTemplate->AttachTo(pHitObj->PAppln());

    // bug 975: if no script engines, do not attempt to call event functions
    if(0 == pTemplate->CountScriptEngines())
        goto LExit;
        
    /*
     * Call event functions as required
     * bug 459: event functions may be in any script engine
     */
    
    // First run Application_OnStart
    if (fOnStartAppln)
        {           
        pHitObj->SetEventState(eEventAppOnStart);
        
        hr = CallScriptFunction(*pEngineInfo, L"Application_OnStart");
        
        if (SUCCEEDED(hr) || hr == DISP_E_UNKNOWNNAME || hr == DISP_E_MEMBERNOTFOUND ||
            intrinsics.PResponse()->FResponseAborted())
            {
            if (fUnHideRequestAndResponse)
                {
                pHitObj->UnHideRequestAndResponseIntrinsics();
                fUnHideRequestAndResponse = FALSE;
                }
                
            fApplnStarted = TRUE;               
            hr = S_OK;
            }
        else
            {
            goto LExit;
            }
        }
    
    if (pHitObj->FStartSession())
        {
    // If application on start was run, add Response and Request names to script engines
        if (fOnStartAppln)
            {
            for (iEng = 0; iEng < pEngineInfo->cActiveEngines; ++iEng)
                {   
                if (FAILED(hr = pEngineInfo->rgActiveEngines[iEng].pScriptEngine->AddAdditionalObject(WSZ_OBJ_RESPONSE, FALSE)))
                    goto LExit;
                
                if (FAILED(hr = pEngineInfo->rgActiveEngines[iEng].pScriptEngine->AddAdditionalObject(WSZ_OBJ_REQUEST, FALSE)))
                    goto LExit;
                }
            }

        pHitObj->SetEventState(eEventSesOnStart);
        hr = CallScriptFunction(*pEngineInfo, L"Session_OnStart");
        
        if (FAILED(hr) && hr != DISP_E_UNKNOWNNAME && hr != DISP_E_MEMBERNOTFOUND &&
            !intrinsics.PResponse()->FResponseAborted())
            {
            // Mark session as on-start-failed - to be deleted soon
            pHitObj->SessionOnStartFailed();
            }   
        else
            {
            if (SUCCEEDED(hr))
                {
                // Mark as on-start-invoked -- need to wait for timeout
                pHitObj->SessionOnStartInvoked();
                }

            // Check if Session_OnEnd Present
            if (SUCCEEDED(TestScriptFunction(*pEngineInfo, L"Session_OnEnd")))
                {
                // Mark as on-end-present -- need to execute OnEnd later
                pHitObj->SessionOnEndPresent();
                }

            hr = S_OK;
            }

        goto LExit;
        }

    if (fOnEndSession)
        {
        pHitObj->SetEventState(eEventSesOnEnd);
        hr = CallScriptFunction(*pEngineInfo, L"Session_OnEnd");
        // We are failing silently here, since there is no corrective action we could take
        }

        
    if (fOnEndAppln)
        {
        pHitObj->SetEventState(eEventAppOnEnd);
        hr = CallScriptFunction(*pEngineInfo, L"Application_OnEnd");
        // We are failing silently here, since there is no corrective action we could take
        }
            
LExit:

    if (fServiceDomainEnterred)
        CoLeaveServiceDomain(NULL);

    if (fUnHideRequestAndResponse)
        {
        pHitObj->UnHideRequestAndResponseIntrinsics();
        }

    if (FAILED(hr) && (hr != E_SOURCE_FILE_IS_EMPTY) && pHitObj->FStartApplication() && !fApplnStarted)
        {
        pHitObj->ApplnOnStartFailed();
        }

    pHitObj->SetEventState(eEventNone);
        
    // Uninit the scripting namespace
    (VOID)intrinsics.PScriptingNamespace()->UnInit();

    // Release the template
    if (pTemplate)
        {

        // bug 975: if no script engines, do not do this
        if(pTemplate->CountScriptEngines() > 0)
            // Return the engine(s) to cache
            DeAllocAndFreeEngines(pEngineInfo, pHitObj->PAppln());

        // If debugging, notify debugger ONPAGEDONE
        if (fDebuggerNotifiedOnStart)
            {
            Assert(pHitObj->PAppln()->FDebuggable());
            pTemplate->NotifyDebuggerOnPageEvent(FALSE);
            }

        pTemplate->Release();
        }

    // It is OK if the event function was not found in global.asa
    if (hr == DISP_E_UNKNOWNNAME || hr == DISP_E_MEMBERNOTFOUND)
        {
        hr = S_OK;
        }

    if (pPoppedASPObjectContext)
        pHitObj->SetASPObjectContext(pPoppedASPObjectContext);

    if (pASPObjectContext)
        pASPObjectContext->Release();

    return hr;
    }

/*===================================================================
CIntrinsicObjects::Prepare

Prepare intrinsics for the request processing

Parameters:
    pSession        session holding the instrinsics (can be NULL)

Returns:
    HRESULT
===================================================================*/
HRESULT CIntrinsicObjects::Prepare
(
CSession *pSession
)
    {
    HRESULT hr = S_OK;

    if (pSession)
        {
        // get request, response, server from session

        if (SUCCEEDED(hr))
            {
            m_pRequest = pSession->PRequest();
            if (m_pRequest)
                m_pRequest->AddRef();
            else
                hr = E_FAIL;
            }

        if (SUCCEEDED(hr))
            {
            m_pResponse = pSession->PResponse();
            if (m_pResponse)
                m_pResponse->AddRef();
            else
                hr = E_FAIL;
            }

        if (SUCCEEDED(hr))
            {
            m_pServer = pSession->PServer();
            if (m_pServer)
                m_pServer->AddRef();
            else
                hr = E_FAIL;
            }
        }
    else
        {
        // create new request, response, server

        if (SUCCEEDED(hr))
            {
            m_pRequest = new CRequest;
            if (!m_pRequest)
                hr = E_OUTOFMEMORY;
            }

        if (SUCCEEDED(hr))
            {
            m_pResponse = new CResponse;
            if (!m_pResponse)
                hr = E_OUTOFMEMORY;
            }

        if (SUCCEEDED(hr))
            {
            m_pServer = new CServer;
            if (!m_pServer)
                hr = E_OUTOFMEMORY;
            }
        }

    // init request, response, server

    if (SUCCEEDED(hr))
        {
        Assert(m_pRequest);
        hr = m_pRequest->Init();
        }

    if (SUCCEEDED(hr))
        {
        Assert(m_pResponse);
        hr = m_pResponse->Init();
        }

    if (SUCCEEDED(hr))
        {
        Assert(m_pServer);
        hr = m_pServer->Init();
        }

    // create the scripting namespace

    if (SUCCEEDED(hr))
        {
        m_pScriptingNamespace = new CScriptingNamespace;
        if (!m_pScriptingNamespace)
            hr = E_OUTOFMEMORY;
        }

    // cleanup on error

    if (FAILED(hr))
        Cleanup();

    m_fIsChild = FALSE;
    return hr;
    }

/*===================================================================
CIntrinsicObjects::PrepareChild

Prepare intrinsics structure for a child request

Parameters:
    pResponse       parent intrinsic
    pRequest        parent intrinsic
    pServer         parent intrinsic

Returns:
    HRESULT
===================================================================*/
HRESULT CIntrinsicObjects::PrepareChild
(
CResponse *pResponse,
CRequest *pRequest,
CServer *pServer
)
    {
    HRESULT hr = S_OK;

    if (!pResponse || !pRequest || !pServer)
        {
        hr = E_FAIL;
        }

    if (SUCCEEDED(hr))
        {
        m_pResponse = pResponse;
        m_pResponse->AddRef();

        m_pRequest = pRequest;
        m_pRequest->AddRef();

        m_pServer = pServer;
        m_pServer->AddRef();

        m_fIsChild = TRUE;
        }

    if (SUCCEEDED(hr))
        {
        m_pScriptingNamespace = new CScriptingNamespace;
        if (!m_pScriptingNamespace)
            hr = E_OUTOFMEMORY;
        }

    if (FAILED(hr))
        Cleanup();

    return hr;
    }

/*===================================================================
CIntrinsicObjects::Cleanup

Cleanup the intrinsics after the request processing

Parameters:

Returns:
    S_OK
===================================================================*/
HRESULT CIntrinsicObjects::Cleanup()
    {
    if (m_pRequest)
        {
        if (!m_fIsChild)
            m_pRequest->UnInit();
        m_pRequest->Release();
        m_pRequest = NULL;
        }

    if (m_pResponse)
        {
        if (!m_fIsChild)
            m_pResponse->UnInit();
        m_pResponse->Release();
        m_pResponse = NULL;
        }

    if (m_pServer)
        {
        if (!m_fIsChild)
            m_pServer->UnInit();
        m_pServer->Release();
        m_pServer = NULL;
        }

    if (m_pScriptingNamespace)
        {
        m_pScriptingNamespace->Release();
        m_pScriptingNamespace = NULL;
        }

    return S_OK;
    }

/*===================================================================
ReInitIntrinsics

Call re-init on each of the intrinsics that require it
to run a new page.

Parameters:
    pHitObj         - pointer to the hit object
    intrinsics      - pointers to the intrinsic IUnknown pointers.
    pEngineInfo     - some engine info
    fPostGlobal     - Is this a reinit after running global.asa?

Returns:
    S_OK on success
===================================================================*/
HRESULT ReInitIntrinsics
(
CHitObj *pHitObj,
const CIntrinsicObjects &intrinsics,
ActiveEngineInfo *pEngineInfo,
BOOL fPostGlobal
)
    {
    HRESULT hr;

    Assert(pHitObj != NULL);
    pHitObj->AssertValid();
    Assert(pEngineInfo != NULL);
    
    // Hand the new CIsapiReqInfo to the Server object
    // Note on bug 682: We do always need to re-init CServer because it takes the phitobj
    if (FAILED(hr = intrinsics.PServer()->ReInit(pHitObj->PIReq(), pHitObj)))
        goto LExit;

    if (FAILED(hr = intrinsics.PScriptingNamespace()->Init()))
        goto LExit;
        
    /*
     * Bug 682 & 671 (better fix to 452 & 512)
     * Dont re-init the Request & response objects after running a Global.Asa
     * because, the running of global.asa may have set cookies into request (bug 671), that reinit
     * would wipe, and the global.asa may have output headers (or other stuff) which impacts the response
     * object (bug 512) that we dont want to reset.
     */
    if (!fPostGlobal)
        {
        if (FAILED(hr = intrinsics.PRequest()->ReInit(pHitObj->PIReq(), pHitObj)))
            goto LExit;
            
        if (FAILED(hr = intrinsics.PResponse()->ReInit(
                                                pHitObj->PIReq(),
                                                pHitObj->PSzNewSessionCookie(),
                                                intrinsics.PRequest(),
                                                GetScriptEngine,
                                                pEngineInfo,
                                                pHitObj
                                                )))
            goto LExit;
        }
            
LExit:
    return(hr);
    }

/*===================================================================
LoadTemplate

Load a template, cleanup and give appropriate errors on failure.

Parameters:
    szFile          - the file to load a template for
    pHitObj         - pointer to the hit object
    ppTemplate      - The returned loaded template
    fGlobalAsa      - is this for Global.asa?

Returns:
    S_OK on success
===================================================================*/
HRESULT LoadTemplate
(
const TCHAR                 *szFile,
      CHitObj               *pHitObj,
      CTemplate             **ppTemplate,
const CIntrinsicObjects     &intrinsics,
      BOOL                  fGlobalAsa,
      BOOL                  *pfTemplateInCache)
{
    HRESULT hr;

    Assert(pHitObj != NULL);
    pHitObj->AssertValid();
    Assert(ppTemplate != NULL);

    // Load the script - cache will AddRef
    if (FAILED(hr = g_TemplateCache.Load(
    							fGlobalAsa,
    							szFile,
    							pHitObj->DWInstanceID(),
    							pHitObj,
    							ppTemplate,
    							pfTemplateInCache)))
        {
        // CONSIDER moving this cleanup into Template.Load
        if (hr == E_COULDNT_OPEN_SOURCE_FILE || hr == E_SOURCE_FILE_IS_EMPTY)
            {
            // Load error string from string table
            // BUG 731: added if to retrieve the correct header

            WCHAR   szwErr[128];
            
            if (hr == E_COULDNT_OPEN_SOURCE_FILE)
                {
                CwchLoadStringOfId(IDH_404_OBJECT_NOT_FOUND, szwErr, 128);
                intrinsics.PResponse()->put_Status( szwErr );
                HandleSysError(404, 0, IDE_404_OBJECT_NOT_FOUND, NULL, NULL, pHitObj);
#ifndef PERF_DISABLE
                g_PerfData.Incr_REQNOTFOUND();
#endif
                }
            // bug 977: silently ignore empty global.asa file
            else if ((E_SOURCE_FILE_IS_EMPTY == hr) && !fGlobalAsa)
                {
                CwchLoadStringOfId(IDH_204_NO_CONTENT, szwErr, 128);
                intrinsics.PResponse()->put_Status( szwErr );
                HandleSysError(204, 0, IDE_204_NO_CONTENT, NULL, NULL, pHitObj);
                }
            }
        // fix for bug 371
        if (*ppTemplate)
            {
            (*ppTemplate)->Release();
            *ppTemplate = NULL;
            }

		if (hr == E_OUTOFMEMORY)
			{
			DBGPRINTF((DBG_CONTEXT, "Loading template returned E_OUTOFMEMORY.  Flushing template & Script Cache.\n"));
			g_TemplateCache.FlushAll();
			g_ScriptManager.FlushAll();
			}
        }

    return(hr);
    }

/*===================================================================
AllocAndLoadEngines

Allocate and load all the engines we need

Parameters:
    pHitObj             - The hit object
    pTemplate           - The template we're gonna run
    pEngineInfo         - Engine info to fill in
    pScriptingNamespace - scripting namespace
    fGlobalAsa          - Are we loading engines to run global.asa?

Returns:
    S_OK on success
===================================================================*/
HRESULT AllocAndLoadEngines
(
CHitObj *pHitObj,
CTemplate *pTemplate,
ActiveEngineInfo *pEngineInfo,
CScriptingNamespace *pScriptingNamespace,
BOOL fGlobalAsa
)
    {
    HRESULT hr = S_OK;
    int iObj;
    WORD iEng;
    WORD iScriptBlock;
    WORD cEngines = pTemplate->CountScriptEngines();

    Assert(pHitObj != NULL);
    pHitObj->AssertValid();
    Assert(pTemplate != NULL);
    Assert(pEngineInfo != NULL);
    Assert(pScriptingNamespace != NULL);

    // Load objects from template into hit object
    for (iObj = pTemplate->Count(tcompObjectInfo) - 1; iObj >= 0; --iObj)
        {
        CHAR      *szObjectName = NULL;
        CLSID      clsid;
        CompScope scope;
        CompModel model;
        CMBCSToWChar    convStr;
        
        // get object-info from template and add to hitobj's list of objects
        hr = pTemplate->GetObjectInfo(iObj, &szObjectName, &clsid, &scope, &model);
        if(FAILED(hr))
            goto LExit;

        hr = convStr.Init(szObjectName);

        if (FAILED(hr))
            goto LExit;

        // ignore error ?
        pHitObj->AddComponent(ctTagged, clsid, scope, model, convStr.GetString());
        }

    // bug 975: if no script engines, exit now
    if(cEngines == 0)
        goto LExit;
    
    // Allocate space for script engines
    //
    // NOTE: There is a timing problem here in that the response object needs to
    //      be instantiated before we instantiate the script engines, but the
    //      response object needs to be able to access the list of active script
    //      engines, because it may need to halt execution.  To accomplish this,
    //      the response object is passed a pointer to the "EngineInfo" structure
    //      as a pointer, and then we modify the contents of the pointer right under
    //      its nose.  We pass an accessor function via pointer so that response just
    //      sees a void pointer.
    //

    if (cEngines == 1)
        {
        // don't do allocations in case of one engine
        pEngineInfo->rgActiveEngines = & (pEngineInfo->siOneActiveEngine);
        }
    else
        {
        pEngineInfo->rgActiveEngines = new ScriptingInfo[cEngines];
        if (pEngineInfo->rgActiveEngines == NULL)
            {
            hr = E_OUTOFMEMORY;
            goto LExit;
            }
        }

    pEngineInfo->cEngines = cEngines;
    pEngineInfo->cActiveEngines = 0;    // number of SUCCESSFULLY instantiated engines

    // Load all of the script engines in advance.
    for (iScriptBlock = 0; iScriptBlock < cEngines; ++iScriptBlock)
        {
        LPCOLESTR       wstrScript;
        SCRIPTSTATE     nScriptState;

        ScriptingInfo *pScriptInfo = &pEngineInfo->rgActiveEngines[iScriptBlock];
        pTemplate->GetScriptBlock(
                                iScriptBlock,
                                &pScriptInfo->szScriptEngine,
                                &pScriptInfo->pProgLangId,
                                &wstrScript);

        // Populate information required for the line mapping callback.
        //
        pScriptInfo->LineMapInfo.iScriptBlock = iScriptBlock;
        pScriptInfo->LineMapInfo.pTemplate = pTemplate;

        // acquire a script engine by:
        //
        //  getting an engine from the template object (if it has one)
        //  else from the script manager.
        //
        // If we are in debug mode, the templates tend to be greedy and hold
        // onto script engines.  (See notes in scrptmgr.h)
        //
        pScriptInfo->pScriptEngine = NULL;
        
        if (pHitObj->PAppln()->FDebuggable())
            {
            pScriptInfo->pScriptEngine = pTemplate->GetActiveScript(iScriptBlock);
            
            if (pScriptInfo->pScriptEngine)
                {
                // If we got one, we don't need to re-init the engine
                nScriptState = SCRIPTSTATE_INITIALIZED;
                hr = static_cast<CActiveScriptEngine *>(pScriptInfo->pScriptEngine)->ReuseEngine(pHitObj, NULL, iScriptBlock, pHitObj->DWInstanceID());
                }
            }
            
        if (pScriptInfo->pScriptEngine == NULL)
            {
            hr = g_ScriptManager.GetEngine(LOCALE_SYSTEM_DEFAULT,
                                            *(pScriptInfo->pProgLangId),
                                            pTemplate->GetSourceFileName(),
                                            pHitObj,
                                            &pScriptInfo->pScriptEngine,
                                            &nScriptState,
                                            pTemplate,
                                            iScriptBlock);
            }
        if (FAILED(hr))
            goto LExit;

        // BUG 252: Keep track of how many engines we actually instantiate
        ++pEngineInfo->cActiveEngines;

        if (nScriptState == SCRIPTSTATE_UNINITIALIZED || fGlobalAsa)
            {
            if (FAILED(hr = pScriptInfo->pScriptEngine->AddObjects(!fGlobalAsa)))
                goto LExit;
            }

        if (nScriptState == SCRIPTSTATE_UNINITIALIZED)
            {
            if (FAILED(hr = pScriptInfo->pScriptEngine->AddScriptlet(wstrScript)))
                goto LExit;
            }

        // Add the engine to the scripting namespace
        if (FAILED(hr = pScriptingNamespace->AddEngineToNamespace(
                                                (CActiveScriptEngine *)pScriptInfo->pScriptEngine)))
                goto LExit;

		// Update locale & code page (in case they are different om this page)
		pScriptInfo->pScriptEngine->UpdateLocaleInfo(hostinfoLocale);
		pScriptInfo->pScriptEngine->UpdateLocaleInfo(hostinfoCodePage);
        }

    // Add the scripting namespace to each script engine. Because all engines might not
    // implement "lazy instantiation", this code requires all
    // engines are pre-instantiated (which means we can't do it in the above loop.)
    // Add the scripting namespace to the hitobj first
    pHitObj->AddScriptingNamespace(pScriptingNamespace);
        
    for (iEng = 0; iEng < pEngineInfo->cActiveEngines; ++iEng)
        pEngineInfo->rgActiveEngines[iEng].pScriptEngine->AddScriptingNamespace();

    /*
     * Bug 735:
     * Bring all engines except the "primary engine" (engine 0) to runnable state
     * in the order in which script for the given language was found in the script file
     */
    for (iEng = 1; iEng < pEngineInfo->cActiveEngines; ++iEng)
        {
        hr = pEngineInfo->rgActiveEngines[iEng].pScriptEngine->MakeEngineRunnable();
        if (FAILED(hr))
            goto LExit;
        }
    
LExit:
    return(hr);
    }

/*===================================================================
DeAllocAndFreeEngines

Deallocate and free any loaded engines

Parameters:
    pEngineInfo         - Engine info to release

Returns:
    Nothing
===================================================================*/
VOID DeAllocAndFreeEngines
(
ActiveEngineInfo *pEngineInfo,
CAppln *pAppln
)
    {
    WORD iEng;

    Assert(pEngineInfo != NULL);

    if (pEngineInfo->cActiveEngines > 0) {
        if (pEngineInfo->rgActiveEngines == NULL) {
            Assert(pEngineInfo->rgActiveEngines);
        }
        else {
            for (iEng = 0; iEng < pEngineInfo->cActiveEngines; ++iEng)
                g_ScriptManager.ReturnEngineToCache(&pEngineInfo->rgActiveEngines[iEng].pScriptEngine, pAppln);
            pEngineInfo->cActiveEngines = 0;
        }
    }
    if (pEngineInfo->cEngines > 1)
        {
        delete pEngineInfo->rgActiveEngines;
        }

    pEngineInfo->cEngines = 0;
    pEngineInfo->rgActiveEngines = NULL;
    }

/*===================================================================
GetScriptEngine

Get a script engine based on index. Return NULL if the index
is not in range.  (this is a callback)

The AllocAndLoadEngines function will create an array of ScriptingInfo
structures that are defined here.  It contains all the infomation
needed to 1. set up the MapScript2SourceLine callback,
2. merge namespaces, 3. set up this callback.

Parameters:
    iScriptEngine - the script engine to retrieve
    pvData      - instance data for the function

Returns:
    The requested script engine or NULL if not such engine
    
Side effects:
    None
===================================================================*/   
CScriptEngine *GetScriptEngine
(
INT iScriptEngine,
VOID *pvData
)
    {
    ActiveEngineInfo *pInfo = static_cast<ActiveEngineInfo *>(pvData);
    if (unsigned(iScriptEngine) >= unsigned(pInfo->cActiveEngines))
        {
        // Note: the caller has no idea how many script engines there are.
        // if the caller asks for an engine out of range, return NULL so they
        // know they have asked for more than there are
        return NULL;
        }

    return(pInfo->rgActiveEngines[iScriptEngine].pScriptEngine);
    }

/*===================================================================
CallScriptFunctionOfEngine

Calls a script engine to execute one of its functions

Returns:
    S_OK on success
    
Side effects:
    None
===================================================================*/
HRESULT CallScriptFunctionOfEngine
(
ActiveEngineInfo &engineInfo,
short iScriptBlock,
wchar_t *strFunction,
CASPObjectContext   *pASPObjectContext  /* = NULL */
)
{
    HRESULT hr;
    CScriptEngine *pScriptEngine = (CScriptEngine *)engineInfo.rgActiveEngines[iScriptBlock].pScriptEngine;


    Assert(engineInfo.rgActiveEngines != NULL);
    Assert(pScriptEngine != NULL);

    hr = pScriptEngine->Call(strFunction);

    // housekeeping for the transacted case...

    if (pASPObjectContext != NULL) {

        // If the script timed out or there was an unhandled error, then autoabort
        if (SUCCEEDED(hr) && (pScriptEngine->FScriptTimedOut() || pScriptEngine->FScriptHadError()))
        {
            hr = pASPObjectContext->SetAbort();
        }

        // If the script author did not do an explicit SetComplete or SetAbort
        // then do a SetComplete here so Viper will return the transaction
        // completion status to the caller
        if (SUCCEEDED(hr) && !pASPObjectContext->FAborted())
        {
            hr = pASPObjectContext->SetComplete();
        }
    }

    return hr;
}

/*===================================================================
CallScriptFunction

Calls each script engine in turn to execute a script function;
exits when an engine succeeds or we run out of engines.

Returns:
    S_OK on success
    
Side effects:
    None
===================================================================*/
HRESULT CallScriptFunction
(
ActiveEngineInfo &engineInfo,
wchar_t *strFunction
)
    {
    HRESULT hr = E_FAIL;
    int     i;
    
    for (i = 0; i < engineInfo.cActiveEngines; i++)
        {
        // if execution succeeds, bail
        if (SUCCEEDED(hr = CallScriptFunctionOfEngine(engineInfo, (SHORT)i, strFunction)))
            goto LExit;

        // if execution fails with exception other then unknown name, bail
        if (hr != DISP_E_UNKNOWNNAME && hr != DISP_E_MEMBERNOTFOUND)
            goto LExit;
        }

LExit:
    return hr;
    }

/*===================================================================
TestScriptFunction

Tests each script engine in turn to test [the existance of] a script
function; exits when an engine succeeds or we run out of engines.

Parameters
    ActiveEngineInfo &engineInfo
    wchar_t          *strFunction       functions name


Returns:
    S_OK if exists
    
Side effects:
    None
===================================================================*/
HRESULT TestScriptFunction
(
ActiveEngineInfo &engineInfo,
wchar_t *strFunction
)
    {
    HRESULT hr = E_FAIL;
    
    for (int i = 0; i < engineInfo.cActiveEngines; i++)
        {
        hr = engineInfo.rgActiveEngines[i].pScriptEngine->
            CheckEntryPoint(strFunction);

        // if execution succeeds, bail
        if (SUCCEEDED(hr))
            break;

        // if fails with result other then unknown name, bail
        if (hr != DISP_E_UNKNOWNNAME && hr != DISP_E_MEMBERNOTFOUND)
            break;
        }

    return hr;
    }