1 /*
2  * mod_v8 for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3  * Copyright (C) 2013-2014, Peter Olsson <peter@olssononline.se>
4  *
5  * Version: MPL 1.1
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * The Original Code is mod_v8 for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18  *
19  * The Initial Developer of the Original Code is
20  * Peter Olsson <peter@olssononline.se>
21  * Portions created by the Initial Developer are Copyright (C)
22  * the Initial Developer. All Rights Reserved.
23  *
24  * Contributor(s):
25  * Peter Olsson <peter@olssononline.se>
26  *
27  * fseventhandler.cpp -- JavaScript EventHandler class
28  *
29  */
30 
31 #include "fseventhandler.hpp"
32 #include "fsevent.hpp"
33 #include "fssession.hpp"
34 
35 #define MAX_QUEUE_LEN 100000
36 
37 using namespace std;
38 using namespace v8;
39 
40 typedef struct {
41 	char *cmd;
42 	char *arg;
43 	char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1];
44 	int ack;
45 	switch_memory_pool_t *pool;
46 } api_command_struct_t;
47 
48 static const char js_class_name[] = "EventHandler";
49 
~FSEventHandler(void)50 FSEventHandler::~FSEventHandler(void)
51 {
52 	v8_remove_event_handler(this);
53 
54 	if (_event_hash) switch_core_hash_destroy(&_event_hash);
55 
56 	if (_event_queue) {
57 		void *pop;
58 
59 		while (switch_queue_trypop(_event_queue, &pop) == SWITCH_STATUS_SUCCESS) {
60 			switch_event_t *pevent = (switch_event_t *) pop;
61 			if (pevent) {
62 				switch_event_destroy(&pevent);
63 			}
64 		}
65 	}
66 
67 	if (_filters) switch_event_destroy(&_filters);
68 	if (_mutex) switch_mutex_destroy(_mutex);
69 	if (_pool) switch_core_destroy_memory_pool(&_pool);
70 }
71 
Init()72 void FSEventHandler::Init()
73 {
74 	if (switch_core_new_memory_pool(&_pool) != SWITCH_STATUS_SUCCESS) {
75 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "OH OH no pool\n");
76 		return;
77 	}
78 
79 	switch_mutex_init(&_mutex, SWITCH_MUTEX_NESTED, _pool);
80 	switch_core_hash_init(&_event_hash);
81 	switch_queue_create(&_event_queue, MAX_QUEUE_LEN, _pool);
82 
83 	_filters = NULL;
84 	memset(&_event_list, 0, sizeof(_event_list));
85 
86 	v8_add_event_handler(this);
87 }
88 
GetJSClassName()89 string FSEventHandler::GetJSClassName()
90 {
91 	return js_class_name;
92 }
93 
QueueEvent(switch_event_t * event)94 void FSEventHandler::QueueEvent(switch_event_t *event)
95 {
96 	switch_event_t *clone;
97 	int send = 0;
98 
99 	switch_mutex_lock(_mutex);
100 
101 	if (_event_list[SWITCH_EVENT_ALL]) {
102 		send = 1;
103 	} else if ((_event_list[event->event_id])) {
104 		if (event->event_id != SWITCH_EVENT_CUSTOM || !event->subclass_name || (switch_core_hash_find(_event_hash, event->subclass_name))) {
105 			send = 1;
106 		}
107 	}
108 
109 	if (send) {
110 		if (_filters && _filters->headers) {
111 			switch_event_header_t *hp;
112 			const char *hval;
113 
114 			send = 0;
115 
116 			for (hp = _filters->headers; hp; hp = hp->next) {
117 				if ((hval = switch_event_get_header(event, hp->name))) {
118 					const char *comp_to = hp->value;
119 					int pos = 1, cmp = 0;
120 
121 					while (comp_to && *comp_to) {
122 						if (*comp_to == '+') {
123 							pos = 1;
124 						} else if (*comp_to == '-') {
125 							pos = 0;
126 						} else if (*comp_to != ' ') {
127 							break;
128 						}
129 						comp_to++;
130 					}
131 
132 					if (send && pos) {
133 						continue;
134 					}
135 
136 					if (!comp_to) {
137 						continue;
138 					}
139 
140 					if (*hp->value == '/') {
141 						switch_regex_t *re = NULL;
142 						int ovector[30];
143 						cmp = !!switch_regex_perform(hval, comp_to, &re, ovector, sizeof(ovector) / sizeof(ovector[0]));
144 						switch_regex_safe_free(re);
145 					} else {
146 						cmp = !strcasecmp(hval, comp_to);
147 					}
148 
149 					if (cmp) {
150 						if (pos) {
151 							send = 1;
152 						} else {
153 							send = 0;
154 							break;
155 						}
156 					}
157 				}
158 			}
159 		}
160 	}
161 
162 	switch_mutex_unlock(_mutex);
163 
164 	if (send) {
165 		if (switch_event_dup(&clone, event) == SWITCH_STATUS_SUCCESS) {
166 			if (switch_queue_trypush(_event_queue, clone) != SWITCH_STATUS_SUCCESS) {
167 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Lost event to JS EventHandler, you must read the events faster!\n");
168 				switch_event_destroy(&clone);
169 			}
170 		} else {
171 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory Error!\n");
172 		}
173 	}
174 }
175 
176 static const char *MARKER = "1";
177 
DoSubscribe(const v8::FunctionCallbackInfo<v8::Value> & info)178 void FSEventHandler::DoSubscribe(const v8::FunctionCallbackInfo<v8::Value>& info)
179 {
180 	int i, custom = 0;
181 	bool ret = false;
182 
183 	for (i = 0; i < info.Length(); i++) {
184 		String::Utf8Value str(info[i]);
185 		switch_event_types_t etype;
186 
187 		if (custom) {
188 			switch_mutex_lock(_mutex);
189 			switch_core_hash_insert(_event_hash, js_safe_str(*str), MARKER);
190 			switch_mutex_unlock(_mutex);
191 		} else if (switch_name_event(js_safe_str(*str), &etype) == SWITCH_STATUS_SUCCESS) {
192 			ret = true;
193 
194 			if (etype == SWITCH_EVENT_ALL) {
195 				uint32_t x = 0;
196 				for (x = 0; x < SWITCH_EVENT_ALL; x++) {
197 					_event_list[x] = 1;
198 				}
199 			}
200 
201 			if (etype <= SWITCH_EVENT_ALL) {
202 				_event_list[etype] = 1;
203 			}
204 
205 			if (etype == SWITCH_EVENT_CUSTOM) {
206 				custom++;
207 			}
208 		}
209 	}
210 
211 	info.GetReturnValue().Set(ret);
212 }
213 
Construct(const v8::FunctionCallbackInfo<v8::Value> & info)214 void *FSEventHandler::Construct(const v8::FunctionCallbackInfo<v8::Value>& info)
215 {
216 	FSEventHandler *obj = new FSEventHandler(info);
217 	obj->DoSubscribe(info);
218 	return obj;
219 }
220 
JS_EVENTHANDLER_FUNCTION_IMPL(Subscribe)221 JS_EVENTHANDLER_FUNCTION_IMPL(Subscribe)
222 {
223 	DoSubscribe(info);
224 }
225 
JS_EVENTHANDLER_FUNCTION_IMPL(UnSubscribe)226 JS_EVENTHANDLER_FUNCTION_IMPL(UnSubscribe)
227 {
228 	int i, custom = 0;
229 	bool ret = false;
230 
231 	for (i = 0; i < info.Length(); i++) {
232 		String::Utf8Value str(info[i]);
233 		switch_event_types_t etype;
234 
235 		if (custom) {
236 			switch_mutex_lock(_mutex);
237 			switch_core_hash_delete(_event_hash, js_safe_str(*str));
238 			switch_mutex_unlock(_mutex);
239 		} else if (switch_name_event(js_safe_str(*str), &etype) == SWITCH_STATUS_SUCCESS) {
240 			uint32_t x = 0;
241 			ret = true;
242 
243 			if (etype == SWITCH_EVENT_CUSTOM) {
244 				custom++;
245 			} else if (etype == SWITCH_EVENT_ALL) {
246 				for (x = 0; x <= SWITCH_EVENT_ALL; x++) {
247 					_event_list[x] = 0;
248 				}
249 			} else {
250 				if (_event_list[SWITCH_EVENT_ALL]) {
251 					_event_list[SWITCH_EVENT_ALL] = 0;
252 					for (x = 0; x < SWITCH_EVENT_ALL; x++) {
253 						_event_list[x] = 1;
254 					}
255 				}
256 				_event_list[etype] = 0;
257 			}
258 		}
259 	}
260 
261 	info.GetReturnValue().Set(ret);
262 }
263 
JS_EVENTHANDLER_FUNCTION_IMPL(DeleteFilter)264 JS_EVENTHANDLER_FUNCTION_IMPL(DeleteFilter)
265 {
266 	if (info.Length() < 1) {
267 		info.GetReturnValue().Set(false);
268 	} else {
269 		String::Utf8Value str(info[0]);
270 		const char *headerName = js_safe_str(*str);
271 
272 		if (zstr(headerName)) {
273 			info.GetReturnValue().Set(false);
274 			return;
275 		}
276 
277 		switch_mutex_lock(_mutex);
278 
279 		if (!_filters) {
280 			switch_event_create_plain(&_filters, SWITCH_EVENT_CLONE);
281 		}
282 
283 		if (!strcasecmp(headerName, "all")) {
284 			switch_event_destroy(&_filters);
285 			switch_event_create_plain(&_filters, SWITCH_EVENT_CLONE);
286 		} else {
287 			switch_event_del_header(_filters, headerName);
288 		}
289 
290 		info.GetReturnValue().Set(true);
291 
292 		switch_mutex_unlock(_mutex);
293 	}
294 }
295 
JS_EVENTHANDLER_FUNCTION_IMPL(AddFilter)296 JS_EVENTHANDLER_FUNCTION_IMPL(AddFilter)
297 {
298 	if (info.Length() < 2) {
299 		info.GetReturnValue().Set(false);
300 	} else {
301 		String::Utf8Value str1(info[0]);
302 		String::Utf8Value str2(info[1]);
303 		const char *headerName = js_safe_str(*str1);
304 		const char *headerVal = js_safe_str(*str2);
305 
306 		if (zstr(headerName) || zstr(headerVal)) {
307 			info.GetReturnValue().Set(false);
308 			return;
309 		}
310 
311 		switch_mutex_lock(_mutex);
312 
313 		if (!_filters) {
314 			switch_event_create_plain(&_filters, SWITCH_EVENT_CLONE);
315 		}
316 
317 		switch_event_add_header_string(_filters, SWITCH_STACK_BOTTOM, headerName, headerVal);
318 
319 		info.GetReturnValue().Set(true);
320 
321 		switch_mutex_unlock(_mutex);
322 	}
323 }
324 
JS_EVENTHANDLER_FUNCTION_IMPL(GetEvent)325 JS_EVENTHANDLER_FUNCTION_IMPL(GetEvent)
326 {
327 	void *pop = NULL;
328 	int timeout = 0;
329 	switch_event_t *pevent = NULL;
330 
331 	if (info.Length() > 0 && !info[0].IsEmpty()) {
332 		timeout = info[0]->Int32Value();
333 	}
334 
335 	if (timeout > 0) {
336 		if (switch_queue_pop_timeout(_event_queue, &pop, (switch_interval_time_t) timeout * 1000) == SWITCH_STATUS_SUCCESS && pop) {
337 			pevent = (switch_event_t *) pop;
338 		}
339 	} else if (timeout < 0) {
340 		if (switch_queue_pop(_event_queue, &pop) == SWITCH_STATUS_SUCCESS && pop) {
341 			pevent = (switch_event_t *) pop;
342 		}
343 	} else {
344 		if (switch_queue_trypop(_event_queue, &pop) == SWITCH_STATUS_SUCCESS && pop) {
345 			pevent = (switch_event_t *) pop;
346 		}
347 	}
348 
349 	if (pevent) {
350 		FSEvent *evt = new FSEvent(info);
351 		evt->SetEvent(pevent, 0);
352 		evt->RegisterInstance(info.GetIsolate(), "", true);
353 		info.GetReturnValue().Set(evt->GetJavaScriptObject());
354 	} else {
355 		info.GetReturnValue().Set(Null(info.GetIsolate()));
356 	}
357 }
358 
JS_EVENTHANDLER_FUNCTION_IMPL(SendEvent)359 JS_EVENTHANDLER_FUNCTION_IMPL(SendEvent)
360 {
361 	if (info.Length() == 0) {
362 		info.GetReturnValue().Set(false);
363 	} else {
364 		if (!info[0].IsEmpty() && info[0]->IsObject()) {
365 			FSEvent *evt = JSBase::GetInstance<FSEvent>(info[0]->ToObject());
366 			switch_event_t **event;
367 
368 			if (!evt || !(event = evt->GetEvent())) {
369 				info.GetReturnValue().Set(false);
370 			} else {
371 				string session_uuid;
372 
373 				if (info.Length() > 1) {
374 					if (!info[1].IsEmpty() && info[1]->IsObject()) {
375 						/* The second argument is a session object */
376 						FSSession *sess = JSBase::GetInstance<FSSession>(info[1]->ToObject());
377 						switch_core_session_t *tmp;
378 
379 						if (sess && (tmp = sess->GetSession())) {
380 							session_uuid = switch_core_session_get_uuid(tmp);
381 						}
382 					} else {
383 						/* The second argument is a session uuid string */
384 						String::Utf8Value str(info[1]);
385 						session_uuid = js_safe_str(*str);
386 					}
387 				}
388 
389 				if (session_uuid.length() > 0) {
390 					/* This is a session event */
391 					switch_core_session_t *session;
392 					switch_status_t status = SWITCH_STATUS_FALSE;
393 
394 					if ((session = switch_core_session_locate(session_uuid.c_str()))) {
395 						if ((status = switch_core_session_queue_private_event(session, event, SWITCH_FALSE)) == SWITCH_STATUS_SUCCESS) {
396 							info.GetReturnValue().Set(true);
397 						} else {
398 							info.GetReturnValue().Set(false);
399 						}
400 						switch_core_session_rwunlock(session);
401 					} else {
402 						info.GetReturnValue().Set(false);
403 						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid session id [%s]\n", switch_str_nil(session_uuid.c_str()));
404 					}
405 				} else {
406 					/* "Normal" event */
407 					switch_event_fire(event);
408 				}
409 			}
410 		}
411 	}
412 }
413 
JS_EVENTHANDLER_FUNCTION_IMPL(ExecuteApi)414 JS_EVENTHANDLER_FUNCTION_IMPL(ExecuteApi)
415 {
416 	if (info.Length() > 0) {
417 		String::Utf8Value str(info[0]);
418 		const char *cmd = js_safe_str(*str);
419 		string arg;
420 		switch_stream_handle_t stream = { 0 };
421 
422 		if (!strcasecmp(cmd, "jsapi")) {
423 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Possible recursive API Call is not allowed\n");
424 			info.GetReturnValue().Set(false);
425 			return;
426 		}
427 
428 		if (info.Length() > 1) {
429 			String::Utf8Value str2(info[1]);
430 			arg = js_safe_str(*str2);
431 		}
432 
433 		SWITCH_STANDARD_STREAM(stream);
434 		switch_api_execute(cmd, arg.c_str(), NULL, &stream);
435 
436 		info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), switch_str_nil((char *) stream.data)));
437 		switch_safe_free(stream.data);
438 	} else {
439 		info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), "-ERR"));
440 	}
441 }
442 
api_exec(switch_thread_t * thread,void * obj)443 static void *SWITCH_THREAD_FUNC api_exec(switch_thread_t *thread, void *obj)
444 {
445 	api_command_struct_t *acs = (api_command_struct_t *) obj;
446 	switch_stream_handle_t stream = { 0 };
447 	char *reply, *freply = NULL;
448 	switch_status_t status;
449 	switch_event_t *event;
450 
451 	if (!acs) {
452 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Internal error.\n");
453 		return NULL;
454 	}
455 
456 	acs->ack = 1;
457 
458 	SWITCH_STANDARD_STREAM(stream);
459 
460 	status = switch_api_execute(acs->cmd, acs->arg, NULL, &stream);
461 
462 	if (status == SWITCH_STATUS_SUCCESS) {
463 		reply = (char *)stream.data;
464 	} else {
465 		freply = switch_mprintf("-ERR %s Command not found!\n", acs->cmd);
466 		reply = freply;
467 	}
468 
469 	if (!reply) {
470 		reply = (char *)"Command returned no output!";
471 	}
472 
473 	if (switch_event_create(&event, SWITCH_EVENT_BACKGROUND_JOB) == SWITCH_STATUS_SUCCESS) {
474 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Job-UUID", acs->uuid_str);
475 		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Job-Command", acs->cmd);
476 		if (acs->arg) {
477 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Job-Command-Arg", acs->arg);
478 		}
479 		switch_event_add_body(event, "%s", reply);
480 		switch_event_fire(&event);
481 	}
482 
483 	switch_safe_free(stream.data);
484 	switch_safe_free(freply);
485 
486 	switch_memory_pool_t *pool = acs->pool;
487 	if (acs->ack == -1) {
488 		int sanity = 2000;
489 		while (acs->ack == -1) {
490 			switch_cond_next();
491 			if (--sanity <= 0)
492 				break;
493 		}
494 	}
495 
496 	acs = NULL;
497 	switch_core_destroy_memory_pool(&pool);
498 	pool = NULL;
499 
500 	return NULL;
501 }
502 
JS_EVENTHANDLER_FUNCTION_IMPL(ExecuteBgApi)503 JS_EVENTHANDLER_FUNCTION_IMPL(ExecuteBgApi)
504 {
505 	string cmd;
506 	string arg;
507 	string jobuuid;
508 	api_command_struct_t *acs = NULL;
509 	switch_memory_pool_t *pool;
510 	switch_thread_t *thread;
511 	switch_threadattr_t *thd_attr = NULL;
512 	switch_uuid_t uuid;
513 	int sanity = 2000;
514 
515 	if (info.Length() > 0) {
516 		String::Utf8Value str(info[0]);
517 		cmd = js_safe_str(*str);
518 
519 		if (info.Length() > 1) {
520 			String::Utf8Value str2(info[1]);
521 			arg = js_safe_str(*str2);
522 		}
523 
524 		if (info.Length() > 2) {
525 			String::Utf8Value str2(info[2]);
526 			jobuuid = js_safe_str(*str2);
527 		}
528 	} else {
529 		info.GetReturnValue().Set(false);
530 		return;
531 	}
532 
533 	if (cmd.length() == 0) {
534 		info.GetReturnValue().Set(false);
535 		return;
536 	}
537 
538 	switch_core_new_memory_pool(&pool);
539 	acs = (api_command_struct_t *)switch_core_alloc(pool, sizeof(*acs));
540 	switch_assert(acs);
541 	acs->pool = pool;
542 
543 	acs->cmd = switch_core_strdup(acs->pool, cmd.c_str());
544 
545 	if (arg.c_str()) {
546 		acs->arg = switch_core_strdup(acs->pool, arg.c_str());
547 	}
548 
549 	switch_threadattr_create(&thd_attr, acs->pool);
550 	switch_threadattr_detach_set(thd_attr, 1);
551 	switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
552 
553 	if (jobuuid.length() > 0) {
554 		switch_copy_string(acs->uuid_str, jobuuid.c_str(), sizeof(acs->uuid_str));
555 	} else {
556 		switch_uuid_get(&uuid);
557 		switch_uuid_format(acs->uuid_str, &uuid);
558 	}
559 
560 	info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), acs->uuid_str));
561 
562 	switch_thread_create(&thread, thd_attr, api_exec, acs, acs->pool);
563 
564 	while (!acs->ack) {
565 		switch_cond_next();
566 		if (--sanity <= 0)
567 			break;
568 	}
569 
570 	if (acs->ack == -1) {
571 		acs->ack--;
572 	}
573 }
574 
JS_EVENTHANDLER_FUNCTION_IMPL_STATIC(Destroy)575 JS_EVENTHANDLER_FUNCTION_IMPL_STATIC(Destroy)
576 {
577 	JS_CHECK_SCRIPT_STATE();
578 
579 	FSEventHandler *obj = JSBase::GetInstance<FSEventHandler>(info.Holder());
580 
581 	if (obj) {
582 		delete obj;
583 		info.GetReturnValue().Set(true);
584 	} else {
585 		info.GetReturnValue().Set(false);
586 	}
587 }
588 
JS_EVENTHANDLER_GET_PROPERTY_IMPL_STATIC(GetReadyProperty)589 JS_EVENTHANDLER_GET_PROPERTY_IMPL_STATIC(GetReadyProperty)
590 {
591 	JS_CHECK_SCRIPT_STATE();
592 
593 	FSEventHandler *obj = JSBase::GetInstance<FSEventHandler>(info.Holder());
594 
595 	if (obj) {
596 		info.GetReturnValue().Set(true);
597 	} else {
598 		info.GetReturnValue().Set(false);
599 	}
600 }
601 
602 static const js_function_t eventhandler_methods[] = {
603 	{"subscribe", FSEventHandler::Subscribe},
604 	{"unSubscribe", FSEventHandler::UnSubscribe},
605 	{"addFilter", FSEventHandler::AddFilter},
606 	{"deleteFilter", FSEventHandler::DeleteFilter},
607 	{"getEvent", FSEventHandler::GetEvent},
608 	{"sendEvent", FSEventHandler::SendEvent},
609 	{"executeApi", FSEventHandler::ExecuteApi},
610 	{"executeBgApi", FSEventHandler::ExecuteBgApi},
611 	{"destroy", FSEventHandler::Destroy},
612 	{0}
613 };
614 
615 static const js_property_t eventhandler_props[] = {
616 	{"ready", FSEventHandler::GetReadyProperty, JSBase::DefaultSetProperty},
617 	{0}
618 };
619 
620 static const js_class_definition_t eventhandler_desc = {
621 	js_class_name,
622 	FSEventHandler::Construct,
623 	eventhandler_methods,
624 	eventhandler_props
625 };
626 
eventhandler_load(const v8::FunctionCallbackInfo<Value> & info)627 static switch_status_t eventhandler_load(const v8::FunctionCallbackInfo<Value>& info)
628 {
629 	JSBase::Register(info.GetIsolate(), &eventhandler_desc);
630 	return SWITCH_STATUS_SUCCESS;
631 }
632 
633 static const v8_mod_interface_t eventhandler_module_interface = {
634 	/*.name = */ js_class_name,
635 	/*.js_mod_load */ eventhandler_load
636 };
637 
GetModuleInterface()638 const v8_mod_interface_t *FSEventHandler::GetModuleInterface()
639 {
640 	return &eventhandler_module_interface;
641 }
642 
643 /* For Emacs:
644  * Local Variables:
645  * mode:c
646  * indent-tabs-mode:t
647  * tab-width:4
648  * c-basic-offset:4
649  * End:
650  * For VIM:
651  * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
652  */
653