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