/** * javascript.cpp * This file is part of the YATE Project http://YATE.null.ro * * Javascript channel support based on libyscript * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2011-2014 Null Team * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #define NATIVE_TITLE "[native code]" #define MIN_CALLBACK_INTERVAL Thread::idleMsec() using namespace TelEngine; namespace { // anonymous class JsEngineWorker; class JsEngine; class JsModule : public ChanAssistList { public: enum { Preroute = AssistPrivate, EngStart, }; JsModule(); virtual ~JsModule(); virtual void initialize(); virtual void init(int priority); virtual ChanAssist* create(Message& msg, const String& id); bool unload(); virtual bool received(Message& msg, int id); virtual bool received(Message& msg, int id, ChanAssist* assist); void msgPostExecute(const Message& msg, bool handled); inline JsParser& parser() { return m_assistCode; } protected: virtual void statusParams(String& str); virtual bool commandExecute(String& retVal, const String& line); virtual bool commandComplete(Message& msg, const String& partLine, const String& partWord); private: bool evalContext(String& retVal, const String& cmd, ScriptContext* context = 0); void clearPostHook(); JsParser m_assistCode; MessagePostHook* m_postHook; bool m_started; }; INIT_PLUGIN(JsModule); class JsMessage; class JsAssist : public ChanAssist { public: enum State { NotStarted, Routing, ReRoute, Ended, Hangup }; inline JsAssist(ChanAssistList* list, const String& id, ScriptRun* runner) : ChanAssist(list, id), m_runner(runner), m_state(NotStarted), m_handled(false), m_repeat(false) { } virtual ~JsAssist(); virtual void msgStartup(Message& msg); virtual void msgHangup(Message& msg); virtual void msgExecute(Message& msg); virtual bool msgRinging(Message& msg); virtual bool msgAnswered(Message& msg); virtual bool msgPreroute(Message& msg); virtual bool msgRoute(Message& msg); virtual bool msgDisconnect(Message& msg, const String& reason); void msgPostExecute(const Message& msg, bool handled); bool init(); inline State state() const { return m_state; } inline const char* stateName() const { return stateName(m_state); } inline void end() { m_repeat = false; if (m_state < Ended) m_state = Ended; } inline JsMessage* message() { return m_message; } inline void handled() { m_repeat = false; m_handled = true; } inline ScriptContext* context() { return m_runner ? m_runner->context() : 0; } Message* getMsg(ScriptRun* runner) const; static const char* stateName(State st); private: bool runFunction(const String& name, Message& msg, bool* handled = 0); bool runScript(Message* msg, State newState); bool setMsg(Message* msg); void clearMsg(bool fromChannel); ScriptRun* m_runner; State m_state; bool m_handled; bool m_repeat; RefPointer m_message; }; class JsGlobal : public NamedString { public: JsGlobal(const char* scriptName, const char* fileName, bool relPath = true, bool fromCfg = true); virtual ~JsGlobal(); bool fileChanged(const char* fileName) const; inline JsParser& parser() { return m_jsCode; } inline ScriptContext* context() { return m_context; } inline const String& fileName() { return m_file; } bool runMain(); static void markUnused(); static void freeUnused(); static void reloadDynamic(); static bool initScript(const String& scriptName, const String& fileName, bool relPath = true, bool fromCfg = true); static bool reloadScript(const String& scriptName); static void loadScripts(const NamedList* sect); inline static ObjList& globals() { return s_globals; } inline static void unloadAll() { s_globals.clear(); } private: JsParser m_jsCode; RefPointer m_context; bool m_inUse; bool m_confLoaded; String m_file; static ObjList s_globals; }; class JsShared : public JsObject { YCLASS(JsShared,JsObject) public: inline JsShared(Mutex* mtx) : JsObject("Shared",mtx,true) { params().addParam(new ExpFunction("inc")); params().addParam(new ExpFunction("dec")); params().addParam(new ExpFunction("get")); params().addParam(new ExpFunction("set")); params().addParam(new ExpFunction("clear")); params().addParam(new ExpFunction("exists")); } protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); }; class JsTimeEvent : public RefObject { public: JsTimeEvent(JsEngineWorker* worker, const ExpFunction& callback, unsigned int interval,bool repeatable, unsigned int id); void processTimeout(const Time& when); inline bool repeatable() const { return m_repeat; } inline u_int64_t fireTime() const { return m_fire; } inline bool timeout(const Time& when) const { return when.msec() >= m_fire; } inline unsigned int getId() const { return m_id; } private: JsEngineWorker* m_worker; ExpFunction m_callbackFunction; unsigned int m_interval; u_int64_t m_fire; bool m_repeat; unsigned int m_id; }; class JsEngineWorker : public Thread { public: JsEngineWorker(JsEngine* engine, ScriptContext* context, ScriptCode* code); ~JsEngineWorker(); unsigned int addEvent(const ExpFunction& callback, unsigned int interval, bool repeat); bool removeEvent(unsigned int id, bool repeatable); ScriptRun* getRunner(); protected: virtual void run(); void postponeEvent(JsTimeEvent* ev); private: ObjList m_events; Mutex m_eventsMutex; unsigned int m_id; ScriptRun* m_runner; RefPointer m_engine; }; class JsSemaphore : public JsObject { YCLASS(JsSemaphore,JsObject) public: inline JsSemaphore(Mutex* mtx) : JsObject("Semaphore",mtx,true), m_constructor(0), m_exit(false) { XDebug(DebugAll,"JsSemaphore::JsSemaphore() [%p]",this); } inline JsSemaphore(JsSemaphore* constructor, Mutex* mtx, unsigned int maxCount, unsigned int initialCount, const char* name) : JsObject("Semaphore",mtx,true), m_name(name), m_semaphore(maxCount,m_name.c_str(),initialCount), m_constructor(constructor), m_exit(false) { XDebug(DebugAll,"JsSemaphore::JsSemaphore(%u,'%s',%u) [%p]",maxCount, name,initialCount,this); params().addParam(new ExpFunction("wait")); params().addParam(new ExpFunction("signal")); } virtual ~JsSemaphore() { XDebug(DebugAll,"~JsSemaphore() [%p]",this); if (m_constructor) m_constructor->removeSemaphore(this); // Notify all the semaphores that we are exiting. Lock myLock(mutex()); JsSemaphore* js = 0;; while ((js = static_cast(m_semaphores.remove(false)))) js->forceExit(); } void runAsync(ObjList& stack, long maxWait); virtual JsObject* runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context); void removeSemaphore(JsSemaphore* sem); void forceExit(); protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); private: String m_name; Semaphore m_semaphore; JsSemaphore* m_constructor; ObjList m_semaphores; bool m_exit; }; #define MKDEBUG(lvl) params().addParam(new ExpOperation((int64_t)Debug ## lvl,"Debug" # lvl)) #define MKTIME(typ) params().addParam(new ExpOperation((int64_t)SysUsage:: typ ## Time,# typ "Time")) class JsEngine : public JsObject, public DebugEnabler { YCLASS(JsEngine,JsObject) public: inline JsEngine(Mutex* mtx, const char* name = 0) : JsObject("Engine",mtx,true), m_worker(0), m_debugName("javascript") { debugName(m_debugName); debugChain(&__plugin); MKDEBUG(Fail); MKDEBUG(Test); MKDEBUG(Crit); MKDEBUG(GoOn); MKDEBUG(Conf); MKDEBUG(Stub); MKDEBUG(Warn); MKDEBUG(Mild); MKDEBUG(Note); MKDEBUG(Call); MKDEBUG(Info); MKDEBUG(All); MKTIME(Wall); MKTIME(User); MKTIME(Kernel); params().addParam(new ExpFunction("output")); params().addParam(new ExpFunction("debug")); params().addParam(new ExpFunction("alarm")); params().addParam(new ExpFunction("sleep")); params().addParam(new ExpFunction("usleep")); params().addParam(new ExpFunction("yield")); params().addParam(new ExpFunction("idle")); params().addParam(new ExpFunction("restart")); params().addParam(new ExpFunction("dump_r")); params().addParam(new ExpFunction("print_r")); params().addParam(new ExpFunction("dump_t")); params().addParam(new ExpFunction("print_t")); params().addParam(new ExpFunction("debugName")); params().addParam(new ExpFunction("debugLevel")); params().addParam(new ExpFunction("debugEnabled")); params().addParam(new ExpFunction("debugAt")); params().addParam(new ExpFunction("setDebug")); params().addParam(new ExpFunction("uptime")); params().addParam(new ExpFunction("started")); params().addParam(new ExpFunction("exiting")); params().addParam(new ExpFunction("accepting")); if (name) params().addParam(new ExpOperation(name,"name")); params().addParam(new ExpWrapper(new JsShared(mtx),"shared")); params().addParam(new ExpFunction("runParams")); params().addParam(new ExpFunction("configFile")); params().addParam(new ExpFunction("setInterval")); params().addParam(new ExpFunction("clearInterval")); params().addParam(new ExpFunction("setTimeout")); params().addParam(new ExpFunction("clearTimeout")); params().addParam(new ExpFunction("loadLibrary")); params().addParam(new ExpFunction("loadObject")); params().addParam(new ExpFunction("replaceParams")); params().addParam(new ExpFunction("atob")); params().addParam(new ExpFunction("btoa")); params().addParam(new ExpFunction("atoh")); params().addParam(new ExpFunction("htoa")); params().addParam(new ExpFunction("btoh")); params().addParam(new ExpFunction("htob")); addConstructor(params(), "Semaphore", new JsSemaphore(mtx)); } static void initialize(ScriptContext* context, const char* name = 0); inline void resetWorker() { m_worker = 0; } protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); virtual void destroyed(); private: JsEngineWorker* m_worker; String m_debugName; }; #undef MKDEBUG #undef MKTIME class JsMessage : public JsObject { public: inline JsMessage(Mutex* mtx) : JsObject("Message",mtx,true), m_message(0), m_owned(false), m_trackPrio(true) { XDebug(&__plugin,DebugAll,"JsMessage::JsMessage() [%p]",this); params().addParam(new ExpFunction("enqueue")); params().addParam(new ExpFunction("dispatch")); params().addParam(new ExpFunction("name")); params().addParam(new ExpFunction("broadcast")); params().addParam(new ExpFunction("retValue")); params().addParam(new ExpFunction("msgTime")); params().addParam(new ExpFunction("msgAge")); params().addParam(new ExpFunction("getParam")); params().addParam(new ExpFunction("setParam")); params().addParam(new ExpFunction("getColumn")); params().addParam(new ExpFunction("getRow")); params().addParam(new ExpFunction("getResult")); params().addParam(new ExpFunction("copyParams")); } inline JsMessage(Message* message, Mutex* mtx, bool owned) : JsObject(mtx,"[object Message]"), m_message(message), m_owned(owned), m_trackPrio(true) { XDebug(&__plugin,DebugAll,"JsMessage::JsMessage(%p) [%p]",message,this); } virtual ~JsMessage() { XDebug(&__plugin,DebugAll,"JsMessage::~JsMessage() [%p]",this); if (m_owned) TelEngine::destruct(m_message); if (Engine::exiting()) while (m_handlers.remove(false)) ; for (ObjList* o = m_hooks.skipNull();o;o = o->skipNext()) { MessageHook* hook = static_cast(o->get()); Engine::uninstallHook(hook); } } virtual void* getObject(const String& name) const; virtual NamedList* nativeParams() const { return m_message; } virtual void fillFieldNames(ObjList& names) { if (m_message) ScriptContext::fillFieldNames(names,*m_message); } virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context); virtual JsObject* runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context); virtual void initConstructor(JsFunction* construct) { construct->params().addParam(new ExpFunction("install")); construct->params().addParam(new ExpFunction("uninstall")); construct->params().addParam(new ExpFunction("handlers")); construct->params().addParam(new ExpFunction("uninstallHook")); construct->params().addParam(new ExpFunction("installHook")); construct->params().addParam(new ExpFunction("trackName")); } inline void clearMsg() { m_message = 0; m_owned = false; } inline void setMsg(Message* message, bool owned = false) { m_message = message; m_owned = owned; } static void initialize(ScriptContext* context); void runAsync(ObjList& stack, Message* msg); protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); void getColumn(ObjList& stack, const ExpOperation* col, GenObject* context); void getRow(ObjList& stack, const ExpOperation* row, GenObject* context); void getResult(ObjList& stack, const ExpOperation& row, const ExpOperation& col, GenObject* context); bool installHook(ObjList& stack, const ExpOperation& oper, GenObject* context); ObjList m_handlers; ObjList m_hooks; String m_trackName; Message* m_message; bool m_owned; bool m_trackPrio; }; class JsHandler : public MessageHandler { YCLASS(JsHandler,MessageHandler) public: inline JsHandler(const char* name, unsigned priority, const ExpFunction& func, GenObject* context) : MessageHandler(name,priority,__plugin.name()), m_function(func.name(),1) { XDebug(&__plugin,DebugAll,"JsHandler::JsHandler('%s',%u,'%s') [%p]", name,priority,func.name().c_str(),this); ScriptRun* runner = YOBJECT(ScriptRun,context); if (runner) { m_context = runner->context(); m_code = runner->code(); } } virtual ~JsHandler() { XDebug(&__plugin,DebugAll,"JsHandler::~JsHandler() '%s' [%p]",c_str(),this); } virtual bool received(Message& msg) { return false; } inline const ExpFunction& function() const { return m_function; } protected: virtual bool receivedInternal(Message& msg); private: ExpFunction m_function; RefPointer m_context; RefPointer m_code; }; class JsMessageQueue : public MessageQueue { YCLASS(JsMessageQueue,MessageQueue) public: inline JsMessageQueue(const ExpFunction* received,const char* name, unsigned threads, const ExpFunction* trap, unsigned trapLunch, GenObject* context) : MessageQueue(name,threads), m_receivedFunction(0), m_trapFunction(0), m_trapLunch(trapLunch), m_trapCalled(false) { ScriptRun* runner = YOBJECT(ScriptRun,context); if (runner) { m_context = runner->context(); m_code = runner->code(); } if (received) m_receivedFunction = new ExpFunction(received->name(),1); if (trap) m_trapFunction = new ExpFunction(trap->name(),0); } virtual ~JsMessageQueue() { TelEngine::destruct(m_receivedFunction); TelEngine::destruct(m_trapFunction); } virtual bool enqueue(Message* msg); bool matchesFilters(const NamedList& filters); protected: virtual void received(Message& msg); private: ExpFunction* m_receivedFunction; ExpFunction* m_trapFunction; RefPointer m_context; RefPointer m_code; unsigned int m_trapLunch; bool m_trapCalled; }; class JsFile : public JsObject { YCLASS(JsFile,JsObject) public: inline JsFile(Mutex* mtx) : JsObject("File",mtx,true) { XDebug(DebugAll,"JsFile::JsFile() [%p]",this); params().addParam(new ExpFunction("exists")); params().addParam(new ExpFunction("remove")); params().addParam(new ExpFunction("rename")); params().addParam(new ExpFunction("mkdir")); params().addParam(new ExpFunction("rmdir")); params().addParam(new ExpFunction("getFileTime")); params().addParam(new ExpFunction("setFileTime")); } static void initialize(ScriptContext* context); protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); }; class JsConfigFile : public JsObject { public: inline JsConfigFile(Mutex* mtx) : JsObject("ConfigFile",mtx,true) { XDebug(DebugAll,"JsConfigFile::JsConfigFile() [%p]",this); } inline JsConfigFile(Mutex* mtx, const char* name, bool warn) : JsObject("ConfigFile",mtx,true), m_config(name,warn) { XDebug(DebugAll,"JsConfigFile::JsConfigFile('%s') [%p]",name,this); params().addParam(new ExpFunction("name")); params().addParam(new ExpFunction("load")); params().addParam(new ExpFunction("save")); params().addParam(new ExpFunction("count")); params().addParam(new ExpFunction("sections")); params().addParam(new ExpFunction("getSection")); params().addParam(new ExpFunction("getValue")); params().addParam(new ExpFunction("getIntValue")); params().addParam(new ExpFunction("getBoolValue")); params().addParam(new ExpFunction("setValue")); params().addParam(new ExpFunction("addValue")); params().addParam(new ExpFunction("clearSection")); params().addParam(new ExpFunction("clearKey")); params().addParam(new ExpFunction("keys")); } virtual void* getObject(const String& name) const; virtual JsObject* runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context); static void initialize(ScriptContext* context); inline Configuration& config() { return m_config; } inline const Configuration& config() const { return m_config; } protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); private: Configuration m_config; }; class JsConfigSection : public JsObject { friend class JsConfigFile; YCLASS(JsConfigSection,JsObject) protected: inline JsConfigSection(JsConfigFile* owner, const char* name) : JsObject(owner->mutex(),name,true), m_owner(owner) { XDebug(DebugAll,"JsConfigSection::JsConfigSection(%p,'%s') [%p]",owner,name,this); params().addParam(new ExpFunction("configFile")); params().addParam(new ExpFunction("getValue")); params().addParam(new ExpFunction("getIntValue")); params().addParam(new ExpFunction("getBoolValue")); params().addParam(new ExpFunction("setValue")); params().addParam(new ExpFunction("addValue")); params().addParam(new ExpFunction("clearKey")); params().addParam(new ExpFunction("keys")); } bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); private: RefPointer m_owner; }; class JsXML : public JsObject { public: inline JsXML(Mutex* mtx) : JsObject("XML",mtx,true), m_xml(0) { XDebug(DebugAll,"JsXML::JsXML() [%p]",this); params().addParam(new ExpFunction("put")); params().addParam(new ExpFunction("getOwner")); params().addParam(new ExpFunction("getParent")); params().addParam(new ExpFunction("unprefixedTag")); params().addParam(new ExpFunction("getTag")); params().addParam(new ExpFunction("getAttribute")); params().addParam(new ExpFunction("setAttribute")); params().addParam(new ExpFunction("removeAttribute")); params().addParam(new ExpFunction("attributes")); params().addParam(new ExpFunction("addChild")); params().addParam(new ExpFunction("getChild")); params().addParam(new ExpFunction("getChildren")); params().addParam(new ExpFunction("clearChildren")); params().addParam(new ExpFunction("addText")); params().addParam(new ExpFunction("getText")); params().addParam(new ExpFunction("setText")); params().addParam(new ExpFunction("getChildText")); params().addParam(new ExpFunction("xmlText")); } inline JsXML(Mutex* mtx, XmlElement* xml, JsXML* owner = 0) : JsObject("XML",mtx,false), m_xml(xml), m_owner(owner) { XDebug(DebugAll,"JsXML::JsXML(%p,%p) [%p]",xml,owner,this); if (owner) { JsObject* proto = YOBJECT(JsObject,owner->params().getParam(protoName())); if (proto && proto->ref()) params().addParam(new ExpWrapper(proto,protoName())); } } virtual ~JsXML() { if (m_owner) { m_xml = 0; m_owner = 0; } else TelEngine::destruct(m_xml); } virtual void* getObject(const String& name) const; virtual JsObject* runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context); virtual void initConstructor(JsFunction* construct) { construct->params().addParam(new ExpOperation((int64_t)0,"PutObject")); construct->params().addParam(new ExpOperation((int64_t)1,"PutText")); construct->params().addParam(new ExpOperation((int64_t)2,"PutBoth")); } inline JsXML* owner() { return m_owner ? (JsXML*)m_owner : this; } inline const XmlElement* element() const { return m_xml; } static void initialize(ScriptContext* context); protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); private: static XmlElement* getXml(const String* obj, bool take); static XmlElement* buildXml(const String* name, const String* text = 0); XmlElement* m_xml; RefPointer m_owner; }; class JsHasher : public JsObject { YCLASS(JsHasher,JsObject) public: inline JsHasher(Mutex* mtx) : JsObject("Hasher",mtx,true), m_hasher(0) { XDebug(DebugAll,"JsHasher::JsHasher() [%p]",this); params().addParam(new ExpFunction("update")); params().addParam(new ExpFunction("hmac")); params().addParam(new ExpFunction("hexDigest")); params().addParam(new ExpFunction("clear")); params().addParam(new ExpFunction("finalize")); params().addParam(new ExpFunction("hashLength")); params().addParam(new ExpFunction("hmacBlockSize")); } inline JsHasher(GenObject* context, Mutex* mtx, Hasher* h) : JsObject(mtx,"Hasher",false), m_hasher(h) { XDebug(DebugAll,"JsHasher::JsHasher(%p) [%p]",m_hasher,this); setPrototype(context,YSTRING("Hasher")); } virtual ~JsHasher() { if (m_hasher) { delete m_hasher; m_hasher = 0; } } virtual void initConstructor(JsFunction* construct) { construct->params().addParam(new ExpFunction("fips186prf")); } virtual JsObject* runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context); static void initialize(ScriptContext* context); protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); private: Hasher* m_hasher; }; class JsJSON : public JsObject { YCLASS(JsJSON,JsObject) public: inline JsJSON(Mutex* mtx) : JsObject("JSON",mtx,true) { params().addParam(new ExpFunction("parse")); params().addParam(new ExpFunction("stringify")); params().addParam(new ExpFunction("loadFile")); params().addParam(new ExpFunction("saveFile")); } static void initialize(ScriptContext* context); protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); static ExpOperation* stringify(const ExpOperation* oper, int spaces); static void stringify(const NamedString* ns, String& buf, int spaces, int indent = 0); static String strEscape(const char* str); }; class JsDNS : public JsObject { YCLASS(JsDNS,JsObject) public: inline JsDNS(Mutex* mtx) : JsObject("DNS",mtx,true) { params().addParam(new ExpFunction("query")); params().addParam(new ExpFunction("queryA")); params().addParam(new ExpFunction("queryAaaa")); params().addParam(new ExpFunction("queryNaptr")); params().addParam(new ExpFunction("querySrv")); params().addParam(new ExpFunction("queryTxt")); params().addParam(new ExpFunction("resolve")); params().addParam(new ExpFunction("local")); params().addParam(new ExpFunction("pack")); params().addParam(new ExpFunction("unpack")); } static void initialize(ScriptContext* context); void runQuery(ObjList& stack, const String& name, Resolver::Type type, GenObject* context); protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); }; class JsChannel : public JsObject { YCLASS(JsChannel,JsObject) public: inline JsChannel(JsAssist* assist, Mutex* mtx) : JsObject("Channel",mtx,false), m_assist(assist) { params().addParam(new ExpFunction("id")); params().addParam(new ExpFunction("peerid")); params().addParam(new ExpFunction("status")); params().addParam(new ExpFunction("direction")); params().addParam(new ExpFunction("answered")); params().addParam(new ExpFunction("answer")); params().addParam(new ExpFunction("hangup")); params().addParam(new ExpFunction("callTo")); params().addParam(new ExpFunction("callJust")); params().addParam(new ExpFunction("playFile")); params().addParam(new ExpFunction("recFile")); } static void initialize(ScriptContext* context, JsAssist* assist); protected: bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); void callToRoute(ObjList& stack, const ExpOperation& oper, GenObject* context, const NamedList* params); void callToReRoute(ObjList& stack, const ExpOperation& oper, GenObject* context, const NamedList* params); JsAssist* m_assist; }; class JsEngAsync : public ScriptAsync { YCLASS(JsEngAsync,ScriptAsync) public: enum Oper { AsyncSleep, AsyncUsleep, AsyncYield, AsyncIdle }; inline JsEngAsync(ScriptRun* runner, Oper op, int64_t val = 0) : ScriptAsync(runner), m_oper(op), m_val(val) { XDebug(DebugAll,"JsEngAsync %d " FMT64,op,val); } virtual bool run(); private: Oper m_oper; int64_t m_val; }; class JsMsgAsync : public ScriptAsync { YCLASS(JsMsgAsync,ScriptAsync) public: inline JsMsgAsync(ScriptRun* runner, ObjList* stack, JsMessage* jsMsg, Message* msg) : ScriptAsync(runner), m_stack(stack), m_msg(jsMsg), m_message(msg) { XDebug(DebugAll,"JsMsgAsync"); } virtual bool run() { m_msg->runAsync(*m_stack,m_message); return true; } private: ObjList* m_stack; RefPointer m_msg; Message* m_message; }; class JsSemaphoreAsync : public ScriptAsync { YCLASS(JsSemaphoreAsync,ScriptAsync) public: inline JsSemaphoreAsync(ScriptRun* runner, ObjList* stack, JsSemaphore* se, long wait) : ScriptAsync(runner), m_stack(stack), m_semaphore(se), m_wait(wait) { XDebug(DebugAll,"JsSemaphoreAsync(%p, %ld) [%p]",se,wait,this); } virtual ~JsSemaphoreAsync() { XDebug(DebugAll,"~JsSemaphoreAsync() [%p]",this); } virtual bool run() { if (m_wait > 0) m_wait *= 1000; m_semaphore->runAsync(*m_stack, m_wait); return true; } private: ObjList* m_stack; RefPointer m_semaphore; long m_wait; }; class JsDnsAsync : public ScriptAsync { YCLASS(JsDnsAsync,ScriptAsync) public: inline JsDnsAsync(ScriptRun* runner, JsDNS* jsDns, ObjList* stack, const String& name, Resolver::Type type, GenObject* context) : ScriptAsync(runner), m_stack(stack), m_name(name), m_type(type), m_context(context), m_dns(jsDns) { XDebug(DebugAll,"JsDnsAsync"); } virtual bool run() { m_dns->runQuery(*m_stack,m_name,m_type,m_context); return true; } private: ObjList* m_stack; String m_name; Resolver::Type m_type; GenObject* m_context; RefPointer m_dns; }; class JsPostExecute : public MessagePostHook { public: virtual void dispatched(const Message& msg, bool handled) { if (msg == YSTRING("call.execute")) __plugin.msgPostExecute(msg,handled); } }; static String s_basePath; static String s_libsPath; static bool s_engineStop = false; static bool s_allowAbort = false; static bool s_allowTrace = false; static bool s_allowLink = true; static bool s_autoExt = true; static unsigned int s_maxFile = 500000; UNLOAD_PLUGIN(unloadNow) { if (unloadNow) { s_engineStop = true; JsGlobal::unloadAll(); return __plugin.unload(); } return true; } // Load extensions in a script context static bool contextLoad(ScriptContext* ctx, const char* name, const char* libs = 0, const char* objs = 0) { if (!ctx) return false; bool start = !(libs || objs); Message msg("script.init",0,start); msg.userData(ctx); msg.addParam("module",__plugin.name()); msg.addParam("language","javascript"); msg.addParam("startup",String::boolText(start)); if (name) msg.addParam("instance",name); if (libs) msg.addParam("libraries",libs); if (objs) msg.addParam("objects",objs); return Engine::dispatch(msg); } // Load extensions in a script runner context static bool contextLoad(ScriptRun* runner, const char* name, const char* libs = 0, const char* objs = 0) { return runner && contextLoad(runner->context(),name,libs,objs); } // Initialize a script context, populate global objects static void contextInit(ScriptRun* runner, const char* name = 0, JsAssist* assist = 0) { if (!runner) return; ScriptContext* ctx = runner->context(); if (!ctx) return; JsObject::initialize(ctx); JsEngine::initialize(ctx,name); if (assist) JsChannel::initialize(ctx,assist); JsMessage::initialize(ctx); JsFile::initialize(ctx); JsConfigFile::initialize(ctx); JsXML::initialize(ctx); JsHasher::initialize(ctx); JsJSON::initialize(ctx); JsDNS::initialize(ctx); if (s_autoExt) contextLoad(ctx,name); } // Build a tabular dump of an Object or Array static void dumpTable(const ExpOperation& oper, String& str, const char* eol) { class Header : public ObjList { public: Header(const char* name) : m_name(name), m_rows(0) { m_width = m_name.length(); } virtual const String& toString() const { return m_name; } inline unsigned int width() const { return m_width; } inline unsigned int rows() const { return m_rows; } inline void setWidth(unsigned int w) { if (m_width < w) m_width = w; } inline void addString(const String& val, unsigned int row) { while (++m_rows < row) append(0,false); append(new String(val),(row <= 1)); } inline const String* getString(unsigned int row) const { return (row < m_rows) ? static_cast(at(row)) : 0; } private: String m_name; unsigned int m_width; unsigned int m_rows; }; const JsObject* jso = YOBJECT(JsObject,&oper); if (!jso || JsParser::isNull(oper)) { if (JsParser::isUndefined(oper)) str = "undefined"; else str = oper; return; } ObjList header; const JsArray* jsa = YOBJECT(JsArray,jso); if (jsa) { // Array of Objects // [ { name1: "val11", name2: "val12" }, { name1: "val21", name3: "val23" } ] unsigned int row = 0; for (int i = 0; i < jsa->length(); i++) { jso = YOBJECT(JsObject,jsa->params().getParam(String(i))); if (!jso) continue; bool newRow = true; for (ObjList* l = jso->params().paramList()->skipNull(); l; l = l->skipNext()) { const NamedString* ns = static_cast(l->get()); if (ns->name() == JsObject::protoName()) continue; Header* h = static_cast(header[ns->name()]); if (!h) { h = new Header(ns->name()); header.append(h); } h->setWidth(ns->length()); if (newRow) { newRow = false; row++; } h->addString(*ns,row); } } } else { // Object containing Arrays // { name1: [ "val11", "val21" ], name2: [ "val12" ], name3: [ undefined, "val23" ] } for (ObjList* l = jso->params().paramList()->skipNull(); l; l = l->skipNext()) { const NamedString* ns = static_cast(l->get()); jsa = YOBJECT(JsArray,ns); if (!jsa) continue; Header* h = new Header(ns->name()); header.append(h); for (int r = 0; r < jsa->length(); r++) { ns = jsa->params().getParam(String(r)); if (ns) { h->setWidth(ns->length()); h->addString(*ns,r + 1); } } } } str.clear(); String tmp; unsigned int rows = 0; for (ObjList* l = header.skipNull(); l; l = l->skipNext()) { Header* h = static_cast(l->get()); if (rows < h->rows()) rows = h->rows(); str.append(h->toString()," ",true); unsigned int sp = h->width() - h->toString().length(); if (sp) str << String(' ',sp); tmp.append(String('-',h->width())," ",true); } if (!rows) return; str << eol << tmp << eol; for (unsigned int r = 0; r < rows; r++) { tmp.clear(); // add each row data for (ObjList* l = header.skipNull(); l; l = l->skipNext()) { Header* h = static_cast(l->get()); const String* s = h->getString(r); if (!s) s = &String::empty(); tmp.append(*s," ",true); unsigned int sp = h->width() - s->length(); if (sp) tmp << String(' ',sp); } str << tmp << eol; } } // Extract arguments from stack // Maximum allowed number of arguments is given by arguments to extract // Return false if the number of arguments is not the expected one static bool extractStackArgs(int minArgc, JsObject* obj, ObjList& stack, const ExpOperation& oper, GenObject* context, ObjList& args, ExpOperation** op1, ExpOperation** op2, ExpOperation** op3 = 0) { if (!obj) return false; int argc = obj->extractArgs(stack,oper,context,args); if (minArgc > argc) return false; switch (argc) { #define EXTRACT_ARG_CHECK(var,n) { \ case n: \ if (!var) \ return false; \ *var = static_cast(args[n - 1]); \ } EXTRACT_ARG_CHECK(op3,3); EXTRACT_ARG_CHECK(op2,2); EXTRACT_ARG_CHECK(op1,1); return true; #undef EXTRACT_ARG_CHECK } return false; } // Copy parameters from one list to another skipping those starting with two underlines static void copyObjParams(NamedList& dest, const NamedList* src) { if (!src) return; unsigned int n = src->length(); for (unsigned int i = 0; i < n; i++) { const NamedString* p = src->getParam(i); if (p && !p->name().startsWith("__") && !p->getObject(YATOM("ExpWrapper"))) dest.setParam(p->name(),*p); } } bool JsEngAsync::run() { switch (m_oper) { case AsyncSleep: Thread::sleep((unsigned int)m_val); break; case AsyncUsleep: Thread::usleep((unsigned long)m_val); break; case AsyncYield: Thread::yield(); break; case AsyncIdle: Thread::idle(); break; } return true; } bool JsEngine::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { if (oper.name() == YSTRING("output")) { String str; for (int i = (int)oper.number(); i; i--) { ExpOperation* op = popValue(stack,context); if (str) str = *op + " " + str; else str = *op; TelEngine::destruct(op); } if (str) Output("%s",str.c_str()); } else if (oper.name() == YSTRING("debug")) { int level = DebugNote; String str; for (int i = (int)oper.number(); i; i--) { ExpOperation* op = popValue(stack,context); if (!op) continue; if ((i == 1) && oper.number() > 1 && op->isInteger()) level = (int)op->number(); else if (*op) { if (str) str = *op + " " + str; else str = *op; } TelEngine::destruct(op); } if (str) { int limit = s_allowAbort ? DebugFail : DebugTest; if (level > DebugAll) level = DebugAll; else if (level < limit) level = limit; Debug(this,level,"%s",str.c_str()); } } else if (oper.name() == YSTRING("alarm")) { if (oper.number() < 2) return false; int level = -1; String info; String str; for (int i = (int)oper.number(); i; i--) { ExpOperation* op = popValue(stack,context); if (!op) continue; if (i == 1) { if (level < 0) { if (op->isInteger()) level = (int)op->number(); else return false; } else info = *op; } else if ((i == 2) && oper.number() > 2 && op->isInteger()) level = (int)op->number(); else if (*op) { if (str) str = *op + " " + str; else str = *op; } TelEngine::destruct(op); } if (str && level >= 0) { int limit = s_allowAbort ? DebugFail : DebugTest; if (level > DebugAll) level = DebugAll; else if (level < limit) level = limit; Alarm(this,info,level,"%s",str.c_str()); } } else if (oper.name() == YSTRING("sleep")) { if (oper.number() != 1) return false; ExpOperation* op = popValue(stack,context); if (!op) return false; int64_t val = op->valInteger(); TelEngine::destruct(op); if (val < 0) val = 0; ScriptRun* runner = YOBJECT(ScriptRun,context); if (!runner) return false; runner->insertAsync(new JsEngAsync(runner,JsEngAsync::AsyncSleep,val)); runner->pause(); } else if (oper.name() == YSTRING("usleep")) { if (oper.number() != 1) return false; ExpOperation* op = popValue(stack,context); if (!op) return false; int64_t val = op->valInteger(); TelEngine::destruct(op); if (val < 0) val = 0; ScriptRun* runner = YOBJECT(ScriptRun,context); if (!runner) return false; runner->insertAsync(new JsEngAsync(runner,JsEngAsync::AsyncUsleep,val)); runner->pause(); } else if (oper.name() == YSTRING("yield")) { if (oper.number() != 0) return false; ScriptRun* runner = YOBJECT(ScriptRun,context); if (!runner) return false; runner->insertAsync(new JsEngAsync(runner,JsEngAsync::AsyncYield)); runner->pause(); } else if (oper.name() == YSTRING("idle")) { if (oper.number() != 0) return false; ScriptRun* runner = YOBJECT(ScriptRun,context); if (!runner) return false; runner->insertAsync(new JsEngAsync(runner,JsEngAsync::AsyncIdle)); runner->pause(); } else if (oper.name() == YSTRING("dump_r")) { String buf; if (oper.number() == 0) { ScriptRun* run = YOBJECT(ScriptRun,context); if (run) dumpRecursive(run->context(),buf); else dumpRecursive(context,buf); } else if (oper.number() == 1) { ExpOperation* op = popValue(stack,context); if (!op) return false; dumpRecursive(op,buf); TelEngine::destruct(op); } else return false; ExpEvaluator::pushOne(stack,new ExpOperation(buf)); } else if (oper.name() == YSTRING("print_r")) { if (oper.number() == 0) { ScriptRun* run = YOBJECT(ScriptRun,context); if (run) printRecursive(run->context()); else printRecursive(context); } else if (oper.number() == 1) { ExpOperation* op = popValue(stack,context); if (!op) return false; printRecursive(op); TelEngine::destruct(op); } else return false; } else if (oper.name() == YSTRING("dump_t")) { if (oper.number() != 1) return false; ExpOperation* op = popValue(stack,context); if (!op) return false; String buf; dumpTable(*op,buf,"\r\n"); TelEngine::destruct(op); ExpEvaluator::pushOne(stack,new ExpOperation(buf)); } else if (oper.name() == YSTRING("print_t")) { if (oper.number() != 1) return false; ExpOperation* op = popValue(stack,context); if (!op) return false; String buf; dumpTable(*op,buf,"\r\n"); TelEngine::destruct(op); Output("%s",buf.safe()); } else if (oper.name() == YSTRING("debugName")) { if (oper.number() == 0) ExpEvaluator::pushOne(stack,new ExpOperation(m_debugName)); else if (oper.number() == 1) { ExpOperation* op = popValue(stack,context); String tmp; if (op && !JsParser::isNull(*op)) tmp = *op; TelEngine::destruct(op); tmp.trimSpaces(); if (tmp.null()) tmp = "javascript"; m_debugName = tmp; debugName(m_debugName); } else return false; } else if (oper.name() == YSTRING("debugLevel")) { if (oper.number() == 0) ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)debugLevel())); else if (oper.number() == 1) { ExpOperation* op = popValue(stack,context); if (op && op->isInteger()) debugLevel((int)op->valInteger()); TelEngine::destruct(op); } else return false; } else if (oper.name() == YSTRING("debugEnabled")) { if (oper.number() == 0) ExpEvaluator::pushOne(stack,new ExpOperation(debugEnabled())); else if (oper.number() == 1) { ExpOperation* op = popValue(stack,context); if (op) debugEnabled(op->valBoolean()); TelEngine::destruct(op); } else return false; } else if (oper.name() == YSTRING("debugAt")) { if (oper.number() == 1) { ExpOperation* op = popValue(stack,context); if (!(op && op->isInteger())) return false; ExpEvaluator::pushOne(stack,new ExpOperation(debugAt((int)op->valInteger()))); TelEngine::destruct(op); } else return false; } else if (oper.name() == YSTRING("setDebug")) { if (oper.number() == 1) { ExpOperation* op = popValue(stack,context); if (!op) return false; if (op->startSkip("level")) { int dbg = debugLevel(); *op >> dbg; if (*op == "+") { if (debugLevel() > dbg) dbg = debugLevel(); } else if (*op == "-") { if (debugLevel() < dbg) dbg = debugLevel(); } debugLevel(dbg); } else if (*op == "reset") debugChain(&__plugin); else if (*op == "engine") debugCopy(); else if (op->isBoolean()) debugEnabled(op->toBoolean(debugEnabled())); TelEngine::destruct(op); } else return false; } else if (oper.name() == YSTRING("runParams")) { if (oper.number() == 0) { JsObject* jso = new JsObject(context,mutex()); jso->params().copyParams(Engine::runParams()); ExpEvaluator::pushOne(stack,new ExpWrapper(jso,oper.name())); } else if (oper.number() == 1) { ExpOperation* op = popValue(stack,context); if (op) ExpEvaluator::pushOne(stack,new ExpOperation(Engine::runParams()[*op])); TelEngine::destruct(op); } else return false; } else if (oper.name() == YSTRING("configFile")) { bool user = false; ObjList args; switch (extractArgs(stack,oper,context,args)) { case 2: user = static_cast(args[1])->valBoolean(); // fall through case 1: ExpEvaluator::pushOne(stack,new ExpOperation( Engine::configFile(*static_cast(args[0]),user))); break; default: return false; } } else if (oper.name() == YSTRING("setInterval") || oper.name() == YSTRING("setTimeout")) { ObjList args; if (extractArgs(stack,oper,context,args) < 2) return false; const ExpFunction* callback = YOBJECT(ExpFunction,args[0]); if (!callback) { JsFunction* jsf = YOBJECT(JsFunction,args[0]); if (jsf) callback = jsf->getFunc(); } if (!callback) return false; ExpOperation* interval = static_cast(args[1]); if (!m_worker) { ScriptRun* runner = YOBJECT(ScriptRun,context); if (!runner) return false; ScriptContext* scontext = runner->context(); ScriptCode* scode = runner->code(); if (!(scontext && scode)) return false; m_worker = new JsEngineWorker(this,scontext,scode); m_worker->startup(); } unsigned int id = m_worker->addEvent(*callback,interval->toInteger(), oper.name() == YSTRING("setInterval")); ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)id)); } else if (oper.name() == YSTRING("clearInterval") || oper.name() == YSTRING("clearTimeout")) { if (!m_worker) return false; ObjList args; if (!extractArgs(stack,oper,context,args)) return false; ExpOperation* id = static_cast(args[0]); bool ret = m_worker->removeEvent((unsigned int)id->valInteger(),oper.name() == YSTRING("clearInterval")); ExpEvaluator::pushOne(stack,new ExpOperation(ret)); } else if (oper.name() == YSTRING("loadLibrary") || oper.name() == YSTRING("loadObject")) { bool obj = oper.name() == YSTRING("loadObject"); bool ok = false; ObjList args; ScriptRun* runner = YOBJECT(ScriptRun,context); int argc = extractArgs(stack,oper,context,args); if (runner && argc) { ok = true; for (int i = 0; i < argc; i++) { ExpOperation* op = static_cast(args[i]); if (!op || op->isBoolean() || op->isNumber() || YOBJECT(ExpWrapper,op)) ok = false; else if (obj) ok = contextLoad(runner,0,0,*op) && ok; else ok = contextLoad(runner,0,*op) && ok; } } ExpEvaluator::pushOne(stack,new ExpOperation(ok)); } else if (oper.name() == YSTRING("replaceParams")) { ObjList args; int argc = extractArgs(stack,oper,context,args); if (argc < 2 || argc > 4) return false; GenObject* arg0 = args[0]; ExpOperation* text = static_cast(arg0); NamedList* params = YOBJECT(NamedList,args[1]); bool sqlEsc = (argc >= 3) && static_cast(args[2])->valBoolean(); char extraEsc = 0; if (argc >= 4) extraEsc = static_cast(args[3])->at(0); if (params) { String str(*text); params->replaceParams(str,sqlEsc,extraEsc); ExpEvaluator::pushOne(stack,new ExpOperation(str,text->name())); } else { args.remove(arg0,false); ExpEvaluator::pushOne(stack,text); } } else if (oper.name() == YSTRING("restart")) { ObjList args; int argc = extractArgs(stack,oper,context,args); if (argc > 2) return false; bool ok = s_allowAbort; if (ok) { int code = 0; if (argc >= 1) { code = static_cast(args[0])->valInteger(); if (code < 0) code = 0; } bool gracefull = (argc >= 2) && static_cast(args[1])->valBoolean(); ok = Engine::restart(code,gracefull); } else Debug(&__plugin,DebugNote,"Engine restart is disabled by allow_abort configuration"); ExpEvaluator::pushOne(stack,new ExpOperation(ok)); } else if (oper.name() == YSTRING("uptime")) { SysUsage::Type typ = SysUsage::WallTime; bool msec = false; ObjList args; switch (extractArgs(stack,oper,context,args)) { case 2: msec = static_cast(args[1])->valBoolean(); // fall through case 1: typ = (SysUsage::Type)static_cast(args[0])->toInteger(typ); // fall through case 0: break; default: return false; } ExpEvaluator::pushOne(stack,new ExpOperation( msec ? (int64_t)SysUsage::msecRunTime(typ) : (int64_t)SysUsage::secRunTime(typ))); } else if (oper.name() == YSTRING("started")) { if (oper.number() != 0) return false; ExpEvaluator::pushOne(stack,new ExpOperation(Engine::started())); } else if (oper.name() == YSTRING("exiting")) { if (oper.number() != 0) return false; ExpEvaluator::pushOne(stack,new ExpOperation(Engine::exiting())); } else if (oper.name() == YSTRING("accepting")) { ObjList args; switch (extractArgs(stack,oper,context,args)) { case 0: ExpEvaluator::pushOne(stack,new ExpOperation( lookup(Engine::accept(),Engine::getCallAcceptStates()))); break; case 1: { int arg = static_cast(args[0])->toInteger( Engine::getCallAcceptStates(),-1); if ((Engine::Accept <= arg) && (Engine::Reject >= arg)) Engine::setAccept((Engine::CallAccept)arg); } break; default: return false; } } else if (oper.name() == YSTRING("atob")) { // str = Engine.atob(b64_str) ObjList args; int argc = extractArgs(stack,oper,context,args); if (argc < 1) return false; Base64 b64; b64 << *static_cast(args[0]); DataBlock buf; if (b64.decode(buf)) { String tmp((const char*)buf.data(),buf.length()); ExpEvaluator::pushOne(stack,new ExpOperation(tmp,"bin")); } else ExpEvaluator::pushOne(stack,new ExpOperation(false)); } else if (oper.name() == YSTRING("btoa")) { // b64_str = Engine.btoa(str,line_len,add_eol) ObjList args; int argc = extractArgs(stack,oper,context,args); if (argc < 1) return false; int len = 0; bool eol = false; if (argc >= 3) eol = static_cast(args[2])->valBoolean(); if (argc >= 2) { len = static_cast(args[1])->valInteger(); if (len < 0) len = 0; } Base64 b64; b64 << *static_cast(args[0]); String buf; b64.encode(buf,len,eol); ExpEvaluator::pushOne(stack,new ExpOperation(buf,"b64")); } else if (oper.name() == YSTRING("atoh")) { // hex_str = Engine.atoh(b64_str,hex_sep,hex_upcase) ObjList args; int argc = extractArgs(stack,oper,context,args); if (argc < 1) return false; Base64 b64; b64 << *static_cast(args[0]); DataBlock buf; if (b64.decode(buf)) { char sep = (argc >= 2) ? static_cast(args[1])->at(0) : '\0'; bool upCase = (argc >= 3) && static_cast(args[2])->valBoolean(); String tmp; tmp.hexify(buf.data(),buf.length(),sep,upCase); ExpEvaluator::pushOne(stack,new ExpOperation(tmp,"hex")); } else ExpEvaluator::pushOne(stack,new ExpOperation(false)); } else if (oper.name() == YSTRING("htoa")) { // b64_str = Engine.htoa(hex_str,line_len,add_eol) ObjList args; int argc = extractArgs(stack,oper,context,args); if (argc < 1) return false; Base64 b64; if (b64.unHexify(*static_cast(args[0]))) { int len = 0; bool eol = false; if (argc >= 3) eol = static_cast(args[2])->valBoolean(); if (argc >= 2) { len = static_cast(args[1])->valInteger(); if (len < 0) len = 0; } String buf; b64.encode(buf,len,eol); ExpEvaluator::pushOne(stack,new ExpOperation(buf,"b64")); } else ExpEvaluator::pushOne(stack,new ExpOperation(false)); } else if (oper.name() == YSTRING("btoh")) { // hex_str = Engine.btoh(str[,sep[,upCase]]) ObjList args; ExpOperation* data = 0; ExpOperation* sep = 0; ExpOperation* upCase = 0; if (!extractStackArgs(1,this,stack,oper,context,args,&data,&sep,&upCase)) return false; String tmp; tmp.hexify((void*)data->c_str(),data->length(),(sep ? sep->at(0) : 0), (upCase && upCase->toBoolean())); ExpEvaluator::pushOne(stack,new ExpOperation(tmp,"hex")); } else if (oper.name() == YSTRING("htob")) { // str = Engine.unHexify(hex_str[,sep]) ObjList args; ExpOperation* data = 0; ExpOperation* sep = 0; if (!extractStackArgs(1,this,stack,oper,context,args,&data,&sep)) return false; bool ok = true; DataBlock buf; if (!sep) ok = buf.unHexify(data->c_str(),data->length()); else ok = buf.unHexify(data->c_str(),data->length(),sep->at(0)); if (ok) { String tmp((const char*)buf.data(),buf.length()); ExpEvaluator::pushOne(stack,new ExpOperation(tmp,"bin")); } else ExpEvaluator::pushOne(stack,new ExpOperation(false)); } else return JsObject::runNative(stack,oper,context); return true; } void JsEngine::destroyed() { JsObject::destroyed(); if (!m_worker) return; m_worker->cancel(); while (m_worker) Thread::idle(); } void JsEngine::initialize(ScriptContext* context, const char* name) { if (!context) return; Mutex* mtx = context->mutex(); Lock mylock(mtx); NamedList& params = context->params(); if (!params.getParam(YSTRING("Engine"))) addObject(params,"Engine",new JsEngine(mtx,name)); } bool JsShared::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsShared::runNative '%s'(" FMT64 ")",oper.name().c_str(),oper.number()); if (oper.name() == YSTRING("inc")) { ObjList args; switch (extractArgs(stack,oper,context,args)) { case 1: case 2: break; default: return false; } ExpOperation* param = static_cast(args[0]); ExpOperation* modulo = static_cast(args[1]); int mod = 0; if (modulo && modulo->isInteger()) mod = (int)modulo->number(); if (mod > 1) mod--; else mod = 0; ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)Engine::sharedVars().inc(*param,mod))); } else if (oper.name() == YSTRING("dec")) { ObjList args; switch (extractArgs(stack,oper,context,args)) { case 1: case 2: break; default: return false; } ExpOperation* param = static_cast(args[0]); ExpOperation* modulo = static_cast(args[1]); int mod = 0; if (modulo && modulo->isInteger()) mod = (int)modulo->number(); if (mod > 1) mod--; else mod = 0; ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)Engine::sharedVars().dec(*param,mod))); } else if (oper.name() == YSTRING("get")) { if (oper.number() != 1) return false; ExpOperation* param = popValue(stack,context); if (!param) return false; String buf; Engine::sharedVars().get(*param,buf); TelEngine::destruct(param); ExpEvaluator::pushOne(stack,new ExpOperation(buf)); } else if (oper.name() == YSTRING("set")) { if (oper.number() != 2) return false; ExpOperation* val = popValue(stack,context); if (!val) return false; ExpOperation* param = popValue(stack,context); if (!param) { TelEngine::destruct(val); return false; } Engine::sharedVars().set(*param,*val); TelEngine::destruct(param); TelEngine::destruct(val); } else if (oper.name() == YSTRING("clear")) { if (oper.number() != 1) return false; ExpOperation* param = popValue(stack,context); if (!param) return false; Engine::sharedVars().clear(*param); TelEngine::destruct(param); } else if (oper.name() == YSTRING("exists")) { if (oper.number() != 1) return false; ExpOperation* param = popValue(stack,context); if (!param) return false; ExpEvaluator::pushOne(stack,new ExpOperation(Engine::sharedVars().exists(*param))); TelEngine::destruct(param); } else return JsObject::runNative(stack,oper,context); return true; } void* JsMessage::getObject(const String& name) const { void* obj = (name == YATOM("JsMessage")) ? const_cast(this) : JsObject::getObject(name); if (m_message && !obj) obj = m_message->getObject(name); return obj; } bool JsMessage::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsMessage::runAssign '%s'='%s'",oper.name().c_str(),oper.c_str()); if (ScriptContext::hasField(stack,oper.name(),context)) return JsObject::runAssign(stack,oper,context); if (frozen() || !m_message) { Debug(&__plugin,DebugWarn,"Message is frozen or missing"); return false; } if (JsParser::isUndefined(oper)) m_message->clearParam(oper.name()); else m_message->setParam(new NamedString(oper.name(),oper)); return true; } bool JsMessage::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsMessage::runNative '%s'(" FMT64 ")",oper.name().c_str(),oper.number()); if (oper.name() == YSTRING("broadcast")) { if (oper.number() != 0) return false; ExpEvaluator::pushOne(stack,new ExpOperation(m_message && m_message->broadcast())); } else if (oper.name() == YSTRING("name")) { if (oper.number() != 0) return false; if (m_message) ExpEvaluator::pushOne(stack,new ExpOperation(*m_message)); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("retValue")) { switch (oper.number()) { case 0: if (m_message) ExpEvaluator::pushOne(stack,new ExpOperation(m_message->retValue(),0,true)); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); break; case 1: { ExpOperation* op = popValue(stack,context); if (!op) return false; if (m_message && !frozen()) m_message->retValue() = *op; TelEngine::destruct(op); } break; default: return false; } } else if (oper.name() == YSTRING("msgTime")) { switch (oper.number()) { case 0: if (m_message) ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)m_message->msgTime().msec())); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); break; case 1: { ExpOperation* op = popValue(stack,context); if (!op) return false; uint64_t newTime = 0; if (op->isBoolean()) { if (op->valBoolean()) newTime = Time::now(); } else if (op->isInteger()) { if (op->number() > 0) newTime = 1000 * op->number(); } TelEngine::destruct(op); if (newTime && m_message && !frozen()) m_message->msgTime() = newTime; else newTime = 0; ExpEvaluator::pushOne(stack,new ExpOperation(0 != newTime)); } break; default: return false; } } else if (oper.name() == YSTRING("msgAge")) { if (oper.number()) return false; if (m_message) ExpEvaluator::pushOne(stack,new ExpOperation( (int64_t)(Time::msecNow() - m_message->msgTime().msec()))); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("getParam")) { bool autoNum = true; ObjList args; switch (extractArgs(stack,oper,context,args)) { case 3: // fall through autoNum = static_cast(args[2])->valBoolean(); case 2: case 1: break; default: return false; } const String& name = *static_cast(args[0]); const String* val = m_message ? m_message->getParam(name) : 0; if (val) ExpEvaluator::pushOne(stack,new ExpOperation(*val,name,autoNum)); else { if (args[1]) ExpEvaluator::pushOne(stack,static_cast(args[1])->clone(name)); else ExpEvaluator::pushOne(stack,new ExpWrapper(0,name)); } } else if (oper.name() == YSTRING("setParam")) { ObjList args; if (extractArgs(stack,oper,context,args) != 2) return false; const String& name = *static_cast(args[0]); const ExpOperation& val = *static_cast(args[1]); if (m_message && name) { if (JsParser::isUndefined(val)) m_message->clearParam(name); else m_message->setParam(name,val); ExpEvaluator::pushOne(stack,new ExpOperation(true)); } else ExpEvaluator::pushOne(stack,new ExpOperation(false)); } else if (oper.name() == YSTRING("getColumn")) { ObjList args; switch (extractArgs(stack,oper,context,args)) { case 0: case 1: break; default: return false; } getColumn(stack,static_cast(args[0]),context); } else if (oper.name() == YSTRING("getRow")) { ObjList args; switch (extractArgs(stack,oper,context,args)) { case 0: case 1: break; default: return false; } getRow(stack,static_cast(args[0]),context); } else if (oper.name() == YSTRING("getResult")) { ObjList args; if (extractArgs(stack,oper,context,args) != 2) return false; if (!(args[0] && args[1])) return false; getResult(stack,*static_cast(args[0]), *static_cast(args[1]),context); } else if (oper.name() == YSTRING("enqueue")) { if (oper.number() != 0) return false; bool ok = false; if (m_owned && !frozen()) { Message* m = m_message; clearMsg(); if (m) freeze(); ok = m && Engine::enqueue(m); } ExpEvaluator::pushOne(stack,new ExpOperation(ok)); } else if (oper.name() == YSTRING("dispatch")) { if (oper.number() > 1) return false; ObjList args; extractArgs(stack,oper,context,args); bool ok = false; if (m_owned && m_message && !frozen()) { Message* m = m_message; clearMsg(); ExpOperation* async = static_cast(args[0]); if (async && async->valBoolean()) { ScriptRun* runner = YOBJECT(ScriptRun,context); if (!runner) return false; runner->insertAsync(new JsMsgAsync(runner,&stack,this,m)); runner->pause(); return true; } ok = Engine::dispatch(*m); m_message = m; m_owned = true; } ExpEvaluator::pushOne(stack,new ExpOperation(ok)); } else if (oper.name() == YSTRING("install")) { ObjList args; if (extractArgs(stack,oper,context,args) < 2) return false; const ExpFunction* func = YOBJECT(ExpFunction,args[0]); if (!func) { JsFunction* jsf = YOBJECT(JsFunction,args[0]); if (jsf) func = jsf->getFunc(); } if (!func) return false; ExpOperation* name = static_cast(args[1]); ExpOperation* prio = static_cast(args[2]); if (!name) return false; unsigned int priority = 100; if (prio) { if (prio->isInteger() && (prio->number() >= 0)) priority = (unsigned int)prio->number(); else return false; } JsHandler* h = new JsHandler(*name,priority,*func,context); ExpOperation* filterName = static_cast(args[3]); ExpOperation* filterValue = static_cast(args[4]); if (filterName && filterValue && *filterName) h->setFilter(*filterName,*filterValue); if (m_trackName) { if (m_trackPrio) h->trackName(m_trackName + ":" + String(priority)); else h->trackName(m_trackName); } m_handlers.append(h); Engine::install(h); } else if (oper.name() == YSTRING("uninstall")) { ObjList args; switch (extractArgs(stack,oper,context,args)) { case 0: m_handlers.clear(); return true; case 1: break; default: return false; } ExpOperation* name = static_cast(args[0]); if (!name) return false; m_handlers.remove(*name); } else if (oper.name() == YSTRING("handlers")) { ObjList args; switch (extractArgs(stack,oper,context,args)) { case 0: case 1: break; default: return false; } ExpOperation* name = static_cast(args[0]); JsRegExp* rexp = YOBJECT(JsRegExp,name); JsArray* jsa = 0; for (ObjList* l = m_handlers.skipNull(); l; l = l->skipNext()) { const JsHandler* h = static_cast(l->get()); if (rexp) { if (!rexp->regexp().matches(*h)) continue; } else if (name && (*h != *name)) continue; if (!jsa) jsa = new JsArray(context,mutex()); JsObject* jso = new JsObject(context,mutex()); jso->params().setParam(new ExpOperation(*h,"name")); jso->params().setParam(new ExpOperation((int64_t)h->priority(),"priority")); jso->params().setParam(new ExpOperation(h->function().name(),"handler")); const NamedString* f = h->filter(); if (f) { jso->params().setParam(new ExpOperation(f->name(),"filterName")); jso->params().setParam(new ExpOperation(*f,"filterValue")); } if (h->trackName()) jso->params().setParam(new ExpOperation(h->trackName(),"trackName")); jsa->push(new ExpWrapper(jso)); } if (jsa) ExpEvaluator::pushOne(stack,new ExpWrapper(jsa,"handlers")); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("installHook")) return installHook(stack,oper,context); else if (oper.name() == YSTRING("uninstallHook")) { ObjList args; if (extractArgs(stack,oper,context,args) < 1) return false; ObjList* o = args.skipNull(); ExpOperation* name = static_cast(o->get()); NamedList hook(*name); for (;o;o = o->skipNext()) { ExpOperation* filter = static_cast(o->get()); ObjList* pair = filter->split('=',false); if (pair->count() == 2) hook.addParam(*(static_cast((*pair)[0])), *(static_cast((*pair)[1]))); TelEngine::destruct(pair); } for (o = m_hooks.skipNull();o;o = o->skipNext()) { JsMessageQueue* queue = static_cast(o->get()); if (!queue->matchesFilters(hook)) continue; Engine::uninstallHook(queue); m_hooks.remove(queue); } } else if (oper.name() == YSTRING("trackName")) { ObjList args; switch (extractArgs(stack,oper,context,args)) { case 0: ExpEvaluator::pushOne(stack,new ExpOperation(m_trackName,oper.name())); break; case 1: case 2: { ExpOperation* name = static_cast(args[0]); ExpOperation* prio = static_cast(args[1]); if (!name) return false; m_trackName = *name; m_trackName.trimSpaces(); if (prio) m_trackPrio = prio->valBoolean(); else m_trackPrio = true; } break; default: return false; } } else if (oper.name() == YSTRING("copyParams")) { if (!(m_message && m_owned)) return true; ObjList args; bool skip = true; String prefix; NamedList* from = 0; NamedList* fromNative = 0; switch (extractArgs(stack,oper,context,args)) { case 3: skip = static_cast(args[2])->valBoolean(skip); // intentional case 2: prefix = static_cast(args[1]); // intentional case 1: { ExpOperation* op = static_cast(args[0]); if (JsParser::isUndefined(*op) || JsParser::isNull(*op)) return true; JsObject* obj = YOBJECT(JsObject,op); if (obj) { if (prefix) { from = new NamedList(""); JsObject* subObj = YOBJECT(JsObject,obj->getField(stack,prefix,context)); if (subObj) { copyObjParams(*from,&subObj->params()); if (subObj->nativeParams()) copyObjParams(*from,subObj->nativeParams()); for (ObjList* o = from->paramList()->skipNull(); o; o = o->skipNext()) { NamedString* ns = static_cast(o->get()); const_cast(ns->name()) = prefix + "." + ns->name(); } prefix += "."; } else { copyObjParams(*from,&obj->params()); if (obj->nativeParams()) copyObjParams(*from,obj->nativeParams()); } } else { from = &obj->params(); fromNative = obj->nativeParams(); } } else from = YOBJECT(NamedList,op); if (!(from || fromNative)) return false; break; } default: return false; } if (prefix) { m_message->copySubParams(*from,prefix,skip,true); TelEngine::destruct(from); } else { if (from) copyObjParams(*m_message,from); if (fromNative) copyObjParams(*m_message,fromNative); } } else return JsObject::runNative(stack,oper,context); return true; } void JsMessage::runAsync(ObjList& stack, Message* msg) { bool ok = Engine::dispatch(*msg); if ((m_message || m_owned) && (msg != m_message)) Debug(&__plugin,DebugWarn,"Message replaced while async dispatching!"); else { m_message = msg; m_owned = true; } ExpEvaluator::pushOne(stack,new ExpOperation(ok)); } bool JsMessage::installHook(ObjList& stack, const ExpOperation& oper, GenObject* context) { ObjList args; unsigned int argsCount = extractArgs(stack,oper,context,args); if (argsCount < 2) return false; ObjList* o = args.skipNull(); const ExpFunction* receivedFunc = YOBJECT(ExpFunction,o->get()); if (!receivedFunc) { JsFunction* jsf = YOBJECT(JsFunction,o->get()); if (jsf) receivedFunc = jsf->getFunc(); } if (receivedFunc) { if (argsCount < 3) return false; o = o->skipNext(); } ExpOperation* name = static_cast(o->get()); if (TelEngine::null(name)) return false; o = o->skipNext(); ExpOperation* threads = static_cast(o->get()); int threadsCount = threads->toInteger(-1); if (threadsCount < 1) return false; o = o->skipNext(); const ExpFunction* trapFunction = 0; int trapLunch = 0; while (o) { trapFunction = YOBJECT(ExpFunction,o->get()); if (!trapFunction) { JsFunction* jsf = YOBJECT(JsFunction,o->get()); if (jsf) trapFunction = jsf->getFunc(); } if (!trapFunction) break; o = o->skipNext(); if (!o) return false; ExpOperation* trap = static_cast(o->get()); trapLunch = trap->toInteger(-1); if (trapLunch < 0) return false; o = o->skipNext(); } JsMessageQueue* msgQueue = new JsMessageQueue(receivedFunc,*name,threadsCount,trapFunction,trapLunch,context); for (;o;o = o->skipNext()) { ExpOperation* filter = static_cast(o->get()); ObjList* pair = filter->split('=',false); if (pair->count() == 2) msgQueue->addFilter(*(static_cast((*pair)[0])), *(static_cast((*pair)[1]))); TelEngine::destruct(pair); } msgQueue->ref(); m_hooks.append(msgQueue); return Engine::installHook(msgQueue); } void JsMessage::getColumn(ObjList& stack, const ExpOperation* col, GenObject* context) { Array* arr = m_message ? YOBJECT(Array,m_message->userData()) : 0; if (arr && arr->getRows()) { int rows = arr->getRows() - 1; int cols = arr->getColumns(); if (col) { // [ val1, val2, val3 ] int idx = -1; if (col->isInteger()) idx = (int)col->number(); else { for (int i = 0; i < cols; i++) { GenObject* o = arr->get(i,0); if (o && (o->toString() == *col)) { idx = i; break; } } } if (idx >= 0 && idx < cols) { JsArray* jsa = new JsArray(context,mutex()); for (int r = 1; r <= rows; r++) { GenObject* o = arr->get(idx,r); if (o) { const DataBlock* d = YOBJECT(DataBlock,o); if (d) { String x; jsa->push(new ExpOperation(x.hexify(d->data(),d->length()),0,false)); } else jsa->push(new ExpOperation(o->toString(),0,true)); } else jsa->push(JsParser::nullClone()); } ExpEvaluator::pushOne(stack,new ExpWrapper(jsa,"column")); return; } } else { // { col1: [ val11, val12, val13], col2: [ val21, val22, val23 ] } JsObject* jso = new JsObject(context,mutex()); for (int c = 0; c < cols; c++) { const String* name = YOBJECT(String,arr->get(c,0)); if (TelEngine::null(name)) continue; JsArray* jsa = new JsArray(context,mutex()); for (int r = 1; r <= rows; r++) { GenObject* o = arr->get(c,r); if (o) { const DataBlock* d = YOBJECT(DataBlock,o); if (d) { String x; jsa->push(new ExpOperation(x.hexify(d->data(),d->length()),*name,false)); } else jsa->push(new ExpOperation(o->toString(),*name,true)); } else jsa->push(JsParser::nullClone()); } jso->params().setParam(new ExpWrapper(jsa,*name)); } ExpEvaluator::pushOne(stack,new ExpWrapper(jso,"columns")); return; } } ExpEvaluator::pushOne(stack,JsParser::nullClone()); } void JsMessage::getRow(ObjList& stack, const ExpOperation* row, GenObject* context) { Array* arr = m_message ? YOBJECT(Array,m_message->userData()) : 0; if (arr && arr->getRows()) { int rows = arr->getRows() - 1; int cols = arr->getColumns(); if (row) { // { col1: val1, col2: val2 } if (row->isInteger()) { int idx = (int)row->number() + 1; if (idx > 0 && idx <= rows) { JsObject* jso = new JsObject(context,mutex()); for (int c = 0; c < cols; c++) { const String* name = YOBJECT(String,arr->get(c,0)); if (TelEngine::null(name)) continue; GenObject* o = arr->get(c,idx); if (o) { const DataBlock* d = YOBJECT(DataBlock,o); if (d) { String x; jso->params().setParam(new ExpOperation(x.hexify(d->data(),d->length()),*name,false)); } else jso->params().setParam(new ExpOperation(o->toString(),*name,true)); } else jso->params().setParam((JsParser::nullClone(*name))); } ExpEvaluator::pushOne(stack,new ExpWrapper(jso,"row")); return; } } } else { // [ { col1: val11, col2: val12 }, { col1: val21, col2: val22 } ] JsArray* jsa = new JsArray(context,mutex()); for (int r = 1; r <= rows; r++) { JsObject* jso = new JsObject(context,mutex()); for (int c = 0; c < cols; c++) { const String* name = YOBJECT(String,arr->get(c,0)); if (TelEngine::null(name)) continue; GenObject* o = arr->get(c,r); if (o) { const DataBlock* d = YOBJECT(DataBlock,o); if (d) { String x; jso->params().setParam(new ExpOperation(x.hexify(d->data(),d->length()),*name,false)); } else jso->params().setParam(new ExpOperation(o->toString(),*name,true)); } else jso->params().setParam((JsParser::nullClone(*name))); } jsa->push(new ExpWrapper(jso)); } ExpEvaluator::pushOne(stack,new ExpWrapper(jsa,"rows")); return; } } ExpEvaluator::pushOne(stack,JsParser::nullClone()); } void JsMessage::getResult(ObjList& stack, const ExpOperation& row, const ExpOperation& col, GenObject* context) { Array* arr = m_message ? YOBJECT(Array,m_message->userData()) : 0; if (arr && arr->getRows() && row.isInteger()) { int rows = arr->getRows() - 1; int cols = arr->getColumns(); int r = (int)row.number(); if (r >= 0 && r < rows) { int c = -1; if (col.isInteger()) c = (int)col.number(); else { for (int i = 0; i < cols; i++) { GenObject* o = arr->get(i,0); if (o && (o->toString() == col)) { c = i; break; } } } if (c >= 0 && c < cols) { GenObject* o = arr->get(c,r + 1); if (o) { ExpEvaluator::pushOne(stack,new ExpOperation(o->toString(),0,true)); return; } } } } ExpEvaluator::pushOne(stack,JsParser::nullClone()); } JsObject* JsMessage::runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsMessage::runConstructor '%s'(" FMT64 ")",oper.name().c_str(),oper.number()); ObjList args; switch (extractArgs(stack,oper,context,args)) { case 1: case 2: case 3: break; default: return 0; } ExpOperation* name = static_cast(args[0]); ExpOperation* broad = static_cast(args[1]); JsObject* objParams = YOBJECT(JsObject,args[2]); if (!name) return 0; if (!ref()) return 0; Message* m = new Message(*name,0,broad && broad->valBoolean()); if (objParams) { copyObjParams(*m,&objParams->params()); if (objParams->nativeParams()) copyObjParams(*m,objParams->nativeParams()); } JsMessage* obj = new JsMessage(m,mutex(),true); obj->params().addParam(new ExpWrapper(this,protoName())); return obj; } void JsMessage::initialize(ScriptContext* context) { if (!context) return; Mutex* mtx = context->mutex(); Lock mylock(mtx); NamedList& params = context->params(); if (!params.getParam(YSTRING("Message"))) addConstructor(params,"Message",new JsMessage(mtx)); } bool JsHandler::receivedInternal(Message& msg) { if (s_engineStop || !m_code) { safeNowInternal(); return false; } DDebug(&__plugin,DebugInfo,"Running %s(message) handler for '%s'", m_function.name().c_str(),c_str()); #ifdef DEBUG u_int64_t tm = Time::now(); #endif ScriptRun* runner = m_code->createRunner(m_context,NATIVE_TITLE); if (!runner) { safeNowInternal(); return false; } JsMessage* jm = new JsMessage(&msg,runner->context()->mutex(),false); jm->setPrototype(runner->context(),YSTRING("Message")); jm->ref(); String name = m_function.name(); safeNowInternal(); // Staring from here the handler may be safely uninstalled ObjList args; args.append(new ExpWrapper(jm,"message")); ScriptRun::Status rval = runner->call(name,args); jm->clearMsg(); bool ok = false; if (ScriptRun::Succeeded == rval) { ExpOperation* op = ExpEvaluator::popOne(runner->stack()); if (op) { ok = op->valBoolean(); TelEngine::destruct(op); } } TelEngine::destruct(jm); TelEngine::destruct(runner); #ifdef DEBUG tm = Time::now() - tm; Debug(&__plugin,DebugInfo,"Handler for '%s' ran for " FMT64U " usec",c_str(),tm); #endif return ok; } void JsMessageQueue::received(Message& msg) { if (s_engineStop || !m_code) return; if (!m_receivedFunction) { MessageQueue::received(msg); return; } ScriptRun* runner = m_code->createRunner(m_context,NATIVE_TITLE); if (!runner) return; JsMessage* jm = new JsMessage(&msg,runner->context()->mutex(),false); jm->setPrototype(runner->context(),YSTRING("Message")); jm->ref(); ObjList args; args.append(new ExpWrapper(jm,"message")); runner->call(m_receivedFunction->name(),args); jm->clearMsg(); TelEngine::destruct(jm); TelEngine::destruct(runner); } bool JsMessageQueue::enqueue(Message* msg) { if (!count()) m_trapCalled = false; bool ret = MessageQueue::enqueue(msg); if (!ret || !m_trapLunch || !m_trapFunction || m_trapCalled || count() < m_trapLunch) return ret; if (s_engineStop || !m_code) return ret; ScriptRun* runner = m_code->createRunner(m_context,NATIVE_TITLE); if (!runner) return ret; ObjList args; runner->call(m_trapFunction->name(),args); TelEngine::destruct(runner); m_trapCalled = true; return ret; } bool JsMessageQueue::matchesFilters(const NamedList& filters) { const NamedList origFilters = getFilters(); if (origFilters != filters) return false; unsigned int ofCount = origFilters.count(), fcount = filters.count(); if (ofCount != fcount) return false; if (!ofCount) return true; for (unsigned int i = 0;i < origFilters.length();i++) { NamedString* param = origFilters.getParam(i); NamedString* secParam = filters.getParam(*param); if (!secParam || *secParam != *param) return false; } return true; } bool JsFile::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsFile::runNative '%s'(" FMT64 ")",oper.name().c_str(),oper.number()); if (oper.name() == YSTRING("exists")) { if (oper.number() != 1) return false; ExpOperation* op = popValue(stack,context); if (!op) return false; ExpEvaluator::pushOne(stack,new ExpOperation(File::exists(*op))); TelEngine::destruct(op); } else if (oper.name() == YSTRING("remove")) { if (oper.number() != 1) return false; ExpOperation* op = popValue(stack,context); if (!op) return false; ExpEvaluator::pushOne(stack,new ExpOperation(File::remove(*op))); TelEngine::destruct(op); } else if (oper.name() == YSTRING("rename")) { if (oper.number() != 2) return false; ExpOperation* newName = popValue(stack,context); if (!newName) return false; ExpOperation* oldName = popValue(stack,context); if (!oldName) { TelEngine::destruct(newName); return false; } ExpEvaluator::pushOne(stack,new ExpOperation(File::rename(*oldName,*newName))); TelEngine::destruct(oldName); TelEngine::destruct(newName); } else if (oper.name() == YSTRING("mkdir")) { int mode = -1; ExpOperation* op = 0; switch (oper.number()) { case 2: op = popValue(stack,context); if (op && op->isInteger()) mode = op->number(); // fall through case 1: op = popValue(stack,context); break; default: return false; } if (!op) return false; ExpEvaluator::pushOne(stack,new ExpOperation(File::mkDir(*op,0,mode))); TelEngine::destruct(op); } else if (oper.name() == YSTRING("rmdir")) { if (oper.number() != 1) return false; ExpOperation* op = popValue(stack,context); if (!op) return false; ExpEvaluator::pushOne(stack,new ExpOperation(File::rmDir(*op))); TelEngine::destruct(op); } else if (oper.name() == YSTRING("getFileTime")) { if (oper.number() != 1) return false; ExpOperation* op = popValue(stack,context); if (!op) return false; unsigned int epoch = 0; int64_t fTime = File::getFileTime(*op,epoch) ? (int64_t)epoch : -1; ExpEvaluator::pushOne(stack,new ExpOperation(fTime)); TelEngine::destruct(op); } else if (oper.name() == YSTRING("setFileTime")) { if (oper.number() != 2) return false; ExpOperation* fTime = popValue(stack,context); if (!fTime) return false; ExpOperation* fName = popValue(stack,context); if (!fName) { TelEngine::destruct(fTime); return false; } bool ok = fTime->isInteger() && (fTime->number() >= 0) && File::setFileTime(*fName,(unsigned int)fTime->number()); TelEngine::destruct(fTime); TelEngine::destruct(fName); ExpEvaluator::pushOne(stack,new ExpOperation(ok)); } else return JsObject::runNative(stack,oper,context); return true; } void JsFile::initialize(ScriptContext* context) { if (!context) return; Mutex* mtx = context->mutex(); Lock mylock(mtx); NamedList& params = context->params(); if (!params.getParam(YSTRING("File"))) addObject(params,"File",new JsFile(mtx)); } void JsSemaphore::runAsync(ObjList& stack, long maxWait) { if (m_exit) { ExpEvaluator::pushOne(stack,new ExpOperation(false)); return; } bool ret = m_semaphore.lock(maxWait); if (m_exit) ret = false; ExpEvaluator::pushOne(stack,new ExpOperation(ret)); } bool JsSemaphore::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsSemaphore::runNative '%s'(" FMT64 ")",oper.name().c_str(),oper.number()); if (oper.name() == YSTRING("wait")) { if (oper.number() != 1) return false; ExpOperation* op = popValue(stack,context); long wait = 0; if (JsParser::isNull(*op)) wait = -1; else if ((wait = op->toInteger()) < 0) wait = 0; TelEngine::destruct(op); ScriptRun* runner = YOBJECT(ScriptRun,context); if (!runner) return false; runner->insertAsync(new JsSemaphoreAsync(runner,&stack,this,wait)); runner->pause(); } else if (oper.name() == YSTRING("signal")) { if (oper.number()) return false; ExpEvaluator::pushOne(stack,new ExpOperation(m_semaphore.unlock())); } else return false; return true; } void JsSemaphore::removeSemaphore(JsSemaphore* sem) { Lock myLock(mutex()); m_semaphores.remove(sem,false); } void JsSemaphore::forceExit() { m_exit = true; m_constructor = 0; m_semaphore.unlock(); } JsObject* JsSemaphore::runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context) { ObjList args; int maxcount = 1; int initialCount = 0; const char* name = "JsSemaphore"; switch (extractArgs(stack,oper,context,args)) { case 3: name = static_cast(args[2])->c_str(); // Intentional case 2: initialCount = static_cast(args[1])->toInteger(-1); if (initialCount < 0) initialCount = 0; // Intentional case 1: maxcount = static_cast(args[0])->toInteger(); if (maxcount < 1) maxcount = 1; // Intentional case 0: break; default: return 0; } JsSemaphore* sem = new JsSemaphore(this,mutex(),maxcount,initialCount,name); mutex()->lock(); m_semaphores.append(sem); mutex()->unlock(); return sem; } void* JsConfigFile::getObject(const String& name) const { void* obj = (name == YATOM("JsConfigFile")) ? const_cast(this) : JsObject::getObject(name); if (!obj) obj = m_config.getObject(name); return obj; } bool JsConfigFile::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsConfigFile::runNative '%s'(" FMT64 ")",oper.name().c_str(),oper.number()); ObjList args; if (oper.name() == YSTRING("name")) { switch (extractArgs(stack,oper,context,args)) { case 0: ExpEvaluator::pushOne(stack,new ExpOperation(m_config)); break; case 1: m_config = *static_cast(args[0]); break; default: return false; } } else if (oper.name() == YSTRING("load")) { switch (extractArgs(stack,oper,context,args)) { case 0: case 1: break; default: return false; } ExpEvaluator::pushOne(stack,new ExpOperation(m_config.load(args[0] && static_cast(args[0])->valBoolean()))); } else if (oper.name() == YSTRING("save")) { if (extractArgs(stack,oper,context,args) != 0) return false; ExpEvaluator::pushOne(stack,new ExpOperation(m_config.save())); } else if (oper.name() == YSTRING("count")) { if (extractArgs(stack,oper,context,args) != 0) return false; ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)m_config.sections())); } else if (oper.name() == YSTRING("sections")) { if (extractArgs(stack,oper,context,args) != 0) return false; JsArray* jsa = new JsArray(context,mutex()); unsigned int n = m_config.sections(); for (unsigned int i = 0; i < n; i++) { NamedList* nl = m_config.getSection(i); if (nl) jsa->params().addParam(new ExpWrapper(new JsConfigSection(this,*nl),*nl)); } ExpEvaluator::pushOne(stack,new ExpWrapper(jsa,"sections")); } else if (oper.name() == YSTRING("getSection")) { bool create = false; switch (extractArgs(stack,oper,context,args)) { case 2: create = static_cast(args[1])->valBoolean(); break; case 1: break; default: return false; } const String& name = *static_cast(args[0]); if (create ? m_config.createSection(name) : m_config.getSection(name)) ExpEvaluator::pushOne(stack,new ExpWrapper(new JsConfigSection(this,name),name)); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("getValue")) { switch (extractArgs(stack,oper,context,args)) { case 2: case 3: break; default: return false; } const String& name = *static_cast(args[1]); static const char defVal[] = "default"; const char* val = m_config.getValue(*static_cast(args[0]),name,defVal); if (val == defVal) { if (args[2]) ExpEvaluator::pushOne(stack,static_cast(args[2])->clone(name)); else ExpEvaluator::pushOne(stack,new ExpWrapper(0,name)); } else ExpEvaluator::pushOne(stack,new ExpOperation(val,name)); } else if (oper.name() == YSTRING("getIntValue")) { int64_t defVal = 0; int64_t minVal = LLONG_MIN; int64_t maxVal = LLONG_MAX; bool clamp = true; switch (extractArgs(stack,oper,context,args)) { case 6: clamp = static_cast(args[5])->valBoolean(clamp); // fall through case 5: maxVal = static_cast(args[4])->valInteger(maxVal); // fall through case 4: minVal = static_cast(args[3])->valInteger(minVal); // fall through case 3: defVal = static_cast(args[2])->valInteger(); // fall through case 2: break; default: return false; } const String& sect = *static_cast(args[0]); const String& name = *static_cast(args[1]); ExpEvaluator::pushOne(stack,new ExpOperation(m_config.getInt64Value(sect,name,defVal,minVal,maxVal,clamp),name)); } else if (oper.name() == YSTRING("getBoolValue")) { bool defVal = false; switch (extractArgs(stack,oper,context,args)) { case 3: defVal = static_cast(args[2])->valBoolean(); // fall through case 2: break; default: return false; } const String& sect = *static_cast(args[0]); const String& name = *static_cast(args[1]); ExpEvaluator::pushOne(stack,new ExpOperation(m_config.getBoolValue(sect,name,defVal),name)); } else if (oper.name() == YSTRING("setValue")) { if (extractArgs(stack,oper,context,args) != 3) return false; m_config.setValue(*static_cast(args[0]),*static_cast(args[1]), *static_cast(args[2])); } else if (oper.name() == YSTRING("addValue")) { if (extractArgs(stack,oper,context,args) != 3) return false; m_config.addValue(*static_cast(args[0]),*static_cast(args[1]), *static_cast(args[2])); } else if (oper.name() == YSTRING("clearSection")) { ExpOperation* op = 0; switch (extractArgs(stack,oper,context,args)) { case 0: break; case 1: op = static_cast(args[0]); if (JsParser::isUndefined(*op) || JsParser::isNull(*op)) op = 0; break; default: return false; } m_config.clearSection(op ? (const char*)*op : 0); } else if (oper.name() == YSTRING("clearKey")) { if (extractArgs(stack,oper,context,args) != 2) return false; m_config.clearKey(*static_cast(args[0]),*static_cast(args[1])); } else if (oper.name() == YSTRING("keys")) { if (extractArgs(stack,oper,context,args) != 1) return false; NamedList* sect = m_config.getSection(*static_cast(args[0])); if (sect) { JsArray* jsa = new JsArray(context,mutex()); int32_t len = 0; for (const ObjList* l = sect->paramList()->skipNull(); l; l = l->skipNext()) { jsa->push(new ExpOperation(static_cast(l->get())->name())); len++; } jsa->setLength(len); ExpEvaluator::pushOne(stack,new ExpWrapper(jsa,oper.name())); } else ExpEvaluator::pushOne(stack,new ExpWrapper(0,oper.name())); } else return JsObject::runNative(stack,oper,context); return true; } JsObject* JsConfigFile::runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsConfigFile::runConstructor '%s'(" FMT64 ") [%p]",oper.name().c_str(),oper.number(),this); bool warn = false; const char* name = 0; ObjList args; switch (extractArgs(stack,oper,context,args)) { case 2: warn = static_cast(args[1])->valBoolean(); // fall through case 1: name = static_cast(args[0])->c_str(); // fall through case 0: return new JsConfigFile(mutex(),name,warn); default: return 0; } } void JsConfigFile::initialize(ScriptContext* context) { if (!context) return; Mutex* mtx = context->mutex(); Lock mylock(mtx); NamedList& params = context->params(); if (!params.getParam(YSTRING("ConfigFile"))) addConstructor(params,"ConfigFile",new JsConfigFile(mtx)); } bool JsConfigSection::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsConfigSection::runNative '%s'(" FMT64 ")",oper.name().c_str(),oper.number()); ObjList args; if (oper.name() == YSTRING("configFile")) { if (extractArgs(stack,oper,context,args) != 0) return false; ExpEvaluator::pushOne(stack,new ExpWrapper(m_owner->ref() ? m_owner : 0)); } else if (oper.name() == YSTRING("getValue")) { switch (extractArgs(stack,oper,context,args)) { case 2: case 1: break; default: return false; } NamedList* sect = m_owner->config().getSection(toString()); const String& name = *static_cast(args[0]); static const char defVal[] = "default"; const char* val = sect ? sect->getValue(name,defVal) : defVal; if (val == defVal) { if (args[1]) ExpEvaluator::pushOne(stack,static_cast(args[1])->clone(name)); else ExpEvaluator::pushOne(stack,new ExpWrapper(0,name)); } else ExpEvaluator::pushOne(stack,new ExpOperation(val,name)); } else if (oper.name() == YSTRING("getIntValue")) { int64_t val = 0; int64_t minVal = LLONG_MIN; int64_t maxVal = LLONG_MAX; bool clamp = true; switch (extractArgs(stack,oper,context,args)) { case 5: clamp = static_cast(args[4])->valBoolean(clamp); // fall through case 4: maxVal = static_cast(args[3])->valInteger(maxVal); // fall through case 3: minVal = static_cast(args[2])->valInteger(minVal); // fall through case 2: val = static_cast(args[1])->valInteger(); // fall through case 1: break; default: return false; } const String& name = *static_cast(args[0]); NamedList* sect = m_owner->config().getSection(toString()); if (sect) val = sect->getInt64Value(name,val,minVal,maxVal,clamp); else if (val < minVal) val = minVal; else if (val > maxVal) val = maxVal; ExpEvaluator::pushOne(stack,new ExpOperation(val,name)); } else if (oper.name() == YSTRING("getBoolValue")) { bool val = false; switch (extractArgs(stack,oper,context,args)) { case 2: val = static_cast(args[1])->valBoolean(); // fall through case 1: break; default: return false; } const String& name = *static_cast(args[0]); NamedList* sect = m_owner->config().getSection(toString()); if (sect) val = sect->getBoolValue(name,val); ExpEvaluator::pushOne(stack,new ExpOperation(val,name)); } else if (oper.name() == YSTRING("setValue")) { if (extractArgs(stack,oper,context,args) != 2) return false; NamedList* sect = m_owner->config().getSection(toString()); if (sect) sect->setParam(*static_cast(args[0]),*static_cast(args[1])); } else if (oper.name() == YSTRING("addValue")) { if (extractArgs(stack,oper,context,args) != 2) return false; NamedList* sect = m_owner->config().getSection(toString()); if (sect) sect->addParam(*static_cast(args[0]),*static_cast(args[1])); } else if (oper.name() == YSTRING("clearKey")) { if (extractArgs(stack,oper,context,args) != 1) return false; NamedList* sect = m_owner->config().getSection(toString()); if (sect) sect->clearParam(*static_cast(args[0])); } else if (oper.name() == YSTRING("keys")) { if (extractArgs(stack,oper,context,args) != 0) return false; NamedList* sect = m_owner->config().getSection(toString()); if (sect) { JsArray* jsa = new JsArray(context,mutex()); int32_t len = 0; for (const ObjList* l = sect->paramList()->skipNull(); l; l = l->skipNext()) { jsa->push(new ExpOperation(static_cast(l->get())->name())); len++; } jsa->setLength(len); ExpEvaluator::pushOne(stack,new ExpWrapper(jsa,oper.name())); } else ExpEvaluator::pushOne(stack,new ExpWrapper(0,oper.name())); } else return JsObject::runNative(stack,oper,context); return true; } JsObject* JsHasher::runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsHasher::runConstructor '%s'(" FMT64 ") [%p]", oper.name().c_str(),oper.number(),this); ObjList args; if (extractArgs(stack,oper,context,args) != 1) return 0; ExpOperation* name = static_cast(args[0]); Hasher* h = 0; if (*name == "md5") h = new MD5; else if (*name == "sha1") h = new SHA1; else if (*name == "sha256") h = new SHA256; else return 0; return new JsHasher(context,mutex(),h); } void JsHasher::initialize(ScriptContext* context) { if (!context) return; Mutex* mtx = context->mutex(); Lock mylock(mtx); NamedList& params = context->params(); if (!params.getParam(YSTRING("Hasher"))) addConstructor(params,"Hasher",new JsHasher(mtx)); } bool JsHasher::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsHasher::runNative '%s'(" FMT64 ") [%p]", oper.name().c_str(),oper.number(),this); if (oper.name() == YSTRING("update")) { if (!m_hasher) return false; ObjList args; ExpOperation* data = 0; ExpOperation* isHex = 0; if (!extractStackArgs(1,this,stack,oper,context,args,&data,&isHex)) return false; bool ok = false; if (!(isHex && isHex->valBoolean())) ok = m_hasher->update(*data); else { DataBlock tmp; ok = tmp.unHexify(*data) && m_hasher->update(tmp); } ExpEvaluator::pushOne(stack,new ExpOperation(ok)); } else if (oper.name() == YSTRING("hmac")) { if (!m_hasher) return false; ObjList args; ExpOperation* key = 0; ExpOperation* msg = 0; ExpOperation* isHex = 0; if (!extractStackArgs(2,this,stack,oper,context,args,&key,&msg,&isHex)) return false; bool ok = false; if (!(isHex && isHex->valBoolean())) ok = m_hasher->hmac(*key,*msg); else { DataBlock k, m; ok = k.unHexify(*key) && m.unHexify(*msg) && m_hasher->hmac(k,m); } ExpEvaluator::pushOne(stack,new ExpOperation(ok)); } else if (oper.name() == YSTRING("hexDigest")) { if (!m_hasher || oper.number()) return false; ExpEvaluator::pushOne(stack,new ExpOperation(m_hasher->hexDigest())); } else if (oper.name() == YSTRING("clear")) { if (!m_hasher || oper.number()) return false; m_hasher->clear(); } else if (oper.name() == YSTRING("finalize")) { if (!m_hasher || oper.number()) return false; m_hasher->finalize(); } else if (oper.name() == YSTRING("hashLength")) { if (!m_hasher || oper.number()) return false; ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)m_hasher->hashLength())); } else if (oper.name() == YSTRING("hmacBlockSize")) { if (!m_hasher || oper.number()) return false; ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)m_hasher->hmacBlockSize())); } else if (oper.name() == YSTRING("fips186prf")) { ObjList args; ExpOperation* opSeed = 0; ExpOperation* opLen = 0; ExpOperation* opSep = 0; if (!extractStackArgs(2,this,stack,oper,context,args,&opSeed,&opLen,&opSep)) return false; DataBlock seed, out; seed.unHexify(*opSeed); SHA1::fips186prf(out,seed,opLen->valInteger()); if (out.data()) { String tmp; char sep = '\0'; if (opSep && !(JsParser::isNull(*opSep) || opSep->isBoolean() || opSep->isNumber())) sep = opSep->at(0); tmp.hexify(out.data(),out.length(),sep); ExpEvaluator::pushOne(stack,new ExpOperation(tmp,"hex")); } else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else return JsObject::runNative(stack,oper,context); return true; } void* JsXML::getObject(const String& name) const { void* obj = (name == YATOM("JsXML")) ? const_cast(this) : JsObject::getObject(name); if (m_xml && !obj) obj = m_xml->getObject(name); return obj; } bool JsXML::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsXML::runNative '%s'(" FMT64 ")",oper.name().c_str(),oper.number()); ObjList args; if (oper.name() == YSTRING("put")) { int argc = extractArgs(stack,oper,context,args); if (argc < 2 || argc > 3) return false; ScriptContext* list = YOBJECT(ScriptContext,static_cast(args[0])); ExpOperation* name = static_cast(args[1]); ExpOperation* text = static_cast(args[2]); if (!name || !list || !m_xml) return false; NamedList* params = list->nativeParams(); if (!params) params = &list->params(); params->clearParam(*name); String txt; if (text && text->valBoolean()) m_xml->toString(txt); if (!text || (text->valInteger() != 1)) params->addParam(new NamedPointer(*name,new XmlElement(*m_xml),txt)); else params->addParam(*name,txt); } else if (oper.name() == YSTRING("getOwner")) { if (extractArgs(stack,oper,context,args) != 0) return false; if (m_owner && m_owner->ref()) ExpEvaluator::pushOne(stack,new ExpWrapper(m_owner)); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("getParent")) { if (extractArgs(stack,oper,context,args) != 0) return false; XmlElement* xml = m_xml ? m_xml->parent() : 0; if (xml) ExpEvaluator::pushOne(stack,new ExpWrapper(new JsXML(mutex(),xml,owner()))); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("unprefixedTag")) { if (extractArgs(stack,oper,context,args) != 0) return false; if (m_xml) ExpEvaluator::pushOne(stack,new ExpOperation(m_xml->unprefixedTag(),m_xml->unprefixedTag())); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("getTag")) { if (extractArgs(stack,oper,context,args) != 0) return false; if (m_xml) ExpEvaluator::pushOne(stack,new ExpOperation(m_xml->getTag(),m_xml->getTag())); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("getAttribute")) { if (extractArgs(stack,oper,context,args) != 1) return false; ExpOperation* name = static_cast(args[0]); if (!name) return false; const String* attr = 0; if (m_xml) attr = m_xml->getAttribute(*name); if (attr) ExpEvaluator::pushOne(stack,new ExpOperation(*attr,name->name())); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("setAttribute")) { if (!m_xml) return false; ExpOperation* name = 0; ExpOperation* val = 0; if (!extractStackArgs(1,this,stack,oper,context,args,&name,&val)) return false; if (JsParser::isUndefined(*name) || JsParser::isNull(*name)) return (val == 0); if (val) { if (JsParser::isUndefined(*val) || JsParser::isNull(*val)) m_xml->removeAttribute(*name); else if (*name) m_xml->setAttribute(*name,*val); } else { JsObject* jso = YOBJECT(JsObject,name); if (!jso) return false; const ObjList* o = jso->params().paramList()->skipNull(); for (; o; o = o->skipNext()) { const NamedString* ns = static_cast(o->get()); if (ns->name() != JsObject::protoName()) m_xml->setAttribute(ns->name(),*ns); } } } else if (oper.name() == YSTRING("removeAttribute")) { if (extractArgs(stack,oper,context,args) != 1) return false; ExpOperation* name = static_cast(args[0]); if (!name) return false; if (m_xml) m_xml->removeAttribute(*name); } else if (oper.name() == YSTRING("attributes")) { if (extractArgs(stack,oper,context,args)) return false; const ObjList* o = m_xml ? m_xml->attributes().paramList()->skipNull() : 0; JsObject* jso = 0; if (o) { jso = new JsObject(context,mutex()); for (; o; o = o->skipNext()) { const NamedString* ns = static_cast(o->get()); if (ns->name() != JsObject::protoName()) jso->params().addParam(ns->name(),*ns); } } if (jso) ExpEvaluator::pushOne(stack,new ExpWrapper(jso,"attributes")); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("addChild")) { int argc = extractArgs(stack,oper,context,args); if (argc < 1 || argc > 2) return false; ExpOperation* name = static_cast(args[0]); ExpOperation* val = static_cast(args[1]); if (!name) return false; if (!m_xml) return false; JsArray* jsa = YOBJECT(JsArray,name); if (jsa) { for (long i = 0; i < jsa->length(); i++) { String n((unsigned int)i); JsXML* x = YOBJECT(JsXML,jsa->getField(stack,n,context)); if (x && x->element()) { XmlElement* xml = new XmlElement(*x->element()); if (XmlSaxParser::NoError != m_xml->addChild(xml)) { TelEngine::destruct(xml); return false; } } } return true; } XmlElement* xml = 0; JsXML* x = YOBJECT(JsXML,name); if (x && x->element()) xml = new XmlElement(*x->element()); else if (!(TelEngine::null(name) || JsParser::isNull(*name))) xml = new XmlElement(name->c_str()); if (xml && val && !JsParser::isNull(*val)) xml->addText(*val); if (xml && (XmlSaxParser::NoError == m_xml->addChild(xml))) ExpEvaluator::pushOne(stack,new ExpWrapper(new JsXML(mutex(),xml,owner()))); else { TelEngine::destruct(xml); ExpEvaluator::pushOne(stack,JsParser::nullClone()); } } else if (oper.name() == YSTRING("getChild")) { if (extractArgs(stack,oper,context,args) > 2) return false; XmlElement* xml = 0; if (m_xml) { ExpOperation* name = static_cast(args[0]); ExpOperation* ns = static_cast(args[1]); if (name && (JsParser::isUndefined(*name) || JsParser::isNull(*name))) name = 0; if (ns && (JsParser::isUndefined(*ns) || JsParser::isNull(*ns))) ns = 0; xml = m_xml->findFirstChild(name,ns); } if (xml) ExpEvaluator::pushOne(stack,new ExpWrapper(new JsXML(mutex(),xml,owner()))); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("getChildren")) { if (extractArgs(stack,oper,context,args) > 2) return false; ExpOperation* name = static_cast(args[0]); ExpOperation* ns = static_cast(args[1]); if (name && (JsParser::isUndefined(*name) || JsParser::isNull(*name))) name = 0; if (ns && (JsParser::isUndefined(*ns) || JsParser::isNull(*ns))) ns = 0; XmlElement* xml = 0; if (m_xml) xml = m_xml->findFirstChild(name,ns); if (xml) { JsArray* jsa = new JsArray(context,mutex()); while (xml) { jsa->push(new ExpWrapper(new JsXML(mutex(),xml,owner()))); xml = m_xml->findNextChild(xml,name,ns); } ExpEvaluator::pushOne(stack,new ExpWrapper(jsa,"children")); } else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("clearChildren")) { if (extractArgs(stack,oper,context,args)) return false; if (m_xml) m_xml->clearChildren(); } else if (oper.name() == YSTRING("addText")) { if (extractArgs(stack,oper,context,args) != 1) return false; ExpOperation* text = static_cast(args[0]); if (!m_xml || !text) return false; if (!(TelEngine::null(text) || JsParser::isNull(*text))) m_xml->addText(*text); } else if (oper.name() == YSTRING("getText")) { if (extractArgs(stack,oper,context,args)) return false; if (m_xml) ExpEvaluator::pushOne(stack,new ExpOperation(m_xml->getText(),m_xml->unprefixedTag())); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("setText")) { if (extractArgs(stack,oper,context,args) != 1) return false; ExpOperation* text = static_cast(args[0]); if (!(m_xml && text)) return false; if (JsParser::isNull(*text)) m_xml->setText(""); else m_xml->setText(*text); } else if (oper.name() == YSTRING("getChildText")) { if (extractArgs(stack,oper,context,args) > 2) return false; ExpOperation* name = static_cast(args[0]); ExpOperation* ns = static_cast(args[1]); if (name && (JsParser::isUndefined(*name) || JsParser::isNull(*name))) name = 0; if (ns && (JsParser::isUndefined(*ns) || JsParser::isNull(*ns))) ns = 0; XmlElement* xml = 0; if (m_xml) xml = m_xml->findFirstChild(name,ns); if (xml) ExpEvaluator::pushOne(stack,new ExpOperation(xml->getText(),xml->unprefixedTag())); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("xmlText")) { if (extractArgs(stack,oper,context,args) > 1) return false; if (m_xml) { int spaces = args[0] ? static_cast(args[0])->number() : 0; const String* line = &String::empty(); String indent; if (spaces > 0) { static const String crlf = "\r\n"; line = &crlf; indent.assign(' ',spaces); } ExpOperation* op = new ExpOperation("",m_xml->unprefixedTag()); m_xml->toString(*op,true,*line,indent); op->startSkip(*line,false); ExpEvaluator::pushOne(stack,op); } else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else return JsObject::runNative(stack,oper,context); return true; } JsObject* JsXML::runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsXML::runConstructor '%s'(" FMT64 ") [%p]",oper.name().c_str(),oper.number(),this); JsXML* obj = 0; ObjList args; int n = extractArgs(stack,oper,context,args); ExpOperation* arg1 = static_cast(args[0]); ExpOperation* arg2 = static_cast(args[1]); switch (n) { case 1: { // new XML(xmlObj), new XML("document") or new XML("element-name") XmlElement* xml = buildXml(arg1); if (!xml) xml = getXml(arg1,false); if (!xml) return JsParser::nullObject(); obj = new JsXML(mutex(),xml); } break; case 2: { // new XML(object,"field-name") or new XML("element-name","text-content") XmlElement* xml = buildXml(arg1,arg2); if (xml) { obj = new JsXML(mutex(),xml); break; } } // fall through case 3: { // new XML(object,"field-name",bool) JsObject* jso = YOBJECT(JsObject,arg1); if (!jso || !arg2) return 0; ExpOperation* arg3 = static_cast(args[2]); bool take = arg3 && arg3->valBoolean(); XmlElement* xml = getXml(jso->getField(stack,*arg2,context),take); if (!xml) return JsParser::nullObject(); obj = new JsXML(mutex(),xml); } break; default: return 0; } if (!obj) return 0; if (!ref()) { TelEngine::destruct(obj); return 0; } obj->params().addParam(new ExpWrapper(this,protoName())); return obj; } XmlElement* JsXML::getXml(const String* obj, bool take) { if (!obj) return 0; XmlElement* xml = 0; NamedPointer* nptr = YOBJECT(NamedPointer,obj); if (nptr) { xml = YOBJECT(XmlElement,nptr); if (xml) { if (take) { nptr->takeData(); return xml; } return new XmlElement(*xml); } } else if (!take) { xml = YOBJECT(XmlElement,obj); if (xml) return new XmlElement(*xml); } XmlDomParser parser; if (!(parser.parse(obj->c_str()) || parser.completeText())) return 0; if (!(parser.document() && parser.document()->root(true))) return 0; return new XmlElement(*parser.document()->root()); } XmlElement* JsXML::buildXml(const String* name, const String* text) { if (TelEngine::null(name) || name->getObject(YSTRING("JsObject"))) return 0; static const Regexp s_elemName("^[[:alpha:]_][[:alnum:]_.-]*$"); if (name->startsWith("xml",false,true) || !s_elemName.matches(*name)) return 0; return new XmlElement(name->c_str(),TelEngine::c_str(text)); } void JsXML::initialize(ScriptContext* context) { if (!context) return; Mutex* mtx = context->mutex(); Lock mylock(mtx); NamedList& params = context->params(); if (!params.getParam(YSTRING("XML"))) addConstructor(params,"XML",new JsXML(mtx)); } bool JsJSON::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { ObjList args; if (oper.name() == YSTRING("parse")) { if (extractArgs(stack,oper,context,args) != 1) return false; ExpOperation* op = JsParser::parseJSON(static_cast(args[0])->c_str(),mutex(),&stack,context); if (!op) op = new ExpWrapper(0,"JSON"); ExpEvaluator::pushOne(stack,op); } else if (oper.name() == YSTRING("stringify")) { if (extractArgs(stack,oper,context,args) < 1) return false; int spaces = args[2] ? static_cast(args[2])->number() : 0; ExpOperation* op = stringify(static_cast(args[0]),spaces); if (!op) op = new ExpWrapper(0,"JSON"); ExpEvaluator::pushOne(stack,op); } else if (oper.name() == YSTRING("loadFile")) { if (extractArgs(stack,oper,context,args) != 1) return false; ExpOperation* op = 0; ExpOperation* file = static_cast(args[0]); if (!TelEngine::null(file)) { File f; if (f.openPath(*file)) { int64_t len = f.length(); if (len > 0 && len <= 65536) { DataBlock buf(0,len + 1); char* text = (char*)buf.data(); if (f.readData(text,len) == len) { text[len] = '\0'; op = JsParser::parseJSON(text,mutex(),&stack,context); } } } } if (!op) op = new ExpWrapper(0,"JSON"); ExpEvaluator::pushOne(stack,op); } else if (oper.name() == YSTRING("saveFile")) { if (extractArgs(stack,oper,context,args) < 2) return false; ExpOperation* file = static_cast(args[0]); bool ok = !TelEngine::null(file); if (ok) { ok = false; int spaces = args[2] ? static_cast(args[2])->number() : 0; ExpOperation* op = stringify(static_cast(args[1]),spaces); if (op) { File f; if (f.openPath(*file,true,false,true)) { int len = op->length(); ok = f.writeData(op->c_str(),len) == len; } } TelEngine::destruct(op); } ExpEvaluator::pushOne(stack,new ExpOperation(ok)); } else return JsObject::runNative(stack,oper,context); return true; } ExpOperation* JsJSON::stringify(const ExpOperation* oper, int spaces) { if (!oper || YOBJECT(JsFunction,oper) || JsParser::isUndefined(*oper)) return 0; if (spaces < 0) spaces = 0; else if (spaces > 10) spaces = 10; ExpOperation* ret = new ExpOperation("","JSON"); stringify(oper,*ret,spaces); return ret; } void JsJSON::stringify(const NamedString* ns, String& buf, int spaces, int indent) { const ExpOperation* oper = YOBJECT(ExpOperation,ns); if (!oper) { if (ns) buf << strEscape(*ns); else buf << "null"; return; } if (JsParser::isNull(*oper) || JsParser::isUndefined(*oper) || YOBJECT(JsFunction,oper)) { buf << "null"; return; } const char* nl = spaces ? "\r\n" : ""; JsObject* jso = YOBJECT(JsObject,oper); JsArray* jsa = YOBJECT(JsArray,jso); if (jsa) { if (jsa->length() <= 0) { buf << "[]"; return; } String li(' ',indent); String ci(' ',indent + spaces); buf << "[" << nl; for (int32_t i = 0; ; ) { const NamedString* p = jsa->params().getParam(String(i)); if (!p) continue; buf << ci; stringify(p,buf,spaces,indent + spaces); if (++i < jsa->length()) buf << "," << nl; else { buf << nl; break; } } buf << li << "]"; return; } if (jso) { switch (jso->params().count()) { case 1: if (!jso->params().getParam(protoName())) break; // fall through case 0: buf << "{}"; return; } ObjList* l = jso->params().paramList()->skipNull(); String li(' ',indent); String ci(' ',indent + spaces); const char* sep = spaces ? ": " : ":"; buf << "{" << nl; while (l) { const NamedString* p = static_cast(l->get()); l = l->skipNext(); if (p->name() == protoName() || YOBJECT(JsFunction,p)) continue; const ExpOperation* op = YOBJECT(ExpOperation,p); if (op && JsParser::isUndefined(*op)) continue; buf << ci << strEscape(p->name()) << sep; stringify(p,buf,spaces,indent + spaces); for (; l; l = l->skipNext()) { p = static_cast(l->get()); op = YOBJECT(ExpOperation,p); if (!(p->name() == protoName() || YOBJECT(JsFunction,p) || (op && JsParser::isUndefined(*op)))) break; } if (l) buf << ","; buf << nl; } buf << li << "}"; return; } if (oper->isBoolean()) buf << String::boolText(oper->valBoolean()); else if (oper->isNumber()) { if (oper->isInteger()) buf << oper->number(); else buf << "null"; } else buf << strEscape(*oper); } String JsJSON::strEscape(const char* str) { String s("\""); char c; while (str && (c = *str++)) { switch (c) { case '\"': case '\\': s += "\\"; break; case '\b': s += "\\b"; continue; case '\f': s += "\\f"; continue; case '\n': s += "\\n"; continue; case '\r': s += "\\r"; continue; case '\t': s += "\\t"; continue; case '\v': s += "\\v"; continue; } s += c; } s += "\""; return s; } void JsJSON::initialize(ScriptContext* context) { if (!context) return; Mutex* mtx = context->mutex(); Lock mylock(mtx); NamedList& params = context->params(); if (!params.getParam(YSTRING("JSON"))) addObject(params,"JSON",new JsJSON(mtx)); } bool JsDNS::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { ObjList args; if (oper.name().startsWith("query")) { String type = oper.name().substr(5); ExpOperation* arg = 0; ExpOperation* async = 0; int argc = extractArgs(stack,oper,context,args); if (type.null() && (argc >= 2)) { type = static_cast(args[0]); arg = static_cast(args[1]); async = static_cast(args[2]); } else if (type && (argc >= 1)) { arg = static_cast(args[0]); async = static_cast(args[1]); } else return false; type.toUpper(); int qType = lookup(type,Resolver::s_types,-1); if ((qType < 0) || TelEngine::null(arg)) ExpEvaluator::pushOne(stack,new ExpWrapper(0,"DNS")); else { if (async && async->valBoolean()) { ScriptRun* runner = YOBJECT(ScriptRun,context); if (!runner) return false; runner->insertAsync(new JsDnsAsync(runner,this,&stack,*arg,(Resolver::Type)qType,context)); runner->pause(); return true; } runQuery(stack,*arg,(Resolver::Type)qType,context); } } else if ((oper.name() == YSTRING("resolve")) || (oper.name() == YSTRING("local"))) { if (extractArgs(stack,oper,context,args) != 1) return false; String tmp = static_cast(args[0]); if ((tmp[0] == '[') && (tmp[tmp.length() - 1] == ']')) tmp = tmp.substr(1,tmp.length() - 2); SocketAddr rAddr; ExpOperation* op = 0; if (rAddr.host(tmp)) { if (oper.name() == YSTRING("resolve")) op = new ExpOperation(rAddr.host(),"IP"); else { SocketAddr lAddr; if (lAddr.local(rAddr)) op = new ExpOperation(lAddr.host(),"IP"); } } if (!op) op = new ExpWrapper(0,"IP"); ExpEvaluator::pushOne(stack,op); } else if (oper.name().startsWith("pack")) { char sep = '\0'; ExpOperation* op; switch (extractArgs(stack,oper,context,args)) { case 2: op = static_cast(args[1]); if (op->isBoolean()) sep = op->valBoolean() ? ' ' : '\0'; else if ((op->length() == 1) && !op->isNumber()) sep = op->at(0); // fall through case 1: op = 0; { String tmp = static_cast(args[0]); if ((tmp[0] == '[') && (tmp[tmp.length() - 1] == ']')) tmp = tmp.substr(1,tmp.length() - 2); SocketAddr addr; if (addr.host(tmp)) { DataBlock d; addr.copyAddr(d); if (d.length()) { tmp.hexify(d.data(),d.length(),sep); op = new ExpOperation(tmp,"IP"); } } } if (!op) op = new ExpWrapper(0,"IP"); ExpEvaluator::pushOne(stack,op); break; default: return false; } } else if (oper.name().startsWith("unpack")) { if (extractArgs(stack,oper,context,args) != 1) return false; ExpOperation* op = 0; DataBlock d; if (d.unHexify(*static_cast(args[0]))) { SocketAddr addr; if (addr.assign(d)) op = new ExpOperation(addr.host(),"IP"); } if (!op) op = new ExpWrapper(0,"IP"); ExpEvaluator::pushOne(stack,op); } else return JsObject::runNative(stack,oper,context); return true; } void JsDNS::runQuery(ObjList& stack, const String& name, Resolver::Type type, GenObject* context) { JsArray* jsa = 0; ObjList res; if (Resolver::query(type,name,res) == 0) { jsa = new JsArray(context,mutex()); switch (type) { case Resolver::A4: case Resolver::A6: case Resolver::Txt: for (ObjList* l = res.skipNull(); l; l = l->skipNext()) { TxtRecord* r = static_cast(l->get()); jsa->push(new ExpOperation(r->text())); } break; case Resolver::Naptr: for (ObjList* l = res.skipNull(); l; l = l->skipNext()) { NaptrRecord* r = static_cast(l->get()); JsObject* jso = new JsObject(context,mutex()); jso->params().setParam(new ExpOperation(r->flags(),"flags")); jso->params().setParam(new ExpOperation(r->serv(),"service")); // Would be nice to create a RegExp here but does not stringify properly jso->params().setParam(new ExpOperation(r->regexp(),"regexp")); jso->params().setParam(new ExpOperation(r->repTemplate(),"replacement")); jso->params().setParam(new ExpOperation(r->nextName(),"name")); jso->params().setParam(new ExpOperation((int64_t)r->ttl(),"ttl")); jso->params().setParam(new ExpOperation((int64_t)r->order(),"order")); jso->params().setParam(new ExpOperation((int64_t)r->pref(),"preference")); jsa->push(new ExpWrapper(jso)); } break; case Resolver::Srv: for (ObjList* l = res.skipNull(); l; l = l->skipNext()) { SrvRecord* r = static_cast(l->get()); JsObject* jso = new JsObject(context,mutex()); jso->params().setParam(new ExpOperation((int64_t)r->port(),"port")); jso->params().setParam(new ExpOperation(r->address(),"name")); jso->params().setParam(new ExpOperation((int64_t)r->ttl(),"ttl")); jso->params().setParam(new ExpOperation((int64_t)r->order(),"order")); jso->params().setParam(new ExpOperation((int64_t)r->pref(),"preference")); jsa->push(new ExpWrapper(jso)); } break; default: break; } } ExpEvaluator::pushOne(stack,new ExpWrapper(jsa,lookup(type,Resolver::s_types))); } void JsDNS::initialize(ScriptContext* context) { if (!context) return; Mutex* mtx = context->mutex(); Lock mylock(mtx); NamedList& params = context->params(); if (!params.getParam(YSTRING("DNS"))) addObject(params,"DNS",new JsDNS(mtx)); } /** * class JsTimeEvent */ JsTimeEvent::JsTimeEvent(JsEngineWorker* worker, const ExpFunction& callback, unsigned int interval,bool repeatable, unsigned int id) : m_worker(worker), m_callbackFunction(callback.name(),1), m_interval(interval), m_repeat(repeatable), m_id(id) { XDebug(&__plugin,DebugAll,"Created new JsTimeEvent(%u,%s) [%p]",interval, String::boolText(repeatable),this); m_fire = Time::msecNow() + m_interval; } void JsTimeEvent::processTimeout(const Time& when) { if (m_repeat) m_fire = when.msec() + m_interval; ScriptRun* runner = m_worker->getRunner(); if (!runner) return; ObjList args; runner->call(m_callbackFunction.name(),args); } /** * class JsEngineWorker */ JsEngineWorker::JsEngineWorker(JsEngine* engine, ScriptContext* context, ScriptCode* code) : Thread("JsScheduler"), m_eventsMutex(false,"JsEngine"), m_id(0), m_runner(code->createRunner(context,NATIVE_TITLE)), m_engine(engine) { DDebug(&__plugin,DebugAll,"Creating JsEngineWorker engine=%p [%p]",(void*)m_engine,this); } JsEngineWorker::~JsEngineWorker() { DDebug(&__plugin,DebugAll,"Destroying JsEngineWorker engine=%p [%p]",(void*)m_engine,this); if (m_engine) m_engine->resetWorker(); m_engine = 0; TelEngine::destruct(m_runner); } unsigned int JsEngineWorker::addEvent(const ExpFunction& callback, unsigned int interval, bool repeat) { Lock myLock(m_eventsMutex); if (interval < MIN_CALLBACK_INTERVAL) interval = MIN_CALLBACK_INTERVAL; // TODO find a better way to generate the id's postponeEvent(new JsTimeEvent(this,callback,interval,repeat,++m_id)); return m_id; } bool JsEngineWorker::removeEvent(unsigned int id, bool repeatable) { Lock myLock(m_eventsMutex); for (ObjList* o = m_events.skipNull();o ; o = o->skipNext()) { JsTimeEvent* ev = static_cast(o->get()); if (ev->getId() != id) continue; if (ev->repeatable() != repeatable) return false; o->remove(); return true; } return false; } void JsEngineWorker::run() { while (!Thread::check(false)) { if (m_engine->refcount() == 1) { m_engine->resetWorker(); return; } Time t; Lock myLock(m_eventsMutex); ObjList* o = m_events.skipNull(); if (!o) { myLock.drop(); Thread::idle(true); continue; } RefPointer ev = static_cast(o->get()); myLock.drop(); if (!ev->timeout(t)) { ev = 0; Thread::idle(true); continue; } ev->processTimeout(t); if (!ev->repeatable()) { myLock.acquire(m_eventsMutex); m_events.remove(ev); continue; } myLock.acquire(m_eventsMutex); if (m_events.remove(ev,false)) postponeEvent(ev); } } void JsEngineWorker::postponeEvent(JsTimeEvent* evnt) { if (!evnt) return; for (ObjList* o = m_events.skipNull();o;o = o->skipNext()) { JsTimeEvent* ev = static_cast(o->get()); if (ev->fireTime() <= evnt->fireTime()) continue; o->insert(evnt); return; } m_events.append(evnt); } ScriptRun* JsEngineWorker::getRunner() { if (m_runner) m_runner->reset(); return m_runner; } bool JsChannel::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) { XDebug(&__plugin,DebugAll,"JsChannel::runNative '%s'(" FMT64 ")",oper.name().c_str(),oper.number()); if (oper.name() == YSTRING("id")) { if (oper.number()) return false; RefPointer ja = m_assist; if (ja) ExpEvaluator::pushOne(stack,new ExpOperation(ja->id())); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("peerid")) { if (oper.number()) return false; RefPointer ja = m_assist; if (!ja) return false; RefPointer cp = ja->locate(); String id; if (cp) cp->getPeerId(id); if (id) ExpEvaluator::pushOne(stack,new ExpOperation(id)); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("status")) { if (oper.number()) return false; RefPointer cp; RefPointer ja = m_assist; if (ja) cp = ja->locate(); Channel* ch = YOBJECT(Channel,cp); if (ch) ExpEvaluator::pushOne(stack,new ExpOperation(ch->status())); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("direction")) { if (oper.number()) return false; RefPointer cp; RefPointer ja = m_assist; if (ja) cp = ja->locate(); Channel* ch = YOBJECT(Channel,cp); if (ch) ExpEvaluator::pushOne(stack,new ExpOperation(ch->direction())); else ExpEvaluator::pushOne(stack,JsParser::nullClone()); } else if (oper.name() == YSTRING("answered")) { if (oper.number()) return false; RefPointer cp; RefPointer ja = m_assist; if (ja) cp = ja->locate(); Channel* ch = YOBJECT(Channel,cp); ExpEvaluator::pushOne(stack,new ExpOperation(ch && ch->isAnswered())); } else if (oper.name() == YSTRING("answer")) { if (oper.number()) return false; RefPointer ja = m_assist; if (ja) { Message* m = new Message("call.answered"); m->addParam("targetid",ja->id()); Engine::enqueue(m); } } else if (oper.name() == YSTRING("hangup")) { bool peer = false; ExpOperation* params = 0; switch (oper.number()) { case 3: params = popValue(stack,context); peer = params && params->valBoolean(); // fall through case 2: params = popValue(stack,context); // fall through case 1: break; default: return false; } ExpOperation* op = popValue(stack,context); ScriptRun* runner = YOBJECT(ScriptRun,context); RefPointer ja = m_assist; if (ja) { NamedList* lst = YOBJECT(NamedList,params); if (!lst) { ScriptContext* ctx = YOBJECT(ScriptContext,params); if (ctx) lst = &ctx->params(); } String id; if (peer) { RefPointer cp = ja->locate(); if (cp) cp->getPeerId(id); } if (!id) id = ja->id(); Message* m = new Message("call.drop"); m->addParam("id",id); copyObjParams(*m,lst); if (op && !op->null()) { m->addParam("reason",*op); // there may be a race between chan.disconnected and call.drop so set in both Message* msg = ja->getMsg(runner); if (msg) { msg->setParam((ja->state() == JsAssist::Routing) ? "error" : "reason",*op); copyObjParams(*msg,lst); } } ja->end(); Engine::enqueue(m); } TelEngine::destruct(op); TelEngine::destruct(params); if (runner) runner->pause(); } else if (oper.name() == YSTRING("callTo") || oper.name() == YSTRING("callJust")) { ExpOperation* params = 0; switch (oper.number()) { case 2: params = popValue(stack,context); // fall through case 1: break; default: return false; } ExpOperation* op = popValue(stack,context); if (!op) { op = params; params = 0; } if (!op) return false; RefPointer ja = m_assist; if (!ja) { TelEngine::destruct(op); TelEngine::destruct(params); return false; } NamedList* lst = YOBJECT(NamedList,params); if (!lst) { ScriptContext* ctx = YOBJECT(ScriptContext,params); if (ctx) lst = &ctx->params(); } switch (ja->state()) { case JsAssist::Routing: callToRoute(stack,*op,context,lst); break; case JsAssist::ReRoute: callToReRoute(stack,*op,context,lst); break; default: break; } TelEngine::destruct(op); TelEngine::destruct(params); if (oper.name() == YSTRING("callJust")) ja->end(); } else return JsObject::runNative(stack,oper,context); return true; } void JsChannel::callToRoute(ObjList& stack, const ExpOperation& oper, GenObject* context, const NamedList* params) { ScriptRun* runner = YOBJECT(ScriptRun,context); if (!runner) return; Message* msg = m_assist->getMsg(YOBJECT(ScriptRun,context)); if (!msg) { Debug(&__plugin,DebugWarn,"JsChannel::callToRoute(): No message!"); return; } if (oper.null() || JsParser::isNull(oper) || JsParser::isUndefined(oper)) { Debug(&__plugin,DebugWarn,"JsChannel::callToRoute(): Invalid target!"); return; } copyObjParams(*msg,params); msg->retValue() = oper; m_assist->handled(); runner->pause(); } void JsChannel::callToReRoute(ObjList& stack, const ExpOperation& oper, GenObject* context, const NamedList* params) { ScriptRun* runner = YOBJECT(ScriptRun,context); if (!runner) return; RefPointer ep; Message* msg = m_assist->getMsg(YOBJECT(ScriptRun,context)); Channel* chan = msg ? YOBJECT(Channel,msg->userData()) : 0; if (!chan) { ep = m_assist->locate(); chan = YOBJECT(Channel,ep); } if (!chan) { Debug(&__plugin,DebugWarn,"JsChannel::callToReRoute(): No channel!"); return; } String target = oper; target.trimSpaces(); if (target.null() || JsParser::isNull(oper) || JsParser::isUndefined(oper)) { Debug(&__plugin,DebugWarn,"JsChannel::callToRoute(): Invalid target!"); return; } Message* m = chan->message("call.execute",false,true); m->setParam("callto",target); // copy params except those already set if (msg) { unsigned int n = msg->length(); for (unsigned int i = 0; i < n; i++) { const NamedString* p = msg->getParam(i); if (p && !m->getParam(p->name())) m->addParam(p->name(),*p); } } copyObjParams(*m,params); Engine::enqueue(m); m_assist->handled(); runner->pause(); } void JsChannel::initialize(ScriptContext* context, JsAssist* assist) { if (!context) return; Mutex* mtx = context->mutex(); Lock mylock(mtx); NamedList& params = context->params(); if (!params.getParam(YSTRING("Channel"))) addObject(params,"Channel",new JsChannel(assist,mtx)); } #define MKSTATE(x) { #x, JsAssist::x } static const TokenDict s_states[] = { MKSTATE(NotStarted), MKSTATE(Routing), MKSTATE(ReRoute), MKSTATE(Ended), MKSTATE(Hangup), { 0, 0 } }; JsAssist::~JsAssist() { if (m_runner) { ScriptContext* context = m_runner->context(); if (m_runner->callable("onUnload")) { ScriptRun* runner = m_runner->code()->createRunner(context,NATIVE_TITLE); if (runner) { ObjList args; runner->call("onUnload",args); TelEngine::destruct(runner); } } m_message = 0; if (context) { Lock mylock(context->mutex()); context->params().clearParams(); } TelEngine::destruct(m_runner); } else m_message = 0; } const char* JsAssist::stateName(State st) { return lookup(st,s_states,"???"); } bool JsAssist::init() { if (!m_runner) return false; contextInit(m_runner,id(),this); if (ScriptRun::Invalid == m_runner->reset(true)) return false; ScriptContext* ctx = m_runner->context(); ScriptContext* chan = YOBJECT(ScriptContext,ctx->getField(m_runner->stack(),YSTRING("Channel"),m_runner)); if (chan) { JsMessage* jsm = YOBJECT(JsMessage,chan->getField(m_runner->stack(),YSTRING("message"),m_runner)); if (!jsm) { jsm = new JsMessage(0,ctx->mutex(),false); ExpWrapper wrap(jsm,"message"); if (!chan->runAssign(m_runner->stack(),wrap,m_runner)) return false; } if (jsm && jsm->ref()) { jsm->setPrototype(ctx,YSTRING("Message")); JsObject* cc = JsObject::buildCallContext(ctx->mutex(),jsm); jsm->ref(); cc->params().setParam(new ExpWrapper(jsm,"message")); ExpEvaluator::pushOne(m_runner->stack(),new ExpWrapper(cc,cc->toString(),true)); } } if (!m_runner->callable("onLoad")) return true; ScriptRun* runner = m_runner->code()->createRunner(m_runner->context(),NATIVE_TITLE); if (runner) { ObjList args; runner->call("onLoad",args); TelEngine::destruct(runner); return true; } return false; } Message* JsAssist::getMsg(ScriptRun* runner) const { if (!runner) runner = m_runner; if (!runner) return 0; ScriptContext* ctx = runner->context(); if (!ctx) return 0; ObjList stack; ScriptContext* chan = YOBJECT(ScriptContext,ctx->getField(stack,YSTRING("Channel"),runner)); if (!chan) return 0; JsMessage* jsm = YOBJECT(JsMessage,chan->getField(stack,YSTRING("message"),runner)); if (!jsm) return 0; return static_cast(jsm->nativeParams()); } bool JsAssist::setMsg(Message* msg) { if (!m_runner) return false; ScriptContext* ctx = m_runner->context(); if (!ctx) return false; Lock mylock(ctx->mutex()); if (!mylock.locked()) return false; if (m_message) return false; ObjList stack; ScriptContext* chan = YOBJECT(ScriptContext,ctx->getField(stack,YSTRING("Channel"),m_runner)); if (!chan) return false; JsMessage* jsm = YOBJECT(JsMessage,chan->getField(stack,YSTRING("message"),m_runner)); if (jsm) jsm->setMsg(msg,false); else return false; m_message = jsm; m_handled = false; return true; } void JsAssist::clearMsg(bool fromChannel) { Lock mylock((m_runner && m_runner->context()) ? m_runner->context()->mutex() : 0); if (!m_message) return; m_message->clearMsg(); m_message = 0; if (fromChannel && mylock.locked()) { ObjList stack; ScriptContext* chan = YOBJECT(ScriptContext,m_runner->context()->getField(stack,YSTRING("Channel"),m_runner)); if (chan) { static const ExpWrapper s_undef(0,"message"); chan->runAssign(stack,s_undef,m_runner); } } } bool JsAssist::runScript(Message* msg, State newState) { XDebug(&__plugin,DebugInfo,"JsAssist::runScript('%s') for '%s' in state %s", msg->c_str(),id().c_str(),stateName()); if (m_state >= Ended) return false; if (m_state < newState) m_state = newState; #ifdef DEBUG u_int64_t tm = Time::now(); #endif if (!setMsg(msg)) { Debug(&__plugin,DebugWarn,"Failed to set message '%s' in '%s'", msg->c_str(),id().c_str()); return false; } m_repeat = true; do { switch (m_runner->execute()) { case ScriptRun::Incomplete: break; case ScriptRun::Invalid: case ScriptRun::Succeeded: if (m_state < Ended) m_state = Ended; // fall through default: m_repeat = false; break; } } while (m_repeat); bool handled = m_handled; clearMsg(m_state >= Ended); if (Routing == m_state) m_state = ReRoute; #ifdef DEBUG tm = Time::now() - tm; Debug(&__plugin,DebugInfo,"Script for '%s' ran for " FMT64U " usec",id().c_str(),tm); #endif return handled; } bool JsAssist::runFunction(const String& name, Message& msg, bool* handled) { if (!(m_runner && m_runner->callable(name))) return false; DDebug(&__plugin,DebugInfo,"Running function %s(message%s) in '%s' state %s", name.c_str(),(handled ? ",handled" : ""),id().c_str(),stateName()); #ifdef DEBUG u_int64_t tm = Time::now(); #endif ScriptRun* runner = __plugin.parser().createRunner(m_runner->context(),NATIVE_TITLE); if (!runner) return false; JsMessage* jm = new JsMessage(&msg,runner->context()->mutex(),false); jm->setPrototype(runner->context(),YSTRING("Message")); jm->ref(); ObjList args; args.append(new ExpWrapper(jm,"message")); if (handled) { jm->freeze(); args.append(new ExpOperation(*handled,"handled")); } ScriptRun::Status rval = runner->call(name,args); jm->clearMsg(); bool ok = false; if (ScriptRun::Succeeded == rval) { ExpOperation* op = ExpEvaluator::popOne(runner->stack()); if (op) { ok = op->valBoolean(); TelEngine::destruct(op); } } TelEngine::destruct(jm); TelEngine::destruct(runner); #ifdef DEBUG tm = Time::now() - tm; Debug(&__plugin,DebugInfo,"Call to %s() ran for " FMT64U " usec",name.c_str(),tm); #endif return ok; } void JsAssist::msgStartup(Message& msg) { runFunction("onStartup",msg); } void JsAssist::msgHangup(Message& msg) { runFunction("onHangup",msg); } void JsAssist::msgExecute(Message& msg) { runFunction("onExecute",msg); } bool JsAssist::msgRinging(Message& msg) { return runFunction("onRinging",msg); } bool JsAssist::msgAnswered(Message& msg) { return runFunction("onAnswered",msg); } bool JsAssist::msgPreroute(Message& msg) { return runFunction("onPreroute",msg); } bool JsAssist::msgRoute(Message& msg) { return runScript(&msg,Routing); } bool JsAssist::msgDisconnect(Message& msg, const String& reason) { return runFunction("onDisconnected",msg) || runScript(&msg,ReRoute); } void JsAssist::msgPostExecute(const Message& msg, bool handled) { runFunction("onPostExecute",const_cast(msg),&handled); } ObjList JsGlobal::s_globals; JsGlobal::JsGlobal(const char* scriptName, const char* fileName, bool relPath, bool fromCfg) : NamedString(scriptName,fileName), m_inUse(true), m_confLoaded(fromCfg), m_file(fileName) { m_jsCode.basePath(s_basePath,s_libsPath); if (relPath) m_jsCode.adjustPath(*this); m_jsCode.setMaxFileLen(s_maxFile); m_jsCode.link(s_allowLink); m_jsCode.trace(s_allowTrace); DDebug(&__plugin,DebugAll,"Loading global Javascript '%s' from '%s'",name().c_str(),c_str()); if (m_jsCode.parseFile(*this)) Debug(&__plugin,DebugInfo,"Parsed '%s' script: %s",name().c_str(),c_str()); else if (*this) Debug(&__plugin,DebugWarn,"Failed to parse '%s' script: %s",name().c_str(),c_str()); } JsGlobal::~JsGlobal() { DDebug(&__plugin,DebugAll,"Unloading global Javascript '%s'",name().c_str()); if (m_jsCode.callable("onUnload")) { ScriptRun* runner = m_jsCode.createRunner(m_context,NATIVE_TITLE); if (runner) { ObjList args; runner->call("onUnload",args); TelEngine::destruct(runner); } } if (m_context) { Lock mylock(m_context->mutex()); m_context->params().clearParams(); } } bool JsGlobal::fileChanged(const char* fileName) const { return m_jsCode.scriptChanged(fileName,s_basePath,s_libsPath); } void JsGlobal::markUnused() { ListIterator iter(s_globals); while (JsGlobal* script = static_cast(iter.get())) script->m_inUse = !script->m_confLoaded; } void JsGlobal::freeUnused() { Lock mylock(__plugin); ListIterator iter(s_globals); while (JsGlobal* script = static_cast(iter.get())) if (!script->m_inUse) { s_globals.remove(script,false); mylock.drop(); TelEngine::destruct(script); mylock.acquire(__plugin); } } void JsGlobal::reloadDynamic() { Lock mylock(__plugin); ListIterator iter(s_globals); while (JsGlobal* script = static_cast(iter.get())) if (!script->m_confLoaded) { String filename = script->fileName(); String name = script->name(); mylock.drop(); JsGlobal::initScript(name,filename,true,false); mylock.acquire(__plugin); } } bool JsGlobal::initScript(const String& scriptName, const String& fileName, bool relPath, bool fromCfg) { if (fileName.null()) return false; DDebug(&__plugin,DebugInfo,"Initialize %s script '%s' from %s file '%s'",(fromCfg ? "configured" : "dynamically loaded"), scriptName.c_str(),(relPath ? "relative" : "absolute"),fileName.c_str()); Lock mylock(__plugin); JsGlobal* script = static_cast(s_globals[scriptName]); if (script) { if (script->m_confLoaded != fromCfg) { Debug(&__plugin,DebugWarn,"Trying to load script '%s' %s, but it was already loaded %s", scriptName.c_str(),fromCfg ? "from configuration file" : "dynamically", fromCfg ? "dynamically" : "from configuration file"); return false; } if (script->fileChanged(fileName)) { s_globals.remove(script,false); mylock.drop(); TelEngine::destruct(script); mylock.acquire(__plugin); } else { script->m_inUse = true; script->m_confLoaded = fromCfg; return true; } } script = new JsGlobal(scriptName,fileName,relPath,fromCfg); s_globals.append(script); mylock.drop(); return script->runMain(); } bool JsGlobal::reloadScript(const String& scriptName) { if (scriptName.null()) return false; Lock mylock(__plugin); JsGlobal* script = static_cast(s_globals[scriptName]); if (!script) return false; String fileName = *script; if (fileName.null()) return false; bool fromCfg = script->m_confLoaded; s_globals.remove(script,false); mylock.drop(); TelEngine::destruct(script); mylock.acquire(__plugin); script = new JsGlobal(scriptName,fileName,false,fromCfg); s_globals.append(script); mylock.drop(); return script->runMain(); } void JsGlobal::loadScripts(const NamedList* sect) { if (!sect) return; unsigned int len = sect->length(); for (unsigned int i=0; igetParam(i); if (!n) continue; String tmp = *n; Engine::runParams().replaceParams(tmp); JsGlobal::initScript(n->name(),tmp); } } bool JsGlobal::runMain() { ScriptRun* runner = m_jsCode.createRunner(m_context); if (!runner) return false; if (!m_context) m_context = runner->context(); contextInit(runner,name()); ScriptRun::Status st = runner->run(); TelEngine::destruct(runner); return (ScriptRun::Succeeded == st); } static const char* s_cmds[] = { "info", "eval", "reload", "load", 0 }; static const char* s_cmdsLine = " javascript {info|eval[=context] instructions...|reload script|load [script=]file}"; JsModule::JsModule() : ChanAssistList("javascript",true), m_postHook(0), m_started(Engine::started()) { Output("Loaded module Javascript"); } JsModule::~JsModule() { Output("Unloading module Javascript"); clearPostHook(); } void JsModule::clearPostHook() { if (m_postHook) { Engine::self()->setHook(m_postHook,true); TelEngine::destruct(m_postHook); } } void JsModule::msgPostExecute(const Message& msg, bool handled) { const String& id = msg[YSTRING("id")]; if (id.null()) return; lock(); RefPointer ja = static_cast(find(id)); unlock(); if (ja) ja->msgPostExecute(msg,handled); } void JsModule::statusParams(String& str) { lock(); str << "globals=" << JsGlobal::globals().count() << ",routing=" << calls().count(); unlock(); } bool JsModule::commandExecute(String& retVal, const String& line) { String cmd = line; if (!cmd.startSkip(name())) return false; cmd.trimSpaces(); if (cmd.null() || cmd == YSTRING("info")) { retVal.clear(); lock(); ListIterator iter(JsGlobal::globals()); while (JsGlobal* script = static_cast(iter.get())) retVal << script->name() << " = " << *script << "\r\n"; iter.assign(calls()); while (JsAssist* assist = static_cast(iter.get())) retVal << assist->id() << ": " << assist->stateName() << "\r\n"; unlock(); return true; } if (cmd.startSkip("reload") && cmd.trimSpaces()) return JsGlobal::reloadScript(cmd); if (cmd.startSkip("eval=",false) && cmd.trimSpaces()) { String scr; cmd.extractTo(" ",scr).trimSpaces(); if (scr.null() || cmd.null()) return false; Lock mylock(this); JsGlobal* script = static_cast(JsGlobal::globals()[scr]); if (script) { RefPointer ctxt = script->context(); mylock.drop(); return evalContext(retVal,cmd,ctxt); } JsAssist* assist = static_cast(calls()[scr]); if (assist) { RefPointer ctxt = assist->context(); mylock.drop(); return evalContext(retVal,cmd,ctxt); } retVal << "Cannot find script context: " << scr << "\n\r"; return true; } if (cmd.startSkip("eval") && cmd.trimSpaces()) return evalContext(retVal,cmd); if (cmd.startSkip("load") && cmd.trimSpaces()) { if (!cmd) { retVal << "Missing mandatory argument specifying which file to load\n\r"; return true; } String name; int pos = cmd.find('='); if (pos > -1) { name = cmd.substr(0,pos); cmd = cmd.c_str() + pos + 1; } if (!cmd) { retVal << "Missing file name argument\n\r"; return true; } if (cmd.endsWith("/") #ifdef _WINDOWS || cmd.endsWith("\\") #endif ) { retVal << "Missing file name. Cannot load directory '" << cmd <<"'\n\r"; return true; } int extPos = cmd.rfind('.'); int sepPos = cmd.rfind('/'); #ifdef _WINDOWS int backPos = cmd.rfind('\\'); sepPos = sepPos > backPos ? sepPos : backPos; #endif if (extPos < 0 || sepPos > extPos) { // for "dir.name/filename" cases extPos = cmd.length(); cmd += ".js"; } if (!name) name = cmd.substr(sepPos + 1,extPos - sepPos - 1); if (!JsGlobal::initScript(name,cmd,true,false)) retVal << "Failed to load script from file '" << cmd << "'\n\r"; return true; } return false; } bool JsModule::evalContext(String& retVal, const String& cmd, ScriptContext* context) { JsParser parser; parser.basePath(s_basePath,s_libsPath); parser.setMaxFileLen(s_maxFile); parser.link(s_allowLink); parser.trace(s_allowTrace); if (!parser.parse(cmd)) { retVal << "parsing failed\r\n"; return true; } ScriptRun* runner = parser.createRunner(context,"[command line]"); if (!context) contextInit(runner); ScriptRun::Status st = runner->run(); if (st == ScriptRun::Succeeded) { while (ExpOperation* op = ExpEvaluator::popOne(runner->stack())) { retVal << "'" << op->name() << "'='" << *op << "'\r\n"; TelEngine::destruct(op); } } else retVal << ScriptRun::textState(st) << "\r\n"; TelEngine::destruct(runner); return true; } bool JsModule::commandComplete(Message& msg, const String& partLine, const String& partWord) { if (partLine.null() && partWord.null()) return false; if (partLine.null() || (partLine == "help")) itemComplete(msg.retValue(),name(),partWord); else if (partLine == name()) { static const String s_eval("eval="); if (partWord.startsWith(s_eval)) { lock(); ListIterator iter(JsGlobal::globals()); while (JsGlobal* script = static_cast(iter.get())) if (!script->name().null()) itemComplete(msg.retValue(),s_eval + script->name(),partWord); iter.assign(calls()); while (JsAssist* assist = static_cast(iter.get())) itemComplete(msg.retValue(),s_eval + assist->id(),partWord); unlock(); return true; } for (const char** list = s_cmds; *list; list++) itemComplete(msg.retValue(),*list,partWord); return true; } else if (partLine == YSTRING("javascript reload")) { lock(); ListIterator iter(JsGlobal::globals()); while (JsGlobal* script = static_cast(iter.get())) { if (!script->name().null()) itemComplete(msg.retValue(),script->name(),partWord); } unlock(); return true; } return Module::commandComplete(msg,partLine,partWord); } bool JsModule::received(Message& msg, int id) { switch (id) { case Help: { const String* line = msg.getParam("line"); if (TelEngine::null(line)) { msg.retValue() << s_cmdsLine << "\r\n"; return false; } if (name() != *line) return false; } msg.retValue() << s_cmdsLine << "\r\n"; msg.retValue() << "Controls and executes Javascript commands\r\n"; return true; case Preroute: case Route: { const String* chanId = msg.getParam("id"); if (TelEngine::null(chanId)) break; Lock mylock(this); RefPointer ca = static_cast(find(*chanId)); switch (id) { case Preroute: if (ca) { mylock.drop(); return ca->msgPreroute(msg); } ca = static_cast(create(msg,*chanId)); if (ca) { calls().append(ca); mylock.drop(); ca->msgStartup(msg); return ca->msgPreroute(msg); } return false; case Route: if (ca) { mylock.drop(); return ca->msgRoute(msg); } ca = static_cast(create(msg,*chanId)); if (ca) { calls().append(ca); mylock.drop(); ca->msgStartup(msg); return ca->msgRoute(msg); } return false; } // switch (id) } break; case Ringing: case Answered: { const String* chanId = msg.getParam("peerid"); if (TelEngine::null(chanId)) return false; Lock mylock(this); RefPointer ca = static_cast(find(*chanId)); if (!ca) return false; switch (id) { case Ringing: return ca && ca->msgRinging(msg); case Answered: return ca && ca->msgAnswered(msg); } } break; case Halt: s_engineStop = true; clearPostHook(); JsGlobal::unloadAll(); return false; case EngStart: if (!m_started) { m_started = true; Configuration cfg(Engine::configFile("javascript")); JsGlobal::loadScripts(cfg.getSection("late_scripts")); } return false; } // switch (id) return ChanAssistList::received(msg,id); } bool JsModule::received(Message& msg, int id, ChanAssist* assist) { return ChanAssistList::received(msg,id,assist); } ChanAssist* JsModule::create(Message& msg, const String& id) { if ((msg == YSTRING("chan.startup")) && (msg[YSTRING("direction")] == YSTRING("outgoing"))) return 0; lock(); ScriptRun* runner = m_assistCode.createRunner(0,NATIVE_TITLE); unlock(); if (!runner) return 0; DDebug(this,DebugInfo,"Creating Javascript for '%s'",id.c_str()); JsAssist* ca = new JsAssist(this,id,runner); if (ca->init()) return ca; TelEngine::destruct(ca); return 0; } bool JsModule::unload() { clearPostHook(); uninstallRelays(); return true; } void JsModule::initialize() { Output("Initializing module Javascript"); ChanAssistList::initialize(); setup(); installRelay(Help); if (!m_postHook) Engine::self()->setHook(m_postHook = new JsPostExecute); Configuration cfg(Engine::configFile("javascript")); String tmp = Engine::sharedPath(); tmp << Engine::pathSeparator() << "scripts"; tmp = cfg.getValue("general","scripts_dir",tmp); Engine::runParams().replaceParams(tmp); if (tmp && !tmp.endsWith(Engine::pathSeparator())) tmp += Engine::pathSeparator(); s_basePath = tmp; tmp = cfg.getValue("general","include_dir","${configpath}"); Engine::runParams().replaceParams(tmp); if (tmp && !tmp.endsWith(Engine::pathSeparator())) tmp += Engine::pathSeparator(); s_libsPath = tmp; s_maxFile = cfg.getIntValue("general","max_length",500000,32768,2097152); s_autoExt = cfg.getBoolValue("general","auto_extensions",true); s_allowAbort = cfg.getBoolValue("general","allow_abort"); bool changed = false; if (cfg.getBoolValue("general","allow_trace") != s_allowTrace) { s_allowTrace = !s_allowTrace; changed = true; } if (cfg.getBoolValue("general","allow_link",true) != s_allowLink) { s_allowLink = !s_allowLink; changed = true; } tmp = cfg.getValue("general","routing"); Engine::runParams().replaceParams(tmp); lock(); if (changed || m_assistCode.scriptChanged(tmp,s_basePath,s_libsPath)) { m_assistCode.clear(); m_assistCode.setMaxFileLen(s_maxFile); m_assistCode.link(s_allowLink); m_assistCode.trace(s_allowTrace); m_assistCode.basePath(s_basePath,s_libsPath); m_assistCode.adjustPath(tmp); if (m_assistCode.parseFile(tmp)) Debug(this,DebugInfo,"Parsed routing script: %s",tmp.c_str()); else if (tmp) Debug(this,DebugWarn,"Failed to parse script: %s",tmp.c_str()); } JsGlobal::markUnused(); unlock(); JsGlobal::loadScripts(cfg.getSection("scripts")); if (m_started) JsGlobal::loadScripts(cfg.getSection("late_scripts")); JsGlobal::reloadDynamic(); JsGlobal::freeUnused(); } void JsModule::init(int priority) { ChanAssistList::init(priority); installRelay(Halt,120); installRelay(Route,priority); installRelay(Ringing,priority); installRelay(Answered,priority); Engine::install(new MessageRelay("call.preroute",this,Preroute,priority,name())); Engine::install(new MessageRelay("engine.start",this,EngStart,150,name())); } }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet: */