/* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2004 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.0 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_0.txt. | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Wez Furlong | +----------------------------------------------------------------------+ */ /* $Id$ */ /* Implementation Notes: * * PHP stores scripting engine state in thread-local storage. That means * that we need to create a dedicated thread per-engine so that a host can * use more than one engine object per thread. * * There are some interesting synchronization issues: Anything to do with * running script in the PHP/Zend engine must take place on the engine * thread. Likewise, calling back to the host must take place on the base * thread - the thread that set the script site. * */ #define _WIN32_DCOM #define ZEND_INCLUDE_FULL_WINDOWS_HEADERS #include "php.h" extern "C" { #include "php_main.h" #include "SAPI.h" #include "zend.h" #include "zend_execute.h" #include "zend_compile.h" #include "php_globals.h" #include "php_variables.h" #include "php_ini.h" #include "php5activescript.h" #include "ext/com_dotnet/php_com_dotnet.h" #include "ext/com_dotnet/php_com_dotnet_internal.h" } #include "php_ticks.h" #include "php5as_scriptengine.h" #include "php5as_classfactory.h" #include #undef php_win_err #define ACTIVEPHP_THREADING_MODE COINIT_MULTITHREADED /* {{{ trace */ static inline void trace(char *fmt, ...) { va_list ap; char buf[4096]; sprintf(buf, "T=%08x ", tsrm_thread_id()); OutputDebugString(buf); va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); OutputDebugString(buf); va_end(ap); } /* }}} */ /* {{{ scriptstate_to_string */ static const char *scriptstate_to_string(SCRIPTSTATE ss) { switch(ss) { case SCRIPTSTATE_UNINITIALIZED: return "SCRIPTSTATE_UNINITIALIZED"; case SCRIPTSTATE_INITIALIZED: return "SCRIPTSTATE_INITIALIZED"; case SCRIPTSTATE_STARTED: return "SCRIPTSTATE_STARTED"; case SCRIPTSTATE_CONNECTED: return "SCRIPTSTATE_CONNECTED"; case SCRIPTSTATE_DISCONNECTED: return "SCRIPTSTATE_DISCONNECTED"; case SCRIPTSTATE_CLOSED: return "SCRIPTSTATE_CLOSED"; default: return "unknown"; } } /* }}} */ /* {{{ TWideString */ /* This class helps manipulate strings from OLE. * It does not use emalloc, so it is better suited for passing pointers * between threads. */ class TWideString { public: LPOLESTR m_ole; char *m_ansi; int m_ansi_strlen; TWideString(LPOLESTR olestr) { m_ole = olestr; m_ansi = NULL; } TWideString(LPCOLESTR olestr) { m_ole = (LPOLESTR)olestr; m_ansi = NULL; } ~TWideString() { if (m_ansi) { CoTaskMemFree(m_ansi); } m_ansi = NULL; } char *safe_ansi_string() { char *ret = ansi_string(); if (ret == NULL) return ""; return ret; } int ansi_len(void) { /* force conversion if it has not already occurred */ if (m_ansi == NULL) ansi_string(); return m_ansi_strlen; } static BSTR bstr_from_ansi(char *ansi) { OLECHAR *ole = NULL; BSTR bstr = NULL; int req = MultiByteToWideChar(CP_ACP, 0, ansi, -1, NULL, 0); if (req) { ole = (OLECHAR*)CoTaskMemAlloc((req + 1) * sizeof(OLECHAR)); if (ole) { req = MultiByteToWideChar(CP_ACP, 0, ansi, -1, ole, req); req--; ole[req] = 0; bstr = SysAllocString(ole); CoTaskMemFree(ole); } } return bstr; } char *ansi_string(void) { if (m_ansi) return m_ansi; if (m_ole == NULL) return NULL; int bufrequired = WideCharToMultiByte(CP_ACP, 0, m_ole, -1, NULL, 0, NULL, NULL); if (bufrequired) { m_ansi = (char*)CoTaskMemAlloc(bufrequired + 1); if (m_ansi) { m_ansi_strlen = WideCharToMultiByte(CP_ACP, 0, m_ole, -1, m_ansi, bufrequired + 1, NULL, NULL); if (m_ansi_strlen) { m_ansi_strlen--; m_ansi[m_ansi_strlen] = 0; } else { trace("conversion failed with return code %08x\n", GetLastError()); } } } return m_ansi; } }; /* }}} */ /* {{{ code fragment structures */ enum fragtype { FRAG_MAIN, FRAG_SCRIPTLET, FRAG_PROCEDURE }; typedef struct { enum fragtype fragtype; zend_op_array *opcodes; char *code; int persistent; /* should be retained for Clone */ int executed; /* for "main" */ char *functionname; unsigned int codelen; unsigned int starting_line; TPHPScriptingEngine *engine; void *ptr; } code_frag; #define FRAG_CREATE_FUNC (char*)-1 static code_frag *compile_code_fragment( enum fragtype fragtype, char *functionname, LPCOLESTR code, ULONG starting_line, EXCEPINFO *excepinfo, TPHPScriptingEngine *engine TSRMLS_DC); static int execute_code_fragment(code_frag *frag, VARIANT *varResult, EXCEPINFO *excepinfo TSRMLS_DC); static void free_code_fragment(code_frag *frag TSRMLS_DC); static code_frag *clone_code_fragment(code_frag *frag, TPHPScriptingEngine *engine TSRMLS_DC); /* }}} */ /* Magic for handling threading correctly */ static inline HRESULT SEND_THREAD_MESSAGE(TPHPScriptingEngine *engine, LONG msg, WPARAM wparam, LPARAM lparam TSRMLS_DC) { if (engine->m_enginethread == 0) return E_UNEXPECTED; if (tsrm_thread_id() == (engine)->m_enginethread) return (engine)->engine_thread_handler((msg), (wparam), (lparam), NULL TSRMLS_CC); return (engine)->SendThreadMessage((msg), (wparam), (lparam)); } /* These functions do some magic so that interfaces can be * used across threads without worrying about marshalling * or not marshalling, as appropriate. * Win95 without DCOM 1.1, and NT SP 2 or lower do not have * the GIT; so we emulate the GIT using other means. * If you trace problems back to this code, installing the relevant * SP should solve them. * */ static inline HRESULT GIT_get(DWORD cookie, REFIID riid, void **obj) { IGlobalInterfaceTable *git; HRESULT ret; if (SUCCEEDED(CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL, CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable, (void**)&git))) { ret = git->GetInterfaceFromGlobal(cookie, riid, obj); git->Release(); return ret; } return CoGetInterfaceAndReleaseStream((LPSTREAM)cookie, riid, obj); } static inline HRESULT GIT_put(IUnknown *unk, REFIID riid, DWORD *cookie) { IGlobalInterfaceTable *git; HRESULT ret; if (SUCCEEDED(CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL, CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable, (void**)&git))) { ret = git->RegisterInterfaceInGlobal(unk, riid, cookie); git->Release(); return ret; } return CoMarshalInterThreadInterfaceInStream(riid, unk, (LPSTREAM*)cookie); } static inline HRESULT GIT_revoke(DWORD cookie, IUnknown *unk) { IGlobalInterfaceTable *git; HRESULT ret; if (SUCCEEDED(CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL, CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable, (void**)&git))) { ret = git->RevokeInterfaceFromGlobal(cookie); git->Release(); } /* Kill remote clients */ return CoDisconnectObject(unk, 0); } /* {{{ A generic stupid IDispatch implementation */ class IDispatchImpl: public IDispatch { protected: volatile LONG m_refcount; public: /* IUnknown */ STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject) { *ppvObject = NULL; if (IsEqualGUID(IID_IDispatch, iid)) { *ppvObject = (IDispatch*)this; } else if (IsEqualGUID(IID_IUnknown, iid)) { *ppvObject = this; } if (*ppvObject) { AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP_(DWORD) AddRef(void) { return InterlockedIncrement(const_cast (&m_refcount)); } STDMETHODIMP_(DWORD) Release(void) { DWORD ret = InterlockedDecrement(const_cast (&m_refcount)); trace("%08x: IDispatchImpl: release ref count is now %d\n", this, ret); if (ret == 0) delete this; return ret; } /* IDispatch */ STDMETHODIMP GetTypeInfoCount(unsigned int * pctinfo) { *pctinfo = 0; trace("%08x: IDispatchImpl: GetTypeInfoCount\n", this); return S_OK; } STDMETHODIMP GetTypeInfo( unsigned int iTInfo, LCID lcid, ITypeInfo **ppTInfo) { trace("%08x: IDispatchImpl: GetTypeInfo\n", this); return DISP_E_BADINDEX; } STDMETHODIMP GetIDsOfNames( REFIID riid, OLECHAR **rgszNames, unsigned int cNames, LCID lcid, DISPID *rgDispId) { unsigned int i; trace("%08x: IDispatchImpl: GetIDsOfNames: \n", this); for (i = 0; i < cNames; i++) { TWideString name(rgszNames[i]); trace(" %s\n", name.ansi_string()); } trace("----\n"); return DISP_E_UNKNOWNNAME; } STDMETHODIMP Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr) { trace("%08x: IDispatchImpl: Invoke dispid %08x\n", this, dispIdMember); return S_OK; } IDispatchImpl() { m_refcount = 1; } virtual ~IDispatchImpl() { } }; /* }}} */ /* {{{ This object represents the PHP engine to the scripting host. * Although the docs say it's implementation is optional, I found that * the Windows Script host would crash if we did not provide it. */ class ScriptDispatch: public IDispatchImpl { public: ScriptDispatch() { m_refcount = 1; } }; /* }}} */ /* {{{ This object is used in conjunction with IActiveScriptParseProcedure to * allow scriptlets to be bound to events. IE uses this for declaring * event handlers such as onclick="...". * The compiled code is stored in this object; IE will call * IDispatch::Invoke when the element is clicked. * */ class ScriptProcedureDispatch: public IDispatchImpl { public: code_frag *m_frag; DWORD m_procflags; TPHPScriptingEngine *m_engine; DWORD m_gitcookie; STDMETHODIMP Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr) { TSRMLS_FETCH(); if (m_frag) { trace("%08x: Procedure Dispatch: Invoke dispid %08x\n", this, dispIdMember); SEND_THREAD_MESSAGE(m_engine, PHPSE_EXEC_PROC, 0, (LPARAM)this TSRMLS_CC); } return S_OK; } ScriptProcedureDispatch() { m_refcount = 1; GIT_put((IDispatch*)this, IID_IDispatch, &m_gitcookie); } }; /* }}} */ /* {{{ code fragment management */ static code_frag *compile_code_fragment( enum fragtype fragtype, char *functionname, LPCOLESTR code, ULONG starting_line, EXCEPINFO *excepinfo, TPHPScriptingEngine *engine TSRMLS_DC) { zval pv; int code_offs = 0; char namebuf[256]; code_frag *frag = (code_frag*)CoTaskMemAlloc(sizeof(code_frag)); memset(frag, 0, sizeof(code_frag)); frag->engine = engine; /* handle the function name */ if (functionname) { int namelen; if (functionname == FRAG_CREATE_FUNC) { ULONG n = ++engine->m_lambda_count; sprintf(namebuf, "__frag_%08x_%u", engine, n); functionname = namebuf; } namelen = strlen(functionname); code_offs = namelen + sizeof("function (){"); frag->functionname = (char*)CoTaskMemAlloc((namelen + 1) * sizeof(char)); memcpy(frag->functionname, functionname, namelen+1); } frag->functionname = functionname; trace("%08x: COMPILED FRAG\n", frag); frag->codelen = WideCharToMultiByte(CP_ACP, 0, code, -1, NULL, 0, NULL, NULL); frag->code = (char*)CoTaskMemAlloc(sizeof(char) * (frag->codelen + code_offs + 1)); if (functionname) { sprintf(frag->code, "function %s(){ ", functionname); } frag->codelen = WideCharToMultiByte(CP_ACP, 0, code, -1, frag->code + code_offs, frag->codelen, NULL, NULL) - 1; if (functionname) { frag->codelen += code_offs + 1; frag->code[frag->codelen-1] = '}'; frag->code[frag->codelen] = 0; } trace("code to compile is:\ncode_offs=%d func=%s\n%s\n", code_offs, functionname, frag->code); frag->fragtype = fragtype; frag->starting_line = starting_line; pv.type = IS_STRING; pv.value.str.val = frag->code; pv.value.str.len = frag->codelen; frag->opcodes = compile_string(&pv, "fragment" TSRMLS_CC); if (frag->opcodes == NULL) { free_code_fragment(frag TSRMLS_CC); if (excepinfo) { memset(excepinfo, 0, sizeof(EXCEPINFO)); excepinfo->wCode = 1000; excepinfo->bstrSource = TWideString::bstr_from_ansi("fragment"); excepinfo->bstrDescription = TWideString::bstr_from_ansi("Problem while parsing/compiling"); } return NULL; } return frag; } static void free_code_fragment(code_frag *frag TSRMLS_DC) { switch(frag->fragtype) { case FRAG_PROCEDURE: if (frag->ptr) { ScriptProcedureDispatch *disp = (ScriptProcedureDispatch*)frag->ptr; disp->Release(); GIT_revoke(disp->m_gitcookie, (IDispatch*)disp); frag->ptr = NULL; } break; } if (frag->opcodes) destroy_op_array(frag->opcodes TSRMLS_CC); if (frag->functionname) CoTaskMemFree(frag->functionname); CoTaskMemFree(frag->code); CoTaskMemFree(frag); } static code_frag *clone_code_fragment(code_frag *frag, TPHPScriptingEngine *engine TSRMLS_DC) { zval pv; code_frag *newfrag = (code_frag*)CoTaskMemAlloc(sizeof(code_frag)); memset(newfrag, 0, sizeof(code_frag)); newfrag->engine = engine; trace("%08x: CLONED FRAG\n", newfrag); newfrag->persistent = frag->persistent; newfrag->codelen = frag->codelen; newfrag->code = (char*)CoTaskMemAlloc(sizeof(char) * frag->codelen + 1); memcpy(newfrag->code, frag->code, frag->codelen + 1); if (frag->functionname) { int namelen = strlen(frag->functionname); newfrag->functionname = (char*)CoTaskMemAlloc(sizeof(char) * (namelen + 1)); memcpy(newfrag->functionname, frag->functionname, namelen+1); } else { newfrag->functionname = NULL; } newfrag->fragtype = frag->fragtype; newfrag->starting_line = frag->starting_line; pv.type = IS_STRING; pv.value.str.val = newfrag->code; pv.value.str.len = newfrag->codelen; newfrag->opcodes = compile_string(&pv, "fragment" TSRMLS_CC); if (newfrag->opcodes == NULL) { free_code_fragment(newfrag TSRMLS_CC); /* if (excepinfo) { memset(excepinfo, 0, sizeof(EXCEPINFO)); excepinfo->wCode = 1000; excepinfo->bstrSource = TWideString::bstr_from_ansi("fragment"); excepinfo->bstrDescription = TWideString::bstr_from_ansi("Problem while parsing/compiling"); } */ return NULL; } return newfrag; } static int execute_code_fragment(code_frag *frag, VARIANT *varResult, EXCEPINFO *excepinfo TSRMLS_DC) { zval *retval_ptr = NULL; jmp_buf *orig_jmpbuf; jmp_buf err_trap; if (frag->fragtype == FRAG_MAIN && frag->executed) return 1; orig_jmpbuf = frag->engine->m_err_trap; frag->engine->m_err_trap = &err_trap; if (setjmp(err_trap) == 0) { trace("*** Executing code in thread %08x\n", tsrm_thread_id()); if (frag->functionname) { zval fname; fname.type = IS_STRING; fname.value.str.val = frag->functionname; fname.value.str.len = strlen(frag->functionname); call_user_function_ex(CG(function_table), NULL, &fname, &retval_ptr, 0, NULL, 1, NULL TSRMLS_CC); } else { zend_op_array *active_op_array = EG(active_op_array); zend_function_state *function_state_ptr = EG(function_state_ptr); zval **return_value_ptr_ptr = EG(return_value_ptr_ptr); zend_op **opline_ptr = EG(opline_ptr); EG(return_value_ptr_ptr) = &retval_ptr; EG(active_op_array) = frag->opcodes; EG(no_extensions) = 1; zend_execute(frag->opcodes TSRMLS_CC); EG(no_extensions) = 0; EG(opline_ptr) = opline_ptr; EG(active_op_array) = active_op_array; EG(function_state_ptr) = function_state_ptr; EG(return_value_ptr_ptr) = return_value_ptr_ptr; } } else { trace("*** --> caught error while executing\n"); if (frag->engine->m_in_main) frag->engine->m_stop_main = 1; } frag->engine->m_err_trap = orig_jmpbuf; if (frag->fragtype == FRAG_MAIN) frag->executed = 1; if (varResult) VariantInit(varResult); if (retval_ptr) { if (varResult) php_com_variant_from_zval(varResult, retval_ptr, CP_ACP TSRMLS_CC); zval_ptr_dtor(&retval_ptr); } return 1; } static void frag_dtor(void *pDest) { TSRMLS_FETCH(); code_frag *frag = *(code_frag**)pDest; free_code_fragment(frag TSRMLS_CC); } /* }}} */ /* glue for getting back into the OO land */ static DWORD WINAPI begin_engine_thread(LPVOID param) { TPHPScriptingEngine *engine = (TPHPScriptingEngine*)param; engine->engine_thread_func(); trace("engine thread has really gone away!\n"); return 0; } TPHPScriptingEngine::TPHPScriptingEngine() { m_scriptstate = SCRIPTSTATE_UNINITIALIZED; m_pass = NULL; m_in_main = 0; m_stop_main = 0; m_err_trap = NULL; m_lambda_count = 0; m_pass_eng = NULL; m_refcount = 1; m_basethread = tsrm_thread_id(); m_mutex = tsrm_mutex_alloc(); m_sync_thread_msg = CreateEvent(NULL, TRUE, FALSE, NULL); TPHPClassFactory::AddToObjectCount(); m_engine_thread_handle = CreateThread(NULL, 0, begin_engine_thread, this, 0, &m_enginethread); } void activescript_run_ticks(int count) { MSG msg; TSRMLS_FETCH(); TPHPScriptingEngine *engine; trace("ticking %d\n", count); engine = (TPHPScriptingEngine*)SG(server_context); /* PostThreadMessage(engine->m_enginethread, PHPSE_DUMMY_TICK, 0, 0); */ while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { if (msg.hwnd) { PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); TranslateMessage(&msg); DispatchMessage(&msg); } else { break; } } } /* Synchronize with the engine thread */ HRESULT TPHPScriptingEngine::SendThreadMessage(LONG msg, WPARAM wparam, LPARAM lparam) { HRESULT ret; if (m_enginethread == 0) return E_UNEXPECTED; trace("I'm waiting for a mutex in SendThreadMessage\n this=%08x ethread=%08x msg=%08x\n", this, m_enginethread, msg); tsrm_mutex_lock(m_mutex); ResetEvent(m_sync_thread_msg); /* If we call PostThreadMessage before the thread has created the queue, the message * posting fails. MSDN docs recommend the following course of action */ while (!PostThreadMessage(m_enginethread, msg, wparam, lparam)) { Sleep(50); if (m_enginethread == 0) { tsrm_mutex_unlock(m_mutex); trace("breaking out of dodgy busy wait\n"); return E_UNEXPECTED; } } /* Wait for the event object to be signalled. * This is a nice "blocking without blocking" wait; window messages are dispatched * and everything works out quite nicely */ while(1) { DWORD result = MsgWaitForMultipleObjects(1, &m_sync_thread_msg, FALSE, 4000, QS_ALLINPUT); if (result == WAIT_OBJECT_0 + 1) { /* Dispatch some messages */ MSG msg; while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { //trace("dispatching message while waiting\n"); TranslateMessage(&msg); DispatchMessage(&msg); } } else if (result == WAIT_TIMEOUT) { trace("timeout while waiting for thread reply\n"); } else { /* the event was signalled */ break; } } ret = m_sync_thread_ret; ResetEvent(m_sync_thread_msg); tsrm_mutex_unlock(m_mutex); return ret; } TPHPScriptingEngine::~TPHPScriptingEngine() { trace("\n\n *** Engine Destructor Called\n\n"); if (m_scriptstate != SCRIPTSTATE_UNINITIALIZED && m_scriptstate != SCRIPTSTATE_CLOSED && m_enginethread) { Close(); } PostThreadMessage(m_enginethread, WM_QUIT, 0, 0); WaitForSingleObject(m_engine_thread_handle, INFINITE); CloseHandle(m_engine_thread_handle); TPHPClassFactory::RemoveFromObjectCount(); tsrm_mutex_free(m_mutex); } /* Set some executor globals and execute a zend_op_array. * The declaration looks wierd because this can be invoked from * zend_hash_apply_with_argument */ static int execute_main(void *pDest, void *arg TSRMLS_DC) { code_frag *frag = *(code_frag**)pDest; if (frag->fragtype == FRAG_MAIN && !(frag->engine->m_in_main && frag->engine->m_stop_main)) execute_code_fragment(frag, NULL, NULL TSRMLS_CC); return ZEND_HASH_APPLY_KEEP; } static int clone_frags(void *pDest, void *arg TSRMLS_DC) { code_frag *frag, *src = *(code_frag**)pDest; TPHPScriptingEngine *engine = (TPHPScriptingEngine*)arg; if (src->persistent) { frag = clone_code_fragment(src, engine TSRMLS_CC); if (frag) zend_hash_next_index_insert(&engine->m_frags, &frag, sizeof(code_frag*), NULL); else trace("WARNING: clone failed!\n"); } return ZEND_HASH_APPLY_KEEP; } HRESULT TPHPScriptingEngine::engine_thread_handler(LONG msg, WPARAM wparam, LPARAM lParam, int *handled TSRMLS_DC) { HRESULT ret = S_OK; trace("engine_thread_handler: running in thread %08x, should be %08x msg=%08x this=%08x\n", tsrm_thread_id(), m_enginethread, msg, this); if (handled) *handled = 1; if (m_enginethread == 0) return E_UNEXPECTED; switch(msg) { case PHPSE_ADD_TYPELIB: { struct php_active_script_add_tlb_info *info = (struct php_active_script_add_tlb_info*)lParam; ITypeLib *TypeLib; if (SUCCEEDED(LoadRegTypeLib(*info->rguidTypeLib, (USHORT)info->dwMajor, (USHORT)info->dwMinor, LANG_NEUTRAL, &TypeLib))) { php_com_import_typelib(TypeLib, CONST_CS, CP_ACP TSRMLS_CC); TypeLib->Release(); } } break; case PHPSE_STATE_CHANGE: { /* handle the state change here */ SCRIPTSTATE ss = (SCRIPTSTATE)lParam; int start_running = 0; trace("%08x: DoSetScriptState(current=%s, new=%s)\n", this, scriptstate_to_string(m_scriptstate), scriptstate_to_string(ss)); if (m_scriptstate == SCRIPTSTATE_INITIALIZED && (ss == SCRIPTSTATE_STARTED || ss == SCRIPTSTATE_CONNECTED)) start_running = 1; m_scriptstate = ss; /* inform host/site of the change */ if (m_pass_eng) m_pass_eng->OnStateChange(m_scriptstate); if (start_running) { /* run "main()", as described in the docs */ if (m_pass_eng) m_pass_eng->OnEnterScript(); trace("%08x: apply execute main to m_frags\n", this); m_in_main = 1; m_stop_main = 0; zend_hash_apply_with_argument(&m_frags, execute_main, this TSRMLS_CC); m_in_main = 0; trace("%08x: --- done execute main\n", this); if (m_pass_eng) m_pass_eng->OnLeaveScript(); /* docs are a bit ambiguous here, but it appears that we should * inform the host that the main script execution has completed, * and also what the return value is */ VARIANT varRes; VariantInit(&varRes); if (m_pass_eng) m_pass_eng->OnScriptTerminate(&varRes, NULL); /* m_scriptstate = SCRIPTSTATE_INITIALIZED; if (m_pass_eng) m_pass_eng->OnStateChange(m_scriptstate); */ } } break; case PHPSE_INIT_NEW: { /* Prepare PHP/ZE for use */ trace("%08x: m_frags : INIT NEW\n", this); #if 0 zend_hash_init(&m_frags, 0, NULL, frag_dtor, TRUE); SG(options) |= SAPI_OPTION_NO_CHDIR; SG(server_context) = this; /* override the default PHP error callback */ zend_error_cb = activescript_error_handler; zend_alter_ini_entry("register_argc_argv", 19, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); zend_alter_ini_entry("html_errors", 12, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); zend_alter_ini_entry("implicit_flush", 15, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); zend_alter_ini_entry("max_execution_time", 19, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); php_request_startup(TSRMLS_C); PG(during_request_startup) = 0; // trace("\n\n *** ticks func at %08x %08x ***\n\n\n", activescript_run_ticks, &activescript_run_ticks); // php_add_tick_function(activescript_run_ticks); #endif } break; case PHPSE_CLOSE: { /* Close things down */ trace("%08x: m_frags : CLOSE/DESTROY\n", this); m_scriptstate = SCRIPTSTATE_CLOSED; if (m_pass_eng) { m_pass_eng->OnStateChange(m_scriptstate); trace("%08x: release site from this side\n", this); m_pass_eng->Release(); m_pass_eng = NULL; } #if 0 zend_hash_destroy(&m_frags); php_request_shutdown(NULL); #endif break; } break; case PHPSE_CLONE: { /* Clone the engine state. This is semantically equal to serializing all * the parsed code from the source and unserializing it in the dest (this). * IE doesn't appear to use it, but Windows Script Host does. I'd expect * ASP/ASP.NET to do so also. * * FIXME: Probably won't work with IActiveScriptParseProcedure scriplets * */ TPHPScriptingEngine *src = (TPHPScriptingEngine*)lParam; trace("%08x: m_frags : CLONE\n", this); zend_hash_apply_with_argument(&src->m_frags, clone_frags, this TSRMLS_CC); } break; case PHPSE_ADD_SCRIPTLET: { /* Parse/compile a chunk of script that will act as an event handler. * If the host supports IActiveScriptParseProcedure, this code will * not be called. * The docs are (typically) vague: AFAICT, once the code has been * compiled, we are supposed to arrange for an IConnectionPoint * advisory connection to the item/subitem, once the script * moves into SCRIPTSTATE_CONNECTED. * That's a lot of work! * * FIXME: this is currently almost useless * */ struct php_active_script_add_scriptlet_info *info = (struct php_active_script_add_scriptlet_info*)lParam; TWideString default_name(info->pstrDefaultName), code(info->pstrCode), item_name(info->pstrItemName), sub_item_name(info->pstrSubItemName), event_name(info->pstrEventName), delimiter(info->pstrDelimiter); /* lets invent a function name for the scriptlet */ char sname[256]; /* should check if the name is already used! */ if (info->pstrDefaultName) strcpy(sname, default_name.ansi_string()); else { sname[0] = 0; strcat(sname, "__"); if (info->pstrItemName) { strcat(sname, item_name.ansi_string()); strcat(sname, "_"); } if (info->pstrSubItemName) { strcat(sname, sub_item_name.ansi_string()); strcat(sname, "_"); } if (info->pstrEventName) strcat(sname, event_name.ansi_string()); } trace("%08x: AddScriptlet:\n state=%s\n name=%s\n code=%s\n item=%s\n subitem=%s\n event=%s\n delim=%s\n line=%d\n", this, scriptstate_to_string(m_scriptstate), default_name.safe_ansi_string(), code.safe_ansi_string(), item_name.safe_ansi_string(), sub_item_name.safe_ansi_string(), event_name.safe_ansi_string(), delimiter.safe_ansi_string(), info->ulStartingLineNumber); code_frag *frag = compile_code_fragment( FRAG_SCRIPTLET, sname, info->pstrCode, info->ulStartingLineNumber, info->pexcepinfo, this TSRMLS_CC); if (frag) { frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT); zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); /* ScriptProcedureDispatch *disp = new ScriptProcedureDispatch; disp->AddRef(); disp->m_frag = frag; disp->m_procflags = info->dwFlags; disp->m_engine = this; frag->ptr = disp; *info->ppdisp = disp; */ ret = S_OK; } else { ret = DISP_E_EXCEPTION; } *info->pbstrName = TWideString::bstr_from_ansi(sname); trace("%08x: done with scriptlet %s\n", this, sname); } break; case PHPSE_GET_DISPATCH: { struct php_active_script_get_dispatch_info *info = (struct php_active_script_get_dispatch_info *)lParam; IDispatch *disp = NULL; if (info->pstrItemName != NULL) { zval **tmp; /* use this rather than php_OLECHAR_to_char because we want to avoid emalloc here */ TWideString itemname(info->pstrItemName); /* Get that item from the global namespace. * If it is an object, export it as a dispatchable object. * */ if (zend_hash_find(&EG(symbol_table), itemname.ansi_string(), itemname.ansi_len() + 1, (void**)&tmp) == SUCCESS) { if (Z_TYPE_PP(tmp) == IS_OBJECT) { /* FIXME: if this causes an allocation (emalloc) and we are * not in the engine thread, things could get ugly!!! */ disp = php_com_wrapper_export(*tmp TSRMLS_CC); } } } else { /* This object represents PHP global namespace */ disp = (IDispatch*) new ScriptDispatch; } if (disp) { ret = GIT_put(disp, IID_IDispatch, &info->dispatch); disp->Release(); trace("GET_DISPATCH: we put it in the GIT\n"); } else { ret = S_FALSE; trace("GET_DISPATCH: FAILED to put it in the GIT\n"); } } break; case PHPSE_ADD_NAMED_ITEM: { /* The Host uses this to add objects to the global namespace. * Some objects are intended to have their child properties * globally visible, so we add those to the global namespace too. * */ struct php_active_script_add_named_item_info *info = (struct php_active_script_add_named_item_info *)lParam; TWideString name(info->pstrName); IDispatch *disp = NULL; HRESULT res; trace("ADD_NAMED_ITEM\n"); #if ACTIVEPHP_THREADING_MODE == COINIT_MULTITHREADED res = info->punk->QueryInterface(IID_IDispatch, (void**)&disp); #else res = GIT_get(info->marshal, IID_IDispatch, (void**)&disp); #endif if (SUCCEEDED(res) && disp) { add_to_global_namespace(disp, info->dwFlags, name.ansi_string() TSRMLS_CC); disp->Release(); } else { trace("Ouch: failed to get IDispatch for %s from GIT '%s'\n", name.ansi_string(), php_win_err(res)); } } break; case PHPSE_SET_SITE: { if (m_pass_eng) { m_pass_eng->Release(); m_pass_eng = NULL; } if (lParam) GIT_get(lParam, IID_IActiveScriptSite, (void**)&m_pass_eng); trace("%08x: site (engine-side) is now %08x (base=%08x)\n", this, m_pass_eng, m_pass); } break; case PHPSE_EXEC_PROC: { ScriptProcedureDispatch *disp = (ScriptProcedureDispatch *)lParam; execute_code_fragment(disp->m_frag, NULL, NULL TSRMLS_CC); } break; case PHPSE_PARSE_PROC: { /* This is the IActiveScriptParseProcedure implementation. * IE uses this to request for an IDispatch that it will invoke in * response to some event, and tells us the code that it wants to * run. * We compile the code and pass it back a dispatch object. * The object will then serialize access to the engine thread and * execute the opcodes */ struct php_active_script_parse_proc_info *info = (struct php_active_script_parse_proc_info*)lParam; TWideString formal_params(info->pstrFormalParams), procedure_name(info->pstrProcedureName), item_name(info->pstrItemName), delimiter(info->pstrDelimiter); trace("%08x: ParseProc:\n state=%s\nparams=%s\nproc=%s\nitem=%s\n delim=%s\n line=%d\n", this, scriptstate_to_string(m_scriptstate), formal_params.ansi_string(), procedure_name.ansi_string(), item_name.safe_ansi_string(), delimiter.safe_ansi_string(), info->ulStartingLineNumber); code_frag *frag = compile_code_fragment( FRAG_PROCEDURE, NULL, info->pstrCode, info->ulStartingLineNumber, NULL, this TSRMLS_CC); if (frag) { frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT); zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); ScriptProcedureDispatch *disp = new ScriptProcedureDispatch; disp->m_frag = frag; disp->m_procflags = info->dwFlags; disp->m_engine = this; frag->ptr = disp; info->dispcookie = disp->m_gitcookie; } else { ret = DISP_E_EXCEPTION; } } break; case PHPSE_PARSE_SCRIPT: { struct php_active_script_parse_info *info = (struct php_active_script_parse_info*)lParam; int doexec; TWideString code(info->pstrCode), item_name(info->pstrItemName), delimiter(info->pstrDelimiter); trace("%08x: ParseScriptText:\n state=%s\ncode=%s\n item=%s\n delim=%s\n line=%d\n", this, scriptstate_to_string(m_scriptstate), code.safe_ansi_string(), item_name.safe_ansi_string(), delimiter.safe_ansi_string(), info->ulStartingLineNumber); code_frag *frag = compile_code_fragment( FRAG_MAIN, info->dwFlags & SCRIPTTEXT_ISEXPRESSION ? FRAG_CREATE_FUNC : NULL, info->pstrCode, info->ulStartingLineNumber, info->pexcepinfo, this TSRMLS_CC); doexec = (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) || m_scriptstate == SCRIPTSTATE_STARTED || m_scriptstate == SCRIPTSTATE_CONNECTED || m_scriptstate == SCRIPTSTATE_DISCONNECTED; if (frag) { frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT); ret = S_OK; if (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) { if (m_scriptstate == SCRIPTSTATE_INITIALIZED) { /* not allowed to execute code in this state */ ret = E_UNEXPECTED; doexec = 0; } } if (doexec) { /* execute the code as an expression */ if (!execute_code_fragment(frag, info->pvarResult, info->pexcepinfo TSRMLS_CC)) ret = DISP_E_EXCEPTION; } zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); } else { ret = DISP_E_EXCEPTION; } if (info->pvarResult) { VariantInit(info->pvarResult); } } break; default: trace("XXXXX: unhandled message type %08x\n", msg); if (handled) *handled = 0; } return ret; } /* The PHP/Zend state actually lives in this thread */ void TPHPScriptingEngine::engine_thread_func(void) { TSRMLS_FETCH(); int handled; int terminated = 0; MSG msg; trace("%08x: engine thread started up!\n", this); CoInitializeEx(0, ACTIVEPHP_THREADING_MODE); zend_first_try { m_tsrm_hack = tsrm_ls; SG(options) |= SAPI_OPTION_NO_CHDIR; SG(headers_sent) = 1; SG(request_info).no_headers = 1; SG(server_context) = this; /* override the default PHP error callback */ zend_error_cb = activescript_error_handler; zend_alter_ini_entry("register_argc_argv", 19, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); zend_alter_ini_entry("html_errors", 12, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); zend_alter_ini_entry("implicit_flush", 15, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); zend_alter_ini_entry("max_execution_time", 19, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); if (SUCCESS == php_request_startup(TSRMLS_C)) { PG(during_request_startup) = 0; if (FAILURE == zend_hash_init(&m_frags, 0, NULL, frag_dtor, 1)) { trace("failed to init frags hash\n"); } while(!terminated) { DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, 4000, QS_ALLINPUT); switch(result) { case WAIT_OBJECT_0: while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { terminated = 1; } else if (msg.hwnd) { TranslateMessage(&msg); DispatchMessage(&msg); } else { handled = 1; m_sync_thread_ret = engine_thread_handler(msg.message, msg.wParam, msg.lParam, &handled TSRMLS_CC); if (handled) SetEvent(m_sync_thread_msg); } } break; case WAIT_TIMEOUT: trace("thread wait timed out\n"); break; default: trace("some strange value\n"); } } #if 0 while(GetMessage(&msg, NULL, 0, 0)) { if (msg.message == WM_QUIT) break; handled = 1; m_sync_thread_ret = engine_thread_handler(msg.message, msg.wParam, msg.lParam, &handled TSRMLS_CC); if (handled) SetEvent(m_sync_thread_msg); } #endif trace("%08x: engine thread exiting!!!!!\n", this); zend_hash_destroy(&m_frags); trace("calling request shutdown\n"); php_request_shutdown(NULL); } else { trace("request startup failed\n"); } } zend_catch { trace("ouch: bailed out\n"); } zend_end_try(); m_enginethread = 0; CoUninitialize(); } /* Only call this in the context of the engine thread, or you'll be sorry. * * When SCRIPTITEM_GLOBALMEMBERS is set, we're only adding COM objects to the namespace. * We could add *all* properties, but I don't like this idea; what if the value changes * while the page is running? We'd be left with stale data. * * TODO: evaluate if it is appropriate to register as an auto_global * */ static inline void make_auto_global(char *name, zval *val TSRMLS_DC) { int namelen = strlen(name); zend_register_auto_global(name, namelen, NULL TSRMLS_CC); zend_auto_global_disable_jit(name, namelen TSRMLS_CC); ZEND_SET_SYMBOL(&EG(symbol_table), name, val); } void TPHPScriptingEngine::add_to_global_namespace(IDispatch *disp, DWORD flags, char *name TSRMLS_DC) { zval *val; ITypeInfo *typ; int i; unsigned int namelen; FUNCDESC *func; BSTR funcname; TYPEATTR *attr; DISPPARAMS dispparams; VARIANT vres; ITypeInfo *rettyp; TYPEATTR *retattr; trace("Add %s to global namespace\n", name); MAKE_STD_ZVAL(val); php_com_wrap_dispatch(val, disp, CP_ACP TSRMLS_CC); if (val == NULL) { disp->Release(); return; } make_auto_global(name, val TSRMLS_CC); if ((flags & SCRIPTITEM_GLOBALMEMBERS) == 0) { disp->Release(); return; } /* Enumerate properties and add those too */ if (FAILED(disp->GetTypeInfo(0, 0, &typ))) { disp->Release(); return; } if (SUCCEEDED(typ->GetTypeAttr(&attr))) { for (i = 0; i < attr->cFuncs; i++) { if (FAILED(typ->GetFuncDesc(i, &func))) continue; /* Look at its type */ if (func->invkind == INVOKE_PROPERTYGET && VT_PTR == func->elemdescFunc.tdesc.vt && VT_USERDEFINED == func->elemdescFunc.tdesc.lptdesc->vt && SUCCEEDED(typ->GetRefTypeInfo(func->elemdescFunc.tdesc.lptdesc->hreftype, &rettyp))) { if (SUCCEEDED(rettyp->GetTypeAttr(&retattr))) { if (retattr->typekind == TKIND_DISPATCH) { /* It's dispatchable */ /* get the value */ dispparams.cArgs = 0; dispparams.cNamedArgs = 0; VariantInit(&vres); if (SUCCEEDED(disp->Invoke(func->memid, IID_NULL, 0, func->invkind, &dispparams, &vres, NULL, NULL))) { /* Get its dispatch */ IDispatch *sub = NULL; if (V_VT(&vres) == VT_UNKNOWN) V_UNKNOWN(&vres)->QueryInterface(IID_IDispatch, (void**)&sub); else if (V_VT(&vres) == VT_DISPATCH) sub = V_DISPATCH(&vres); if (sub) { /* find out its name */ typ->GetDocumentation(func->memid, &funcname, NULL, NULL, NULL); name = php_com_olestring_to_string(funcname, &namelen, CP_ACP, 0); /* add to namespace */ zval *subval; MAKE_STD_ZVAL(subval); php_com_wrap_dispatch(subval, sub, CP_ACP TSRMLS_CC); if (subval) { make_auto_global(name, subval TSRMLS_CC); } efree(name); SysFreeString(funcname); } VariantClear(&vres); } } rettyp->ReleaseTypeAttr(retattr); } rettyp->Release(); } typ->ReleaseFuncDesc(func); } typ->ReleaseTypeAttr(attr); } disp->Release(); } STDMETHODIMP_(DWORD) TPHPScriptingEngine::AddRef(void) { return InterlockedIncrement(const_cast (&m_refcount)); } STDMETHODIMP_(DWORD) TPHPScriptingEngine::Release(void) { DWORD ret = InterlockedDecrement(const_cast (&m_refcount)); if (ret == 0) { trace("%08x: Release: zero refcount, destroy the engine!\n", this); delete this; } return ret; } STDMETHODIMP TPHPScriptingEngine::QueryInterface(REFIID iid, void **ppvObject) { *ppvObject = NULL; if (IsEqualGUID(IID_IActiveScript, iid)) { *ppvObject = (IActiveScript*)this; } else if (IsEqualGUID(IID_IActiveScriptParse, iid)) { *ppvObject = (IActiveScriptParse*)this; } else if (IsEqualGUID(IID_IActiveScriptParseProcedure, iid)) { *ppvObject = (IActiveScriptParseProcedure*)this; #if ACTIVEPHP_OBJECT_SAFETY } else if (IsEqualGUID(IID_IObjectSafety, iid)) { *ppvObject = (IObjectSafety*)this; #endif } else if (IsEqualGUID(IID_IUnknown, iid)) { *ppvObject = this; } else { LPOLESTR guidw; StringFromCLSID(iid, &guidw); { TWideString guid(guidw); trace("%08x: QueryInterface for unsupported %s\n", this, guid.ansi_string()); } CoTaskMemFree(guidw); } if (*ppvObject) { AddRef(); return S_OK; } return E_NOINTERFACE; } /* This is called by the host to set the scrite site. * It also defines the base thread. */ STDMETHODIMP TPHPScriptingEngine::SetScriptSite(IActiveScriptSite *pass) { TSRMLS_FETCH(); tsrm_mutex_lock(m_mutex); trace("%08x: -----> Base thread is %08x\n", this, tsrm_thread_id()); if (m_pass) { m_pass->Release(); m_pass = NULL; SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, 0 TSRMLS_CC); } if (pass == NULL) { trace("Closing down site; we should have no references to objects from the host\n" " m_pass=%08x\n m_pass_eng=%08x\n What about named items??\n", m_pass, m_pass_eng); } m_pass = pass; if (m_pass) { m_pass->AddRef(); DWORD cookie; if (SUCCEEDED(GIT_put(m_pass, IID_IActiveScriptSite, &cookie))) SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, cookie TSRMLS_CC); if (m_scriptstate == SCRIPTSTATE_UNINITIALIZED) SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, SCRIPTSTATE_INITIALIZED TSRMLS_CC); } tsrm_mutex_unlock(m_mutex); return S_OK; } STDMETHODIMP TPHPScriptingEngine::GetScriptSite(REFIID riid, void **ppvObject) { HRESULT ret = S_FALSE; trace("%08x: GetScriptSite()\n", this); tsrm_mutex_lock(m_mutex); if (m_pass) ret = m_pass->QueryInterface(riid, ppvObject); tsrm_mutex_unlock(m_mutex); return ret; } STDMETHODIMP TPHPScriptingEngine::SetScriptState(SCRIPTSTATE ss) { TSRMLS_FETCH(); trace("%08x: SetScriptState(%s)\n", this, scriptstate_to_string(ss)); return SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, ss TSRMLS_CC); } STDMETHODIMP TPHPScriptingEngine::GetScriptState(SCRIPTSTATE *pssState) { trace("%08x: GetScriptState(current=%s)\n", this, scriptstate_to_string(m_scriptstate)); tsrm_mutex_lock(m_mutex); *pssState = m_scriptstate; tsrm_mutex_unlock(m_mutex); return S_OK; } STDMETHODIMP TPHPScriptingEngine::Close(void) { TSRMLS_FETCH(); if (m_pass) { m_pass->Release(); m_pass = NULL; } SEND_THREAD_MESSAGE(this, PHPSE_CLOSE, 0, 0 TSRMLS_CC); return S_OK; } /* Add an item to global namespace. * This is called in the context of the base thread (or perhaps some other thread). * We want to be able to work with the object in the engine thread, so we marshal * it into a stream and let the engine thread deal with it. * This works quite nicely when PHP scripts call into the object; threading is * handled correctly. */ STDMETHODIMP TPHPScriptingEngine::AddNamedItem(LPCOLESTR pstrName, DWORD dwFlags) { struct php_active_script_add_named_item_info info; HRESULT res; TSRMLS_FETCH(); info.pstrName = pstrName; info.dwFlags = dwFlags; res = m_pass->GetItemInfo(pstrName, SCRIPTINFO_IUNKNOWN, &info.punk, NULL); if (SUCCEEDED(res)) { #if ACTIVEPHP_THREADING_MODE == COINIT_MULTITHREADED /* strangely, the GIT doesn't want to give the engine thread the interface * in this threading mode */ SEND_THREAD_MESSAGE(this, PHPSE_ADD_NAMED_ITEM, 0, (LPARAM)&info TSRMLS_CC); #else IDispatch *disp; res = info.punk->QueryInterface(IID_IDispatch, (void**)&disp); if (SUCCEEDED(res) && disp) { if (SUCCEEDED(GIT_put(disp, IID_IDispatch, &info.marshal))) { trace("put disp=%p into git with marshal ID of %x\n", disp, info.marshal); SEND_THREAD_MESSAGE(this, PHPSE_ADD_NAMED_ITEM, 0, (LPARAM)&info TSRMLS_CC); GIT_revoke(info.marshal, disp); } disp->Release(); } else { trace("failed to get IDispatch from punk %s", php_win_err(res)); } info.punk->Release(); #endif } else { trace("failed to get named item, %s", php_win_err(res)); } return S_OK; } /* Bind to a type library */ STDMETHODIMP TPHPScriptingEngine::AddTypeLib( /* [in] */ REFGUID rguidTypeLib, /* [in] */ DWORD dwMajor, /* [in] */ DWORD dwMinor, /* [in] */ DWORD dwFlags) { struct php_active_script_add_tlb_info info; TSRMLS_FETCH(); info.rguidTypeLib = &rguidTypeLib; info.dwMajor = dwMajor; info.dwMinor = dwMinor; info.dwFlags = dwFlags; SEND_THREAD_MESSAGE(this, PHPSE_ADD_TYPELIB, 0, (LPARAM)&info TSRMLS_CC); return S_OK; } /* Returns an object representing the PHP Scripting Engine. * Optionally, a client can request a particular item directly. * For the moment, we only do the bare minimum amount of work * for the engine to work correctly; we can flesh out this part * a little later. */ STDMETHODIMP TPHPScriptingEngine::GetScriptDispatch( /* [in] */ LPCOLESTR pstrItemName, /* [out] */ IDispatch **ppdisp) { TSRMLS_FETCH(); *ppdisp = NULL; struct php_active_script_get_dispatch_info info; info.pstrItemName = pstrItemName; info.dispatch = NULL; /* This hack is required because the host is likely to query us * for a dispatch if we use any of its objects from PHP script. * Since the engine thread will be waiting for the return from * a COM call, we need to deliberately poke a hole in thread * safety so that it is possible to read the symbol table from * outside the engine thread and give it a valid return value. * This is "safe" only in this instance, since we are not modifying * the engine state by looking up the dispatch (I hope). * The scripting engine rules pretty much guarantee that this * method is only called in the base thread. * This appears to only happen when we set the threading to * apartment. */ if (tsrm_thread_id() != m_enginethread) { tsrm_ls = m_tsrm_hack; trace("HEY: hacking thread safety!\n"); } if (S_OK == engine_thread_handler(PHPSE_GET_DISPATCH, 0, (LPARAM)&info, NULL TSRMLS_CC)) { GIT_get(info.dispatch, IID_IDispatch, (void**)ppdisp); } if (*ppdisp) { return S_OK; } return S_FALSE; } STDMETHODIMP TPHPScriptingEngine::GetCurrentScriptThreadID( /* [out] */ SCRIPTTHREADID *pstidThread) { // tsrm_mutex_lock(m_mutex); trace("%08x: GetCurrentScriptThreadID()\n", this); *pstidThread = m_enginethread; // tsrm_mutex_unlock(m_mutex); return S_OK; } STDMETHODIMP TPHPScriptingEngine::GetScriptThreadID( /* [in] */ DWORD dwWin32ThreadId, /* [out] */ SCRIPTTHREADID *pstidThread) { // tsrm_mutex_lock(m_mutex); trace("%08x: GetScriptThreadID()\n", this); *pstidThread = dwWin32ThreadId; // tsrm_mutex_unlock(m_mutex); return S_OK; } STDMETHODIMP TPHPScriptingEngine::GetScriptThreadState( /* [in] */ SCRIPTTHREADID stidThread, /* [out] */ SCRIPTTHREADSTATE *pstsState) { // tsrm_mutex_lock(m_mutex); trace("%08x: GetScriptThreadState()\n", this); *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT; switch(stidThread) { case SCRIPTTHREADID_BASE: stidThread = m_basethread; break; case SCRIPTTHREADID_CURRENT: stidThread = m_enginethread; break; }; if (stidThread == m_basethread) { *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT; } else if (stidThread == m_enginethread) { *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT; } // tsrm_mutex_unlock(m_mutex); return S_OK; } STDMETHODIMP TPHPScriptingEngine::InterruptScriptThread( /* [in] */ SCRIPTTHREADID stidThread, /* [in] */ const EXCEPINFO *pexcepinfo, /* [in] */ DWORD dwFlags) { /* do not serialize this method, or call into the script site */ trace("%08x: InterruptScriptThread()\n", this); return S_OK; } /* Clone is essential when running under Windows Script Host. * It creates an engine to parse the code. Once it is parsed, * the host clones other engines from the original and runs those. * It is intended to be a fast method of running the same script * multiple times in multiple threads. */ STDMETHODIMP TPHPScriptingEngine::Clone( /* [out] */ IActiveScript **ppscript) { TPHPScriptingEngine *cloned = new TPHPScriptingEngine; TSRMLS_FETCH(); trace("%08x: Clone()\n", this); if (ppscript) *ppscript = NULL; if (cloned) { cloned->InitNew(); SEND_THREAD_MESSAGE(cloned, PHPSE_CLONE, 0, (LPARAM)this TSRMLS_CC); trace("%08x: Cloned OK, returning cloned object ptr %08x\n", this, cloned); *ppscript = (IActiveScript*)cloned; return S_OK; } return E_FAIL; } STDMETHODIMP TPHPScriptingEngine::InitNew( void) { TSRMLS_FETCH(); SEND_THREAD_MESSAGE(this, PHPSE_INIT_NEW, 0, 0 TSRMLS_CC); return S_OK; } STDMETHODIMP TPHPScriptingEngine::AddScriptlet( /* [in] */ LPCOLESTR pstrDefaultName, /* [in] */ LPCOLESTR pstrCode, /* [in] */ LPCOLESTR pstrItemName, /* [in] */ LPCOLESTR pstrSubItemName, /* [in] */ LPCOLESTR pstrEventName, /* [in] */ LPCOLESTR pstrDelimiter, /* [in] */ DWORD dwSourceContextCookie, /* [in] */ ULONG ulStartingLineNumber, /* [in] */ DWORD dwFlags, /* [out] */ BSTR *pbstrName, /* [out] */ EXCEPINFO *pexcepinfo) { struct php_active_script_add_scriptlet_info info; TSRMLS_FETCH(); info.pstrDefaultName = pstrDefaultName; info.pstrCode = pstrCode; info.pstrItemName = pstrItemName; info.pstrSubItemName = pstrSubItemName; info.pstrEventName = pstrEventName; info.pstrDelimiter = pstrDelimiter; info.dwSourceContextCookie = dwSourceContextCookie; info.ulStartingLineNumber = ulStartingLineNumber; info.dwFlags = dwFlags; info.pbstrName = pbstrName; info.pexcepinfo = pexcepinfo; return SEND_THREAD_MESSAGE(this, PHPSE_ADD_SCRIPTLET, 0, (LPARAM)&info TSRMLS_CC); } STDMETHODIMP TPHPScriptingEngine::ParseScriptText( /* [in] */ LPCOLESTR pstrCode, /* [in] */ LPCOLESTR pstrItemName, /* [in] */ IUnknown *punkContext, /* [in] */ LPCOLESTR pstrDelimiter, /* [in] */ DWORD dwSourceContextCookie, /* [in] */ ULONG ulStartingLineNumber, /* [in] */ DWORD dwFlags, /* [out] */ VARIANT *pvarResult, /* [out] */ EXCEPINFO *pexcepinfo) { struct php_active_script_parse_info info; TSRMLS_FETCH(); info.pstrCode = pstrCode; info.pstrItemName = pstrItemName; info.punkContext = punkContext; info.pstrDelimiter = pstrDelimiter; info.dwSourceContextCookie = dwSourceContextCookie; info.ulStartingLineNumber = ulStartingLineNumber; info.dwFlags = dwFlags; info.pvarResult = pvarResult; info.pexcepinfo = pexcepinfo; return SEND_THREAD_MESSAGE(this, PHPSE_PARSE_SCRIPT, 0, (LPARAM)&info TSRMLS_CC); } STDMETHODIMP TPHPScriptingEngine::ParseProcedureText( /* [in] */ LPCOLESTR pstrCode, /* [in] */ LPCOLESTR pstrFormalParams, /* [in] */ LPCOLESTR pstrProcedureName, /* [in] */ LPCOLESTR pstrItemName, /* [in] */ IUnknown *punkContext, /* [in] */ LPCOLESTR pstrDelimiter, /* [in] */ DWORD dwSourceContextCookie, /* [in] */ ULONG ulStartingLineNumber, /* [in] */ DWORD dwFlags, /* [out] */ IDispatch **ppdisp) { struct php_active_script_parse_proc_info info; HRESULT ret; TSRMLS_FETCH(); info.pstrCode = pstrCode; info.pstrFormalParams = pstrFormalParams; info.pstrProcedureName = pstrProcedureName; info.pstrItemName = pstrItemName; info.punkContext = punkContext; info.pstrDelimiter = pstrDelimiter; info.dwSourceContextCookie = dwSourceContextCookie; info.ulStartingLineNumber = ulStartingLineNumber; info.dwFlags = dwFlags; ret = SEND_THREAD_MESSAGE(this, PHPSE_PARSE_PROC, 0, (LPARAM)&info TSRMLS_CC); if (ret == S_OK) ret = GIT_get(info.dispcookie, IID_IDispatch, (void**)ppdisp); trace("ParseProc: ret=%08x disp=%08x\n", ret, *ppdisp); return ret; } #if ACTIVEPHP_OBJECT_SAFETY STDMETHODIMP TPHPScriptingEngine::GetInterfaceSafetyOptions( /* [in] */ REFIID riid, // Interface that we want options for /* [out] */ DWORD *pdwSupportedOptions, // Options meaningful on this interface /* [out] */ DWORD *pdwEnabledOptions) // current option values on this interface { /* PHP isn't really safe for untrusted anything */ *pdwSupportedOptions = 0; *pdwEnabledOptions = 0; return S_OK; } STDMETHODIMP TPHPScriptingEngine::SetInterfaceSafetyOptions( /* [in] */ REFIID riid, // Interface to set options for /* [in] */ DWORD dwOptionSetMask, // Options to change /* [in] */ DWORD dwEnabledOptions) // New option values { /* PHP isn't really safe for untrusted anything */ if (dwEnabledOptions == 0) { return S_OK; } return E_FAIL; } #endif extern "C" void activescript_error_func(int type, const char *error_msg, ...) { TSRMLS_FETCH(); TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context); } class TActiveScriptError: public IActiveScriptError { protected: volatile LONG m_refcount; public: /* IUnknown */ STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject) { *ppvObject = NULL; if (IsEqualGUID(IID_IActiveScriptError, iid)) { *ppvObject = (IActiveScriptError*)this; } else if (IsEqualGUID(IID_IUnknown, iid)) { *ppvObject = this; } if (*ppvObject) { AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP_(DWORD) AddRef(void) { return InterlockedIncrement(const_cast (&m_refcount)); } STDMETHODIMP_(DWORD) Release(void) { DWORD ret = InterlockedDecrement(const_cast (&m_refcount)); trace("Release: errobj refcount=%d\n", ret); if (ret == 0) delete this; return ret; } HRESULT STDMETHODCALLTYPE GetExceptionInfo( /* [out] */ EXCEPINFO *pexcepinfo) { memset(pexcepinfo, 0, sizeof(EXCEPINFO)); pexcepinfo->bstrDescription = SysAllocString(m_message); pexcepinfo->bstrSource = SysAllocString(m_filename); pexcepinfo->wCode = 1000; return S_OK; } HRESULT STDMETHODCALLTYPE GetSourcePosition( /* [out] */ DWORD *pdwSourceContext, /* [out] */ ULONG *pulLineNumber, /* [out] */ LONG *plCharacterPosition) { *pdwSourceContext = 0; *pulLineNumber = m_lineno; *plCharacterPosition = 0; return S_OK; } HRESULT STDMETHODCALLTYPE GetSourceLineText( /* [out] */ BSTR *pbstrSourceLine) { *pbstrSourceLine = NULL; return E_FAIL; } BSTR m_filename, m_message; UINT m_lineno; TActiveScriptError(const char *filename, const uint lineno, const char *message) { m_refcount = 0; /* start with zero refcount because this object is passed * directly to the script site; it will call addref */ m_filename = TWideString::bstr_from_ansi((char*)filename); m_message = TWideString::bstr_from_ansi((char*)message); m_lineno = lineno; } ~TActiveScriptError() { trace("%08x: cleaning up error object\n", this); SysFreeString(m_filename); SysFreeString(m_message); } }; extern "C" void activescript_error_handler(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) { TSRMLS_FETCH(); char *buf; int buflen; TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context); buflen = vspprintf(&buf, PG(log_errors_max_len), format, args); trace("%08x: Error: %s\n", engine, buf); /* if it's a fatal error, report it using IActiveScriptError. */ switch(type) { case E_ERROR: case E_CORE_ERROR: case E_COMPILE_ERROR: case E_USER_ERROR: case E_PARSE: /* trigger an error in the host */ TActiveScriptError *eobj = new TActiveScriptError(error_filename, error_lineno, buf); trace("raising error object!\n"); if (engine->m_pass_eng) engine->m_pass_eng->OnScriptError(eobj); /* now throw the exception to abort execution */ if (engine->m_err_trap) longjmp(*engine->m_err_trap, 1); break; } efree(buf); }