/* * mod_v8 for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2013-2014, Peter Olsson * * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is ported from FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * * The Initial Developer of the Original Code is * Anthony Minessale II * Portions created by the Initial Developer are Copyright (C) * the Initial Developer. All Rights Reserved. * * Contributor(s): * Peter Olsson * Anthony Minessale II * William King * Andrey Volk * * mod_v8.cpp -- JavaScript FreeSWITCH module * */ /* * This module executes JavaScript using Google's V8 JavaScript engine. * * It extends the available JavaScript classes with the following FS related classes; * CoreDB Adds features to access the core DB (SQLite) in FreeSWITCH. (on request only) * DBH Database Handler. Makes use of connection pooling provided by FreeSWITCH. (on request only) * CURL Adds some extra methods for CURL access. (on request only) * DTMF Object that holds information about a DTMF event. * Event Object that holds information about a FreeSWITCH event. * EventHandler Features for handling FS events. * File Class to reflect the Spidermonkey built-in class "File". Not yet implemented! (on request only) * FileIO Simple class for basic file IO. * ODBC Adds features to access any ODBC available database in the system. (on request only) * PCRE Adds features to do regexp using the PCRE implementeation. * Request Class for extra features during API call from FS (using 'jsapi' function). This class cannot be constructed from JS code! * The Request class is only availble when started from 'jsapi' FS command, and only inside the predefined variable 'request'. * Session Main FS class, includes all functions to handle a session. * Socket Class for communicating over a TCP/IP socket. (on request only) * TeleTone Class used to play tones to a FS channel. (on request only) * XML XML parsing class, using the features from switch_xml. (on request only) * * Some of the classes above are available on request only, using the command [use('Class');] before using the class for the first time. * * It also adds quite a few global functions, directly available for the user (see fsglobal.cpp for the implementation). * * Depedning on how the script was started from FreeSWITCH, some variables might be defined already; * session If the script is started from the dialplan, the variable 'session' holds the session for the current call. * request If the script is started using 'jsapi' function, the variable 'request' is an instance of the Request class. * message If the script is started as a chat application, the actual FreeSWITCH event will be available in the variable 'message'. * * All classes are implemented in a pair of hpp/cpp files, named after the class. For instance; class "File" is implemented in fsfile.cpp. * */ #include "mod_v8.h" #include #ifdef V8_ENABLE_DEBUGGING #include #endif /* Global JavaScript functions */ #include "fsglobal.hpp" /* Common JavaScript classes */ #include "fsrequest.hpp" /* Only loaded during 'jsapi' and 'jsjson' call */ #include "fspcre.hpp" #include "fsevent.hpp" #include "fssession.hpp" #include "fsdtmf.hpp" #include "fsfileio.hpp" /* Optional JavaScript classes (loaded on demand) */ #include "fscoredb.hpp" #include "fsdbh.hpp" #include "fscurl.hpp" #include "fsteletone.hpp" #include "fssocket.hpp" #include "fsodbc.hpp" #include "fsxml.hpp" #include "fsfile.hpp" #include "fseventhandler.hpp" #include using namespace std; using namespace v8; SWITCH_BEGIN_EXTERN_C /* FreeSWITCH module load definitions */ SWITCH_MODULE_LOAD_FUNCTION(mod_v8_load); SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_v8_shutdown); SWITCH_MODULE_DEFINITION_EX(mod_v8, mod_v8_load, mod_v8_shutdown, NULL, SMODF_GLOBAL_SYMBOLS); /* API interfaces */ static switch_api_interface_t *jsrun_interface = NULL; static switch_api_interface_t *jsapi_interface = NULL; /* Module manager for loadable modules */ module_manager_t module_manager = { 0 }; /* Global data for this module */ typedef struct { switch_memory_pool_t *pool; switch_mutex_t *event_mutex; switch_event_node_t *event_node; set *event_handlers; char *xml_handler; } mod_v8_global_t; static mod_v8_global_t globals = { 0 }; /* Loadable module struct, used for external extension modules */ typedef struct { char *filename; void *lib; const v8_mod_interface_t *module_interface; v8_mod_init_t v8_mod_init; } v8_loadable_module_t; #ifdef V8_ENABLE_DEBUGGING static bool debug_enable_callback = false; static int debug_listen_port = 9999; static bool debug_wait_for_connection = true; static bool debug_manual_break = true; #endif using namespace v8; static switch_status_t v8_mod_init_built_in(const v8_mod_interface_t *mod_interface) { switch_assert(mod_interface); switch_core_hash_insert(module_manager.load_hash, (char *) mod_interface->name, mod_interface); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Successfully Loaded [%s]\n", mod_interface->name); return SWITCH_STATUS_SUCCESS; } static switch_status_t v8_mod_load_file(const char *filename) { v8_loadable_module_t *module = NULL; switch_dso_lib_t dso = NULL; switch_status_t status = SWITCH_STATUS_SUCCESS; switch_loadable_module_function_table_t *function_handle = NULL; v8_mod_init_t v8_mod_init = NULL; const v8_mod_interface_t *module_interface = NULL, *mp; char *derr = NULL; const char *err = NULL; switch_assert(filename != NULL); if (!(dso = switch_dso_open(filename, 1, &derr))) { status = SWITCH_STATUS_FALSE; } if (derr || status != SWITCH_STATUS_SUCCESS) { err = derr; goto err; } function_handle = (switch_loadable_module_function_table_t *)switch_dso_data_sym(dso, "v8_mod_init", &derr); if (!function_handle || derr) { status = SWITCH_STATUS_FALSE; err = derr; goto err; } v8_mod_init = (v8_mod_init_t) (intptr_t) function_handle; if (v8_mod_init(&module_interface) != SWITCH_STATUS_SUCCESS) { err = "Module load routine returned an error"; goto err; } if (!(module = (v8_loadable_module_t *) switch_core_permanent_alloc(sizeof(*module)))) { err = "Could not allocate memory\n"; } err: if (err || !module) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Loading module %s\n**%s**\n", filename, switch_str_nil(err)); switch_safe_free(derr); return SWITCH_STATUS_GENERR; } module->filename = switch_core_permanent_strdup(filename); module->v8_mod_init = v8_mod_init; module->module_interface = module_interface; module->lib = dso; if ((mp = module->module_interface)) { switch_core_hash_insert(module_manager.load_hash, (char *) mp->name, (void *) mp); } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Successfully Loaded [%s]\n", module->filename); return SWITCH_STATUS_SUCCESS; } static switch_status_t v8_load_module(const char *dir, const char *fname) { switch_size_t len = 0; char *path; char *file; #ifdef WIN32 const char *ext = ".dll"; #else const char *ext = ".so"; #endif if ((file = switch_core_strdup(module_manager.pool, fname)) == 0) { return SWITCH_STATUS_FALSE; } if (*file == '/') { path = switch_core_strdup(module_manager.pool, file); } else { if (strchr(file, '.')) { len = strlen(dir); len += strlen(file); len += 4; path = (char *) switch_core_alloc(module_manager.pool, len); switch_snprintf(path, len, "%s%s%s", dir, SWITCH_PATH_SEPARATOR, file); } else { len = strlen(dir); len += strlen(file); len += 8; path = (char *) switch_core_alloc(module_manager.pool, len); switch_snprintf(path, len, "%s%s%s%s", dir, SWITCH_PATH_SEPARATOR, file, ext); } } return v8_mod_load_file(path); } SWITCH_END_EXTERN_C static switch_status_t load_modules(void) { const char *cf = "v8.conf"; switch_xml_t cfg, xml; unsigned int count = 0; #ifdef WIN32 const char *ext = ".dll"; const char *EXT = ".DLL"; #elif defined (MACOSX) || defined (DARWIN) const char *ext = ".dylib"; const char *EXT = ".DYLIB"; #else const char *ext = ".so"; const char *EXT = ".SO"; #endif switch_core_new_memory_pool(&module_manager.pool); switch_core_hash_init(&module_manager.load_hash); if ((xml = switch_xml_open_cfg(cf, &cfg, NULL))) { switch_xml_t mods, ld; if ((mods = switch_xml_child(cfg, "modules"))) { for (ld = switch_xml_child(mods, "load"); ld; ld = ld->next) { const char *val = switch_xml_attr_soft(ld, "module"); if (!zstr(val) && strchr(val, '.') && !strstr(val, ext) && !strstr(val, EXT)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Invalid extension for %s\n", val); continue; } v8_load_module(SWITCH_GLOBAL_dirs.mod_dir, val); count++; } } switch_xml_free(xml); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Open of %s failed\n", cf); } return SWITCH_STATUS_SUCCESS; } static void load_configuration(void) { const char *cf = "v8.conf"; switch_xml_t cfg, xml; if ((xml = switch_xml_open_cfg(cf, &cfg, NULL))) { switch_xml_t settings, param, hook; if ((settings = switch_xml_child(cfg, "settings"))) { for (param = switch_xml_child(settings, "param"); param; param = param->next) { char *var = (char *)switch_xml_attr_soft(param, "name"); char *val = (char *)switch_xml_attr_soft(param, "value"); if (!strcmp(var, "xml-handler-script")) { globals.xml_handler = switch_core_strdup(globals.pool, val); } else if (!strcmp(var, "xml-handler-bindings")) { if (!zstr(globals.xml_handler)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "binding '%s' to '%s'\n", globals.xml_handler, val); switch_xml_bind_search_function(v8_fetch, switch_xml_parse_section_string(val), NULL); } } else if (!strcmp(var, "startup-script")) { if (val) { v8_thread_launch(val); } } } for (hook = switch_xml_child(settings, "hook"); hook; hook = hook->next) { char *event = (char *)switch_xml_attr_soft(hook, "event"); char *subclass = (char *)switch_xml_attr_soft(hook, "subclass"); char *script = (char *)switch_xml_attr_soft(hook, "script"); switch_event_types_t evtype; if (!zstr(script)) { script = switch_core_strdup(globals.pool, script); } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "hook params: '%s' | '%s' | '%s'\n", event, subclass, script); if (switch_name_event(event, &evtype) == SWITCH_STATUS_SUCCESS) { if (!zstr(script)) { if (switch_event_bind(modname, evtype, !zstr(subclass) ? subclass : SWITCH_EVENT_SUBCLASS_ANY, v8_event_handler, script) == SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "event handler for '%s' set to '%s'\n", switch_event_name(evtype), script); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "cannot set event handler: unsuccessful bind\n"); } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "cannot set event handler: no script name for event type '%s'\n", event); } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "cannot set event handler: unknown event type '%s'\n", event); } } } switch_xml_free(xml); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Open of %s failed\n", cf); } } static int env_init(JSMain *js) { /* Init all "global" functions first */ const js_function_t *fs_proc = FSGlobal::GetFunctionDefinitions(); for (int i = 0; fs_proc[i].name && fs_proc[i].func; i++) { js->AddJSExtenderFunction(fs_proc[i].func, fs_proc[i].name); } /* Init all basic classes made available from FreeSWITCH */ js->AddJSExtenderClass(FSSession::GetClassDefinition()); js->AddJSExtenderClass(FSFileIO::GetClassDefinition()); js->AddJSExtenderClass(FSEvent::GetClassDefinition()); js->AddJSExtenderClass(FSDTMF::GetClassDefinition()); js->AddJSExtenderClass(FSPCRE::GetClassDefinition()); /* To add a class that will always be available inside JS, add the definition here */ return 1; } static void v8_error(Isolate* isolate, TryCatch* try_catch) { HandleScope handle_scope(isolate); String::Utf8Value exception(try_catch->Exception()); const char *exception_string = js_safe_str(*exception); Handle message = try_catch->Message(); const char *msg = ""; string filename = __FILE__; int line = __LINE__; string text = ""; JSMain *js = JSMain::GetScriptInstanceFromIsolate(isolate); if (js && js->GetForcedTermination()) { js->ResetForcedTermination(); switch_log_printf(SWITCH_CHANNEL_ID_LOG, js->GetForcedTerminationScriptFile(), modname, js->GetForcedTerminationLineNumber(), NULL, SWITCH_LOG_NOTICE, "Script exited with info [%s]\n", js->GetForcedTerminationMessage()); return; } if (!message.IsEmpty()) { String::Utf8Value fname(message->GetScriptResourceName()); if (*fname) { filename = *fname; } line = message->GetLineNumber(); msg = exception_string; String::Utf8Value sourceline(message->GetSourceLine()); if (*sourceline) { text = *sourceline; } } else { msg = exception_string; } if (!msg) { msg = ""; } if (text.length() > 0) { switch_log_printf(SWITCH_CHANNEL_ID_LOG, filename.c_str(), modname, line, NULL, SWITCH_LOG_ERROR, "Exception: %s (near: \"%s\")\n", msg, text.c_str()); } else { switch_log_printf(SWITCH_CHANNEL_ID_LOG, filename.c_str(), modname, line, NULL, SWITCH_LOG_ERROR, "Exception: %s\n", msg); } } static char *v8_get_script_path(const char *script_file) { const char *p; char *ret = NULL; const char delims[] = "/\\"; const char *i; if (script_file) { for (i = delims; *i; i++) { if ((p = strrchr(script_file, *i))) { js_strdup(ret, script_file); *(ret + (p - script_file)) = '\0'; return ret; } } js_strdup(ret, "."); return ret; } else { return NULL; } } static int v8_parse_and_execute(switch_core_session_t *session, const char *input_code, switch_stream_handle_t *api_stream, v8_event_t *v8_event, v8_xml_handler_t* xml_handler) { string res; JSMain *js; Isolate *isolate; char *arg, *argv[512]; int argc = 0, x = 0, y = 0; unsigned int flags = 0; char *path = NULL; string result_string; int result = 0; if (zstr(input_code)) { return -1; } js = new JSMain(); isolate = js->GetIsolate(); env_init(js); /* Try to read lock the session first */ if (session) { if (switch_core_session_read_lock_hangup(session) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Read Lock Failure.\n"); session = NULL; } } /* Execute the actual script */ //isolate->Enter(); { Locker lock(isolate); Isolate::Scope iscope(isolate); { const char *script; // Create a stack-allocated handle scope. HandleScope scope(isolate); // Store our object internally isolate->SetData(0, js); // New global template Handle global = ObjectTemplate::New(); if (global.IsEmpty()) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create JS global object template\n"); } else { /* Function to print current V8 version */ global->Set(String::NewFromUtf8(isolate, "version"), FunctionTemplate::New(isolate, JSMain::Version)); /* Add all global functions */ for (size_t i = 0; i < js->GetExtenderFunctions().size(); i++) { js_function_t *proc = js->GetExtenderFunctions()[i]; global->Set(String::NewFromUtf8(isolate, proc->name), FunctionTemplate::New(isolate, proc->func)); } // Create a new context. Local context = Context::New(isolate, NULL, global); if (context.IsEmpty()) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create JS context\n"); } else { // Enter the created context for compiling and running the script. Context::Scope context_scope(context); #ifdef V8_ENABLE_DEBUGGING Persistent *debug_context = new Persistent(); isolate->SetData(1, debug_context); debug_context->Reset(isolate, context); //v8::Locker lck(isolate); if (debug_enable_callback) { Debug::SetDebugMessageDispatchHandler(V8DispatchDebugMessages, true); } if (debug_listen_port > 0) { char *name = switch_mprintf("mod_v8-%d", (int)switch_thread_self()); Debug::EnableAgent(name, debug_listen_port, debug_wait_for_connection); switch_safe_free(name); } #endif /* Register all plugin classes */ for (size_t i = 0; i < js->GetExtenderClasses().size(); i++) { JSBase::Register(isolate, js->GetExtenderClasses()[i]); } /* Register all instances of specific plugin classes */ for (size_t i = 0; i < js->GetExtenderInstances().size(); i++) { registered_instance_t *inst = js->GetExtenderInstances()[i]; inst->obj->RegisterInstance(isolate, inst->name, inst->auto_destroy); } /* Emaculent conception of session object into the script if one is available */ if (session) { FSSession *obj = new FSSession(js); obj->Init(session, flags); obj->RegisterInstance(isolate, "session", true); } else { /* Add a session object as a boolean instead, just to make it safe to check if it exists as expected */ context->Global()->Set(String::NewFromUtf8(isolate, "session"), Boolean::New(isolate, false)); } if (v8_event) { if (v8_event->event && v8_event->var_name) FSEvent::New(v8_event->event, v8_event->var_name, js); } if (api_stream) { /* The JS class "Request" is only needed when a api_stream object is passed here */ JSBase::Register(isolate, FSRequest::GetClassDefinition()); FSRequest *ptr = new FSRequest(js); ptr->Init(input_code, api_stream); ptr->RegisterInstance(isolate, "request", true); } if (xml_handler) { /* Add xml handler global variables */ Handle XML_REQUEST = Array::New(isolate, 4); XML_REQUEST->Set(String::NewFromUtf8(isolate, "key_name"), String::NewFromUtf8(isolate, js_safe_str(xml_handler->key_name))); XML_REQUEST->Set(String::NewFromUtf8(isolate, "key_value"), String::NewFromUtf8(isolate, js_safe_str(xml_handler->key_value))); XML_REQUEST->Set(String::NewFromUtf8(isolate, "section"), String::NewFromUtf8(isolate, js_safe_str(xml_handler->section))); XML_REQUEST->Set(String::NewFromUtf8(isolate, "tag_name"), String::NewFromUtf8(isolate, js_safe_str(xml_handler->tag_name))); context->Global()->Set(String::NewFromUtf8(isolate, "XML_REQUEST"), XML_REQUEST); if (xml_handler->params) { FSEvent::New(xml_handler->params, "params", js); } } script = input_code; if (*script != '~') { if ((arg = (char *)strchr(script, ' '))) { *arg++ = '\0'; argc = switch_separate_string(arg, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); } // Add arguments before running script. Local arguments = Array::New(isolate, argc); for (y = 0; y < argc; y++) { arguments->Set(Integer::New(isolate, y), String::NewFromUtf8(isolate, argv[y])); } context->Global()->Set(String::NewFromUtf8(isolate, "argv"), arguments); context->Global()->Set(String::NewFromUtf8(isolate, "argc"), Integer::New(isolate, argc)); } const char *script_data = NULL; const char *script_file = NULL; string s; if (*script == '~') { script_data = script + 1; script_file = "inline"; } else { const char *script_name = NULL; if (switch_is_file_path(script)) { script_name = script; } else if ((path = switch_mprintf("%s%s%s", SWITCH_GLOBAL_dirs.script_dir, SWITCH_PATH_SEPARATOR, script))) { script_name = path; } if (script_name) { if (JSMain::FileExists(script_name)) { s = JSMain::LoadFileToString(script_name); script_data = s.c_str(); script_file = script_name; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot Open File: %s\n", script_name); } } } if (!script_data) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No script to execute!\n"); } else { /* Store our base directoy in variable 'scriptPath' */ char *path = v8_get_script_path(script_file); if (path) { context->Global()->Set(String::NewFromUtf8(isolate, "scriptPath"), String::NewFromUtf8(isolate, path)); free(path); } // Create a string containing the JavaScript source code. Handle source = String::NewFromUtf8(isolate, script_data); TryCatch try_catch; // Compile the source code. Handle