Asterisk - The Open Source Telephony Project  21.4.1
func_json.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2021-2022, Naveen Albert
5  *
6  * See http://www.asterisk.org for more information about
7  * the Asterisk project. Please do not directly contact
8  * any of the maintainers of this project for assistance;
9  * the project provides a web site, mailing lists and IRC
10  * channels for your use.
11  *
12  * This program is free software, distributed under the terms of
13  * the GNU General Public License Version 2. See the LICENSE file
14  * at the top of the source tree.
15  */
16 
17 /*! \file
18  *
19  * \brief JSON parsing function
20  *
21  * \author Naveen Albert <asterisk@phreaknet.org>
22  *
23  * \ingroup functions
24  */
25 
26 /*** MODULEINFO
27  <support_level>extended</support_level>
28  ***/
29 
30 #include "asterisk.h"
31 
32 #include "asterisk/module.h"
33 #include "asterisk/channel.h"
34 #include "asterisk/pbx.h"
35 #include "asterisk/utils.h"
36 #include "asterisk/test.h"
37 #include "asterisk/app.h"
38 #include "asterisk/conversions.h"
39 
40 /*** DOCUMENTATION
41  <function name="JSON_DECODE" language="en_US">
42  <since>
43  <version>16.24.0</version>
44  <version>18.10.0</version>
45  <version>19.2.0</version>
46  </since>
47  <synopsis>
48  Returns the string value of a JSON object key from a string containing a
49  JSON array.
50  </synopsis>
51  <syntax>
52  <parameter name="varname" required="true">
53  <para>The name of the variable containing the JSON string to parse.</para>
54  </parameter>
55  <parameter name="item" required="true">
56  <para>The name of the key whose value to return.</para>
57  <para>Multiple keys can be listed separated by a hierarchy delimeter, which will recursively index into a nested JSON string to retrieve a specific subkey's value.</para>
58  </parameter>
59  <parameter name="separator" required="false">
60  <para>A single character that delimits a key hierarchy for nested indexing. Default is a period (.)</para>
61  <para>This value should not appear in the key or hierarchy of keys itself, except to delimit the hierarchy of keys.</para>
62  </parameter>
63  <parameter name="options" required="no">
64  <optionlist>
65  <option name="c">
66  <para>For keys that reference a JSON array, return
67  the number of items in the array.</para>
68  <para>This option has no effect on any other type
69  of value.</para>
70  </option>
71  </optionlist>
72  </parameter>
73  </syntax>
74  <description>
75  <para>The JSON_DECODE function retrieves the value of the given variable name
76  and parses it as JSON, returning the value at a specified key. If the key cannot
77  be found, an empty string is returned.</para>
78  </description>
79  <see-also>
80  <ref type="function">CURL</ref>
81  </see-also>
82  </function>
83  ***/
84 
85 AST_THREADSTORAGE(result_buf);
86 
87 enum json_option_flags {
88  OPT_COUNT = (1 << 0),
89 };
90 
91 AST_APP_OPTIONS(json_options, {
92  AST_APP_OPTION('c', OPT_COUNT),
93 });
94 
95 #define MAX_JSON_STACK 32
96 
97 static int parse_node(char **key, char *currentkey, char *nestchar, int count, struct ast_json *json, char *buf, size_t len, int *depth)
98 {
99  const char *result = NULL;
100  char *previouskey;
101  struct ast_json *jsonval = json;
102 
103  /* Prevent a huge JSON string from blowing the stack. */
104  (*depth)++;
105  if (*depth > MAX_JSON_STACK) {
106  ast_log(LOG_WARNING, "Max JSON stack (%d) exceeded\n", MAX_JSON_STACK);
107  return -1;
108  }
109 
110  snprintf(buf, len, "%s", ""); /* clear the buffer from previous round if necessary */
111  if (!json) { /* no error or warning should be thrown */
112  ast_debug(1, "Could not find key '%s' in parsed JSON\n", currentkey);
113  return -1;
114  }
115 
116  switch(ast_json_typeof(jsonval)) {
117  unsigned long int size;
118  int r;
119  double d;
120 
121  case AST_JSON_STRING:
122  result = ast_json_string_get(jsonval);
123  ast_debug(1, "Got JSON string: %s\n", result);
124  ast_copy_string(buf, result, len);
125  break;
126  case AST_JSON_INTEGER:
127  r = ast_json_integer_get(jsonval);
128  ast_debug(1, "Got JSON integer: %d\n", r);
129  snprintf(buf, len, "%d", r); /* the snprintf below is mutually exclusive with this one */
130  break;
131  case AST_JSON_REAL:
132  d = ast_json_real_get(jsonval);
133  ast_debug(1, "Got JSON real: %.17g\n", d);
134  snprintf(buf, len, "%.17g", d); /* the snprintf below is mutually exclusive with this one */
135  break;
136  case AST_JSON_ARRAY:
137  ast_debug(1, "Got JSON array\n");
138  previouskey = currentkey;
139  currentkey = strsep(key, nestchar); /* retrieve the desired index */
140  size = ast_json_array_size(jsonval);
141  ast_debug(1, "Parsed JSON array of size %lu, key: %s\n", size, currentkey);
142  if (!currentkey) { /* this is the end, so just dump the array */
143  if (count) {
144  ast_debug(1, "No key on which to index in the array, so returning count: %lu\n", size);
145  snprintf(buf, len, "%lu", size);
146  return 0;
147  } else {
148  char *result2 = ast_json_dump_string(jsonval);
149  ast_debug(1, "No key on which to index in the array, so dumping '%s' array\n", previouskey);
150  ast_copy_string(buf, result2, len);
151  ast_json_free(result2);
152  }
153  } else if (ast_str_to_int(currentkey, &r) || r < 0) {
154  ast_debug(1, "Requested index '%s' is not numeric or is invalid\n", currentkey);
155  } else if (r >= size) {
156  ast_debug(1, "Requested index '%d' does not exist in parsed array\n", r);
157  } else {
158  ast_debug(1, "Recursing on index %d in array\n", r);
159  if (parse_node(key, currentkey, nestchar, count, ast_json_array_get(jsonval, r), buf, len, depth)) { /* recurse on this node */
160  return -1;
161  }
162  }
163  break;
164  case AST_JSON_TRUE:
165  case AST_JSON_FALSE:
166  r = ast_json_is_true(jsonval);
167  ast_debug(1, "Got JSON %s for key %s\n", r ? "true" : "false", currentkey);
168  snprintf(buf, len, "%d", r); /* the snprintf below is mutually exclusive with this one */
169  break;
170  case AST_JSON_NULL:
171  ast_debug(1, "Got JSON null for key %s\n", currentkey);
172  break;
173  case AST_JSON_OBJECT:
174  ast_debug(1, "Got generic JSON object for key %s\n", currentkey);
175  previouskey = currentkey;
176  currentkey = strsep(key, nestchar); /* retrieve the desired index */
177  if (!currentkey) { /* this is the end, so just dump the object */
178  char *result2 = ast_json_dump_string(jsonval);
179  ast_copy_string(buf, result2, len);
180  ast_json_free(result2);
181  } else {
182  ast_debug(1, "Recursing on object (key was '%s' and is now '%s')\n", previouskey, currentkey);
183  if (parse_node(key, currentkey, nestchar, count, ast_json_object_get(jsonval, currentkey), buf, len, depth)) { /* recurse on this node */
184  return -1;
185  }
186  }
187  break;
188  default:
189  ast_log(LOG_WARNING, "Got unsuported type %d\n", ast_json_typeof(jsonval));
190  return -1;
191  }
192  return 0;
193 }
194 
195 static int json_decode_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
196 {
197  int count = 0;
198  struct ast_flags flags = {0};
199  struct ast_json *json = NULL, *start = NULL;
200  char *nestchar = "."; /* default delimeter for nesting key indexing is . */
201  int index, res, depth = 0;
202 
204  AST_APP_ARG(varname);
205  AST_APP_ARG(key);
206  AST_APP_ARG(nestchar);
207  AST_APP_ARG(options);
208  );
209  char *varsubst, *key, *currentkey, *nextkey, *firstkey, *tmp;
210  struct ast_str *str = ast_str_thread_get(&result_buf, 16);
211 
212  AST_STANDARD_APP_ARGS(args, data);
213 
214  if (!ast_strlen_zero(args.options)) {
215  ast_app_parse_options(json_options, &flags, NULL, args.options);
216  if (ast_test_flag(&flags, OPT_COUNT)) {
217  count = 1;
218  }
219  }
220 
221  if (ast_strlen_zero(args.varname)) {
222  ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
223  return -1;
224  }
225 
226  if (ast_strlen_zero(args.key)) {
227  ast_log(LOG_WARNING, "%s requires a key\n", cmd);
228  return -1;
229  }
230 
231  key = ast_strdupa(args.key);
232  if (!ast_strlen_zero(args.nestchar)) {
233  int seplen = strlen(args.nestchar);
234  if (seplen != 1) {
235  ast_log(LOG_WARNING, "Nesting separator '%s' has length %d and is invalid (must be a single character)\n", args.nestchar, seplen);
236  } else {
237  nestchar = args.nestchar;
238  }
239  }
240 
241  varsubst = ast_alloca(strlen(args.varname) + 4); /* +4 for ${} and null terminator */
242  if (!varsubst) {
243  ast_log(LOG_ERROR, "Failed to allocate string\n");
244  return -1;
245  }
246  sprintf(varsubst, "${%s}", args.varname); /* safe, because of the above allocation */
247  ast_str_substitute_variables(&str, 0, chan, varsubst);
248 
249  ast_debug(1, "Parsing JSON using nesting delimeter '%s'\n", nestchar);
250 
251  if (ast_str_strlen(str) == 0) {
252  ast_debug(1, "Variable '%s' contains no data, nothing to search!\n", args.varname);
253  return -1; /* empty json string */
254  }
255 
256  /* allow for multiple key nesting */
257  currentkey = key;
258  firstkey = ast_strdupa(currentkey);
259  tmp = strstr(firstkey, nestchar);
260  if (tmp) {
261  *tmp = '\0';
262  }
263 
264  /* parse a string as JSON */
265  ast_debug(1, "Parsing JSON: %s (key: '%s')\n", ast_str_buffer(str), currentkey);
266  if (ast_strlen_zero(currentkey)) {
267  ast_debug(1, "Empty JSON key\n");
268  return -1;
269  }
270  if (ast_str_strlen(str) == 0) {
271  ast_debug(1, "JSON node '%s', contains no data, nothing to search!\n", currentkey);
272  return -1; /* empty json string */
273  }
274 
275  json = ast_json_load_str(str, NULL);
276  if (!json) {
277  ast_log(LOG_WARNING, "Failed to parse as JSON: %s\n", ast_str_buffer(str));
278  return -1;
279  }
280 
281  /* parse the JSON object, potentially recursively */
282  nextkey = strsep(&key, nestchar);
283  if (ast_json_is_object(json)) {
284  start = ast_json_object_get(json, firstkey);
285  } else {
286  if (ast_str_to_int(currentkey, &index)) {
287  ast_debug(1, "Requested index '%s' is not numeric or is invalid\n", currentkey);
288  return -1;
289  }
290  start = ast_json_array_get(json, index);
291  }
292 
293  res = parse_node(&key, nextkey, nestchar, count, start, buf, len, &depth);
294  ast_json_unref(json);
295  return res;
296 }
297 
298 static struct ast_custom_function json_decode_function = {
299  .name = "JSON_DECODE",
300  .read = json_decode_read,
301 };
302 
303 #ifdef TEST_FRAMEWORK
304 AST_TEST_DEFINE(test_JSON_DECODE)
305 {
306  int i, res = AST_TEST_PASS;
307  struct ast_channel *chan; /* dummy channel */
308  struct ast_str *str; /* fancy string for holding comparing value */
309 
310  const char *test_strings[][6] = {
311  {"{\"myboolean\": true, \"state\": \"USA\"}", "", "myboolean", "1"},
312  {"{\"myboolean\": false, \"state\": \"USA\"}", "", "myboolean", "0"},
313  {"{\"myreal\": 1E+2, \"state\": \"USA\"}", "", "myreal", "100"},
314  {"{\"myreal\": 1.23, \"state\": \"USA\"}", "", "myreal", "1.23"},
315  {"{\"myarray\": [[1]], \"state\": \"USA\"}", "", "myarray.0.0", "1"},
316  {"{\"myarray\": [null], \"state\": \"USA\"}", "", "myarray.0", ""},
317  {"{\"myarray\": [0, 1], \"state\": \"USA\"}", "", "myarray", "[0,1]"},
318  {"[0, 1]", "", "", ""},
319  {"[0, 1]", "", "0", "0"},
320  {"[0, 1]", "", "foo", ""},
321  {"{\"mynull\": null, \"state\": \"USA\"}", "", "mynull", ""},
322  {"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "city", "Anytown"},
323  {"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "state", "USA"},
324  {"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "blah", ""},
325  {"{\"key1\": \"123\", \"key2\": \"456\"}", "", "key1", "123"},
326  {"{\"key1\": 123, \"key2\": 456}", "", "key1", "123"},
327  {"{ \"path\": { \"to\": { \"elem\": \"someVar\" } } }", "/", "path/to/elem", "someVar"},
328  {"{ \"path\": { \"to\": { \"elem\": \"someVar\" } } }", "", "path.to.elem2", ""},
329  {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/2", ""}, /* nonexistent index */
330  {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/-1", ""}, /* bogus index */
331  {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/test", ""}, /* bogus index */
332  {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "", "path.to.arr.test.test2.subkey", ""}, /* bogus index */
333  {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", ",c", "path.to.arr", "2"}, /* test count */
334  {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "", "path.to.arr", "[\"item0\",\"item1\"]"},
335  {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", ".", "path.to.arr.1", "item1"},
336  {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr", "[\"item0\",\"item1\"]"},
337  {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/1", "item1"},
338  {"{ \"path\": { \"to\": { \"arr\": [ {\"name\": \"John Smith\", \"phone\": \"123\"}, {\"name\": \"Jane Doe\", \"phone\": \"234\"} ] } } }", ",c", "path.to.arr.0.name", "John Smith"},
339  {"{ \"path\": { \"to\": { \"arr\": [ {\"name\": 1, \"phone\": 123}, {\"name\": 2, \"phone\": 234} ] } } }", ",c", "path.to.arr.0.name", "1"},
340  {"{ \"path\": { \"to\": { \"arr\": [ {\"name\": [ \"item11\", \"item12\" ], \"phone\": [ \"item13\", \"item14\" ]}, {\"name\": [ \"item15\", \"item16\" ], \"phone\": [ \"item17\", \"item18\" ]} ] } } }", ",c", "path.to.arr.0.name.1", "item12"},
341  {"{ \"startId\": \"foobar\", \"abcd\": { \"id\": \"abcd\", \"type\": \"EXT\" }, \"bcde\": { \"id\": \"bcde\", \"type\": \"CONDITION\" }, \"defg\": { \"id\": \"defg\", \"type\": \"EXT\" }, \"efgh\": { \"id\": \"efgh\", \"type\": \"VOICEMAIL\" } }", "", "bcde", "{\"id\":\"bcde\",\"type\":\"CONDITION\"}"},
342  };
343 
344  switch (cmd) {
345  case TEST_INIT:
346  info->name = "func_JSON_DECODE";
347  info->category = "/funcs/func_json/";
348  info->summary = "Test JSON_DECODE function";
349  info->description = "Verify JSON_DECODE behavior";
350  return AST_TEST_NOT_RUN;
351  case TEST_EXECUTE:
352  break;
353  }
354 
355  if (!(chan = ast_dummy_channel_alloc())) {
356  ast_test_status_update(test, "Unable to allocate dummy channel\n");
357  return AST_TEST_FAIL;
358  }
359 
360  if (!(str = ast_str_create(64))) {
361  ast_test_status_update(test, "Unable to allocate dynamic string buffer\n");
362  ast_channel_release(chan);
363  return AST_TEST_FAIL;
364  }
365 
366  for (i = 0; i < ARRAY_LEN(test_strings); i++) {
367  char tmp[512];
368 
369  struct ast_var_t *var = ast_var_assign("test_string", test_strings[i][0]);
370  if (!var) {
371  ast_test_status_update(test, "Unable to allocate variable\n");
372  ast_free(str);
373  ast_channel_release(chan);
374  return AST_TEST_FAIL;
375  }
376 
377  AST_LIST_INSERT_HEAD(ast_channel_varshead(chan), var, entries);
378 
379  snprintf(tmp, sizeof(tmp), "${JSON_DECODE(%s,%s,%s)}", "test_string", test_strings[i][2], test_strings[i][1]);
380 
381  ast_str_substitute_variables(&str, 0, chan, tmp);
382  if (strcmp(test_strings[i][3], ast_str_buffer(str))) {
383  ast_test_status_update(test, "Format string '%s' substituted to '%s' (key: %s). Expected '%s'.\n", test_strings[i][0], ast_str_buffer(str), test_strings[i][2], test_strings[i][3]);
384  res = AST_TEST_FAIL;
385  }
386  }
387 
388  ast_free(str);
389  ast_channel_release(chan);
390 
391  return res;
392 }
393 #endif
394 
395 static int unload_module(void)
396 {
397  int res;
398 
399 #ifdef TEST_FRAMEWORK
400  AST_TEST_UNREGISTER(test_JSON_DECODE);
401 #endif
402  res = ast_custom_function_unregister(&json_decode_function);
403 
404  return res;
405 }
406 
407 static int load_module(void)
408 {
409  int res;
410 
411 #ifdef TEST_FRAMEWORK
412  AST_TEST_REGISTER(test_JSON_DECODE);
413 #endif
414  res = ast_custom_function_register(&json_decode_function);
415 
416  return res;
417 }
418 
419 AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "JSON decoding function");
const char * name
Definition: pbx.h:119
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:86
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
struct ast_channel * ast_channel_release(struct ast_channel *chan)
Unlink and release reference to a channel.
Definition: channel.c:1584
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
void ast_json_free(void *p)
Asterisk's custom JSON allocator. Exposed for use by unit tests.
Definition: json.c:52
int ast_json_is_true(const struct ast_json *value)
Check if value is JSON true.
Definition: json.c:263
#define ast_json_dump_string(root)
Encode a JSON value to a compact string.
Definition: json.h:810
void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ)
Test Framework API.
struct ast_json * ast_json_load_str(const struct ast_str *input, struct ast_json_error *error)
Parse ast_str into a JSON object or array.
Definition: json.c:580
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
Utility functions.
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_dummy_channel_alloc()
Create a fake channel structure.
Definition: channel.h:1282
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
Conversion utility functions.
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:283
#define ast_debug(level,...)
Log a DEBUG message.
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Definition: main/app.c:3066
Core PBX routines and definitions.
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
int ast_str_to_int(const char *str, int *res)
Convert the given string to a signed integer.
Definition: conversions.c:44
Support for dynamic strings.
Definition: strings.h:623
#define AST_LIST_INSERT_HEAD(head, elm, field)
Inserts a list entry at the head of a list.
Definition: linkedlists.h:711
double ast_json_real_get(const struct ast_json *real)
Get the value from a JSON real number.
Definition: json.c:347
int ast_json_is_object(const struct ast_json *value)
Check if value is JSON object.
Definition: json.c:258
enum ast_json_type ast_json_typeof(const struct ast_json *value)
Get the type of value.
Definition: json.c:78
Structure used to handle boolean flags.
Definition: utils.h:199
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:407
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
size_t ast_json_array_size(const struct ast_json *array)
Get the size of a JSON array.
Definition: json.c:366
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
Abstract JSON element (object, array, string, int, ...).
struct ast_str * ast_str_thread_get(struct ast_threadstorage *ts, size_t init_len)
Retrieve a thread locally stored dynamic string.
Definition: strings.h:909
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
intmax_t ast_json_integer_get(const struct ast_json *integer)
Get the value from a JSON integer.
Definition: json.c:332
struct ast_json * ast_json_array_get(const struct ast_json *array, size_t index)
Get an element from an array.
Definition: json.c:370
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
#define AST_APP_ARG(name)
Define an application argument.