1 /*
2  * mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3  * Copyright (C) 2013-2018, Grasshopper
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_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18  *
19  * The Initial Developer of the Original Code is Grasshopper
20  * Portions created by the Initial Developer are Copyright (C)
21  * the Initial Developer. All Rights Reserved.
22  *
23  * Contributor(s):
24  * Chris Rienzo <chris.rienzo@grasshopper.com>
25  *
26  * nlsml.c -- Parses / creates NLSML results
27  *
28  */
29 #include <switch.h>
30 #include <iksemel.h>
31 
32 #include "nlsml.h"
33 #include "iks_helpers.h"
34 
35 struct nlsml_parser;
36 
37 /** function to handle tag attributes */
38 typedef int (* tag_attribs_fn)(struct nlsml_parser *, char **);
39 /** function to handle tag CDATA */
40 typedef int (* tag_cdata_fn)(struct nlsml_parser *, char *, size_t);
41 
42 /**
43  * Tag definition
44  */
45 struct tag_def {
46 	tag_attribs_fn attribs_fn;
47 	tag_cdata_fn cdata_fn;
48 	switch_bool_t is_root;
49 	switch_hash_t *children_tags;
50 };
51 
52 /**
53  * library configuration
54  */
55 typedef struct {
56 	/** true if initialized */
57 	switch_bool_t init;
58 	/** Mapping of tag name to definition */
59 	switch_hash_t *tag_defs;
60 	/** library memory pool */
61 	switch_memory_pool_t *pool;
62 } nlsml_globals;
63 static nlsml_globals globals = { 0 };
64 
65 /**
66  * The node in the XML tree
67  */
68 struct nlsml_node {
69 	/** tag name */
70 	const char *name;
71 	/** tag definition */
72 	struct tag_def *tag_def;
73 	/** parent to this node */
74 	struct nlsml_node *parent;
75 };
76 
77 /**
78  * The SAX parser state
79  */
80 struct nlsml_parser {
81 	/** current node */
82 	struct nlsml_node *cur;
83 	/** optional UUID for logging */
84 	const char *uuid;
85 	/** true if a match exists */
86 	int match;
87 	/** true if noinput */
88 	int noinput;
89 	/** true if nomatch */
90 	int nomatch;
91 };
92 
93 /**
94  * Tag def destructor
95  */
destroy_tag_def(void * ptr)96 static void destroy_tag_def(void *ptr)
97 {
98     struct tag_def *tag = (struct tag_def *) ptr;
99 	if (tag->children_tags) {
100 		switch_core_hash_destroy(&tag->children_tags);
101 	}
102 }
103 
104 /**
105  * Add a definition for a tag
106  * @param tag the name
107  * @param attribs_fn the function to handle the tag attributes
108  * @param cdata_fn the function to handler the tag CDATA
109  * @param children_tags comma-separated list of valid child tag names
110  * @return the definition
111  */
add_tag_def(const char * tag,tag_attribs_fn attribs_fn,tag_cdata_fn cdata_fn,const char * children_tags)112 static struct tag_def *add_tag_def(const char *tag, tag_attribs_fn attribs_fn, tag_cdata_fn cdata_fn, const char *children_tags)
113 {
114 	struct tag_def *def = switch_core_alloc(globals.pool, sizeof(*def));
115 	switch_core_hash_init(&def->children_tags);
116 	if (!zstr(children_tags)) {
117 		char *children_tags_dup = switch_core_strdup(globals.pool, children_tags);
118 		char *tags[32] = { 0 };
119 		int tag_count = switch_separate_string(children_tags_dup, ',', tags, sizeof(tags) / sizeof(tags[0]));
120 		if (tag_count) {
121 			int i;
122 			for (i = 0; i < tag_count; i++) {
123 				switch_core_hash_insert(def->children_tags, tags[i], tags[i]);
124 			}
125 		}
126 	}
127 	def->attribs_fn = attribs_fn;
128 	def->cdata_fn = cdata_fn;
129 	def->is_root = SWITCH_FALSE;
130 	switch_core_hash_insert_destructor(globals.tag_defs, tag, def, destroy_tag_def);
131 	return def;
132 }
133 
134 /**
135  * Add a definition for a root tag
136  * @param tag the name
137  * @param attribs_fn the function to handle the tag attributes
138  * @param cdata_fn the function to handler the tag CDATA
139  * @param children_tags comma-separated list of valid child tag names
140  * @return the definition
141  */
add_root_tag_def(const char * tag,tag_attribs_fn attribs_fn,tag_cdata_fn cdata_fn,const char * children_tags)142 static struct tag_def *add_root_tag_def(const char *tag, tag_attribs_fn attribs_fn, tag_cdata_fn cdata_fn, const char *children_tags)
143 {
144 	struct tag_def *def = add_tag_def(tag, attribs_fn, cdata_fn, children_tags);
145 	def->is_root = SWITCH_TRUE;
146 	return def;
147 }
148 
149 /**
150  * Handle tag attributes
151  * @param parser the parser
152  * @param name the tag name
153  * @param atts the attributes
154  * @return IKS_OK if OK IKS_BADXML on parse failure
155  */
process_tag(struct nlsml_parser * parser,const char * name,char ** atts)156 static int process_tag(struct nlsml_parser *parser, const char *name, char **atts)
157 {
158 	struct nlsml_node *cur = parser->cur;
159 	if (cur->tag_def->is_root && cur->parent == NULL) {
160 		/* no parent for ROOT tags */
161 		return cur->tag_def->attribs_fn(parser, atts);
162 	} else if (!cur->tag_def->is_root && cur->parent) {
163 		/* check if this child is allowed by parent node */
164 		struct tag_def *parent_def = cur->parent->tag_def;
165 		if (switch_core_hash_find(parent_def->children_tags, "ANY") ||
166 			switch_core_hash_find(parent_def->children_tags, name)) {
167 			return cur->tag_def->attribs_fn(parser, atts);
168 		} else {
169 			switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_INFO, "<%s> cannot be a child of <%s>\n", name, cur->parent->name);
170 		}
171 	} else if (cur->tag_def->is_root && cur->parent != NULL) {
172 		switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_INFO, "<%s> must be the root element\n", name);
173 	} else {
174 		switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_INFO, "<%s> cannot be a root element\n", name);
175 	}
176 	return IKS_BADXML;
177 }
178 
179 /**
180  * Handle tag attributes that are ignored
181  * @param parser the parser
182  * @param atts the attributes
183  * @return IKS_OK
184  */
process_attribs_ignore(struct nlsml_parser * parser,char ** atts)185 static int process_attribs_ignore(struct nlsml_parser *parser, char **atts)
186 {
187 	return IKS_OK;
188 }
189 
190 /**
191  * Handle CDATA that is ignored
192  * @param parser the parser
193  * @param data the CDATA
194  * @param len the CDATA length
195  * @return IKS_OK
196  */
process_cdata_ignore(struct nlsml_parser * parser,char * data,size_t len)197 static int process_cdata_ignore(struct nlsml_parser *parser, char *data, size_t len)
198 {
199 	return IKS_OK;
200 }
201 
202 /**
203  * Handle CDATA that is not allowed
204  * @param parser the parser
205  * @param data the CDATA
206  * @param len the CDATA length
207  * @return IKS_BADXML if any printable characters
208  */
process_cdata_bad(struct nlsml_parser * parser,char * data,size_t len)209 static int process_cdata_bad(struct nlsml_parser *parser, char *data, size_t len)
210 {
211 	int i;
212 	for (i = 0; i < len; i++) {
213 		if (isgraph(data[i])) {
214 			switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_INFO, "Unexpected CDATA for <%s>\n", parser->cur->name);
215 			return IKS_BADXML;
216 		}
217 	}
218 	return IKS_OK;
219 }
220 
221 /**
222  * Handle CDATA with match text
223  * @param parser the parser
224  * @param data the CDATA
225  * @param len the CDATA length
226  * @return IKS_OK
227  */
process_cdata_match(struct nlsml_parser * parser,char * data,size_t len)228 static int process_cdata_match(struct nlsml_parser *parser, char *data, size_t len)
229 {
230 	int i;
231 	for (i = 0; i < len; i++) {
232 		if (isgraph(data[i])) {
233 			parser->match++;
234 			return IKS_OK;
235 		}
236 	}
237 	return IKS_OK;
238 }
239 
240 /**
241  * Handle nomatch
242  * @param parser the parser
243  * @param atts the attributes
244  * @return IKS_OK
245  */
process_nomatch(struct nlsml_parser * parser,char ** atts)246 static int process_nomatch(struct nlsml_parser *parser, char **atts)
247 {
248 	parser->nomatch++;
249 	return IKS_OK;
250 }
251 
252 /**
253  * Handle noinput
254  * @param parser the parser
255  * @param atts the attributes
256  * @return IKS_OK
257  */
process_noinput(struct nlsml_parser * parser,char ** atts)258 static int process_noinput(struct nlsml_parser *parser, char **atts)
259 {
260 	parser->noinput++;
261 	return IKS_OK;
262 }
263 
264 /**
265  * Process a tag
266  */
tag_hook(void * user_data,char * name,char ** atts,int type)267 static int tag_hook(void *user_data, char *name, char **atts, int type)
268 {
269 	int result = IKS_OK;
270 	struct nlsml_parser *parser = (struct nlsml_parser *)user_data;
271 
272 	if (type == IKS_OPEN || type == IKS_SINGLE) {
273 		struct nlsml_node *child_node = malloc(sizeof(*child_node));
274 		switch_assert(child_node);
275 		child_node->name = name;
276 		child_node->tag_def = switch_core_hash_find(globals.tag_defs, name);
277 		if (!child_node->tag_def) {
278 			child_node->tag_def = switch_core_hash_find(globals.tag_defs, "ANY");
279 		}
280 		child_node->parent = parser->cur;
281 		parser->cur = child_node;
282 		switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_DEBUG1, "<%s>\n", name);
283 		result = process_tag(parser, name, atts);
284 	}
285 
286 	if (type == IKS_CLOSE || type == IKS_SINGLE) {
287 		struct nlsml_node *node = parser->cur;
288 		parser->cur = node->parent;
289 		free(node);
290 		switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_DEBUG1, "</%s>\n", name);
291 	}
292 
293 	return result;
294 }
295 
296 /**
297  * Process cdata
298  * @param user_data the parser
299  * @param data the CDATA
300  * @param len the CDATA length
301  * @return IKS_OK
302  */
cdata_hook(void * user_data,char * data,size_t len)303 static int cdata_hook(void *user_data, char *data, size_t len)
304 {
305 	struct nlsml_parser *parser = (struct nlsml_parser *)user_data;
306 	if (!parser) {
307 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Missing parser\n");
308 		return IKS_BADXML;
309 	}
310 	if (parser->cur) {
311 		struct tag_def *def = parser->cur->tag_def;
312 		if (def) {
313 			return def->cdata_fn(parser, data, len);
314 		}
315 		switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_INFO, "Missing definition for <%s>\n", parser->cur->name);
316 		return IKS_BADXML;
317 	}
318 	return IKS_OK;
319 }
320 
321 /**
322  * Parse the result, looking for noinput/nomatch/match
323  * @param nlsml_result the NLSML result to parse
324  * @param uuid optional UUID for logging
325  * @return true if successful
326  */
nlsml_parse(const char * nlsml_result,const char * uuid)327 enum nlsml_match_type nlsml_parse(const char *nlsml_result, const char *uuid)
328 {
329 	struct nlsml_parser parser = { 0 };
330 	int result = NMT_BAD_XML;
331 	iksparser *p = NULL;
332 	parser.uuid = uuid;
333 
334 	if (!zstr(nlsml_result)) {
335 		p = iks_sax_new(&parser, tag_hook, cdata_hook);
336 		if (iks_parse(p, nlsml_result, 0, 1) == IKS_OK) {
337 			/* check result */
338 			if (parser.match) {
339 				result = NMT_MATCH;
340 				goto end;
341 			}
342 			if (parser.nomatch) {
343 				result = NMT_NOMATCH;
344 				goto end;
345 			}
346 			if (parser.noinput) {
347 				result = NMT_NOINPUT;
348 				goto end;
349 			}
350 			switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser.uuid), SWITCH_LOG_INFO, "NLSML result does not have match/noinput/nomatch!\n");
351 		} else {
352 			switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser.uuid), SWITCH_LOG_INFO, "Failed to parse NLSML!\n");
353 		}
354 	} else {
355 		switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser.uuid), SWITCH_LOG_INFO, "Missing NLSML result\n");
356 	}
357  end:
358 
359 	while (parser.cur) {
360 		struct nlsml_node *node = parser.cur;
361 		parser.cur = node->parent;
362 		free(node);
363 	}
364 
365 	if ( p ) {
366 		iks_parser_delete(p);
367 	}
368 	return result;
369 }
370 
371 #define NLSML_NS "http://www.ietf.org/xml/ns/mrcpv2"
372 
373 /**
374  * Makes NLSML result to conform to mrcpv2
375  * @param result the potentially non-conforming result
376  * @return the conforming result
377  */
nlsml_normalize(const char * result)378 iks *nlsml_normalize(const char *result)
379 {
380 	iks *result_xml = NULL;
381 	iksparser *p = iks_dom_new(&result_xml);
382 	if (iks_parse(p, result, 0, 1) == IKS_OK && result_xml) {
383 		/* for now, all that is needed is to set the proper namespace */
384 		iks_insert_attrib(result_xml, "xmlns", NLSML_NS);
385 	} else {
386 		/* unexpected ... */
387 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Failed to normalize NLSML result: %s\n", result);
388 		if (result_xml) {
389 			iks_delete(result_xml);
390 		}
391 	}
392 	iks_parser_delete(p);
393 	return result_xml;
394 }
395 
396 /**
397  * @return true if digit is a DTMF
398  */
isdtmf(const char digit)399 static int isdtmf(const char digit)
400 {
401 	switch(digit) {
402 	case '0':
403 	case '1':
404 	case '2':
405 	case '3':
406 	case '4':
407 	case '5':
408 	case '6':
409 	case '7':
410 	case '8':
411 	case '9':
412 	case '*':
413 	case '#':
414 	case 'a':
415 	case 'A':
416 	case 'b':
417 	case 'B':
418 	case 'c':
419 	case 'C':
420 	case 'd':
421 	case 'D':
422 		return 1;
423 	}
424 	return 0;
425 }
426 
427 /**
428  * Construct an NLSML result for match
429  * @param match the matching digits or text
430  * @param interpretation the optional digit interpretation
431  * @param mode dtmf or speech
432  * @param confidence 0-100
433  * @return the NLSML <result>
434  */
nlsml_create_match(const char * match,const char * interpretation,const char * mode,int confidence)435 iks *nlsml_create_match(const char *match, const char *interpretation, const char *mode, int confidence)
436 {
437 	iks *result = iks_new("result");
438 	iks_insert_attrib(result, "xmlns", NLSML_NS);
439 	iks_insert_attrib(result, "xmlns:xf", "http://www.w3.org/2000/xforms");
440 	if (!zstr(match)) {
441 		iks *interpretation_node = iks_insert(result, "interpretation");
442 		iks *input_node = iks_insert(interpretation_node, "input");
443 		iks *instance_node = iks_insert(interpretation_node, "instance");
444 		iks_insert_attrib(input_node, "mode", mode);
445 		iks_insert_attrib_printf(input_node, "confidence", "%d", confidence);
446 		iks_insert_cdata(input_node, match, strlen(match));
447 		if (zstr(interpretation)) {
448 			iks_insert_cdata(instance_node, match, strlen(match));
449 		} else {
450 			iks_insert_cdata(instance_node, interpretation, strlen(interpretation));
451 		}
452 	}
453 	return result;
454 }
455 
456 /**
457  * Construct an NLSML result for match
458  * @param match the matching digits or text
459  * @param interpretation the optional digit interpretation
460  * @return the NLSML <result>
461  */
nlsml_create_dtmf_match(const char * digits,const char * interpretation)462 iks *nlsml_create_dtmf_match(const char *digits, const char *interpretation)
463 {
464 	iks *result = NULL;
465 	int first = 1;
466 	int i;
467 	int num_digits = strlen(digits);
468 	switch_stream_handle_t stream = { 0 };
469 	SWITCH_STANDARD_STREAM(stream);
470 	for (i = 0; i < num_digits; i++) {
471 		if (isdtmf(digits[i])) {
472 			if (first) {
473 				stream.write_function(&stream, "%c", digits[i]);
474 				first = 0;
475 			} else {
476 				stream.write_function(&stream, " %c", digits[i]);
477 			}
478 		}
479 	}
480 	result = nlsml_create_match((const char *)stream.data, interpretation, "dtmf", 100);
481 	switch_safe_free(stream.data);
482 	return result;
483 }
484 
485 /**
486  * Initialize NLSML parser.  This function is not thread safe.
487  */
nlsml_init(void)488 int nlsml_init(void)
489 {
490 	if (globals.init) {
491 		return 1;
492 	}
493 
494 	globals.init = SWITCH_TRUE;
495 	switch_core_new_memory_pool(&globals.pool);
496 	switch_core_hash_init(&globals.tag_defs);
497 
498 	add_root_tag_def("result", process_attribs_ignore, process_cdata_ignore, "interpretation");
499 	add_tag_def("interpretation", process_attribs_ignore, process_cdata_ignore, "input,model,xf:model,instance,xf:instance");
500 	add_tag_def("input", process_attribs_ignore, process_cdata_match, "input,nomatch,noinput");
501 	add_tag_def("noinput", process_noinput, process_cdata_bad, "");
502 	add_tag_def("nomatch", process_nomatch, process_cdata_ignore, "");
503 	add_tag_def("model", process_attribs_ignore, process_cdata_ignore, "ANY");
504 	add_tag_def("xf:model", process_attribs_ignore, process_cdata_ignore, "ANY");
505 	add_tag_def("instance", process_attribs_ignore, process_cdata_ignore, "ANY");
506 	add_tag_def("xf:instance", process_attribs_ignore, process_cdata_ignore, "ANY");
507 	add_tag_def("ANY", process_attribs_ignore, process_cdata_ignore, "ANY");
508 
509 	return 1;
510 }
511 
512 /**
513  * Destruction of NLSML parser environment
514  */
nlsml_destroy(void)515 void nlsml_destroy(void)
516 {
517 	if (globals.init) {
518 		if (globals.tag_defs) {
519 			switch_core_hash_destroy(&globals.tag_defs);
520 			globals.tag_defs = NULL;
521 		}
522 		if (globals.pool) {
523 			switch_core_destroy_memory_pool(&globals.pool);
524 			globals.pool = NULL;
525 		}
526 		globals.init = SWITCH_FALSE;
527 	}
528 }
529 
530 /* For Emacs:
531  * Local Variables:
532  * mode:c
533  * indent-tabs-mode:t
534  * tab-width:4
535  * c-basic-offset:4
536  * End:
537  * For VIM:
538  * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet
539  */
540