The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
jpath.c
Go to the documentation of this file.
1 /*
2  * This program is is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or (at
5  * your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: 8dde82c609b3d7be5f23c6f2420a1280d308ec0b $
19  * @file jpath.c
20  * @brief Implements the evaluation and parsing functions for the FreeRADIUS version of jpath.
21  *
22  * @author Arran Cudbard-Bell
23  *
24  * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
25  * @copyright 2015 Network RADIUS SAS (legal@networkradius.com)
26  * @copyright 2015 The FreeRADIUS Server Project
27  */
28 #include <freeradius-devel/util/debug.h>
29 #include "base.h"
30 
31 #define SELECTOR_INDEX_UNSET INT32_MAX
32 
33 typedef enum {
34  JPATH_OPERAND_LITERAL = 0, //!< Operand is a literal
35  JPATH_OPERAND_JPATH //!< Operand is a jpath sequence
37 
38 typedef enum {
40  JPATH_SELECTOR_ROOT, //!< Jump to the root of the document.
41  JPATH_SELECTOR_CURRENT, //!< Continue at the current node in the document.
42  JPATH_SELECTOR_WILDCARD, //!< Wildcard, operate over all array indices, or fields
43  JPATH_SELECTOR_FIELD, //!< A field, current JSON node must be an object.
44  JPATH_SELECTOR_INDEX, //!< Array index, current JSON node must be an array.
45  JPATH_SELECTOR_SLICE, //!< Array slice, current JSON node must be an array.
46  JPATH_SELECTOR_FILTER_EXPRESSION, //!< Complex filter expression (NYI).
47  JPATH_SELECTOR_EXPRESSION, //!< Expression (NYI).
48  JPATH_SELECTOR_RECURSIVE_DESCENT //!< Descend through the JSON tree, looking for a node which matches
49  //!< the next one in the jpath sequence.
51 
52 /** Operand in a jpath expression
53  *
54  */
55 typedef union {
56  char const *literal; //!< Operand is a literal (value)
57  fr_jpath_node_t *jpath; //!< Operand is a jpath expression
59 
60 /** A jpath expression for performing complex comparisons against field values
61  *
62  */
63 typedef struct {
64  jpath_operand_t lhs; //!< LHS value.
65 
66  jpath_operand_t rhs; //!< RHS value.
67 } jpath_expr_t;
68 
69 /** Selects a subset of JSON child nodes
70  *
71  */
72 typedef struct jpath_selector jpath_selector_t;
74  union {
75  char const *field; //!< JSON object name
76  int32_t slice[3]; //!< Array index, or slice, or step
77 
78  jpath_expr_t expr; //!< Expression
79  };
80  jpath_type_t type; //!< Type of the Jpath node.
82 };
83 
84 /** Node in a jpath selector sequence
85  *
86  */
87 struct fr_jpath_node {
88  jpath_selector_t *selector; //!< Jpath selector head (there may be multiple).
89  fr_jpath_node_t *next; //!< Next in the jpath chain.
90 };
91 
92 static char const escape_chars[] = "[],*.:()?";
93 
94 
95 /** Escapes special chars
96  *
97  * Escapes any characters that may have special meaning within a jpath expression
98  *
99  * @param request Current request (unused may be NULL).
100  * @param out Where to write the escaped string.
101  * @param outlen Length of the output buffer.
102  * @param in data to escape.
103  * @param arg uctx data, not used.
104  * @return the number of chars written to out.
105  */
106 size_t fr_jpath_escape_func(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
107 {
108  char const *p = in;
109  char *q = out, *end = out + outlen;
110 
111  if (outlen == 0) return 0;
112 
113  while (*p && (q < end)) {
114  if (memchr(escape_chars, *p, sizeof(escape_chars))) {
115  if ((q + 1) >= end) break;
116  q[0] = '\\';
117  q[1] = *p++;
118  q += 2;
119  continue;
120  }
121  *q++ = *p++;
122  }
123  *q = '\0';
124 
125  return q - out;
126 }
127 
128 /** Recursive function for jpath_expr_evaluate
129  *
130  * @param[in,out] ctx to allocate fr_value_box_t in.
131  * @param[out] tail Where to write fr_value_box_t.
132  * @param[in] dst_type FreeRADIUS type to convert to.
133  * @param[in] dst_enumv Enumeration values to allow string to integer conversions.
134  * @param[in] object current node in the json tree.
135  * @param[in] jpath to evaluate.
136  * @return
137  * - 1 on match.
138  * - 0 on no match.
139  * - -1 on error.
140  */
141 static int jpath_evaluate(TALLOC_CTX *ctx, fr_value_box_list_t *tail,
142  fr_type_t dst_type, fr_dict_attr_t const *dst_enumv,
143  json_object *object, fr_jpath_node_t const *jpath)
144 {
146  fr_jpath_node_t const *node;
147  jpath_selector_t const *selector;
148  bool child_matched = false;
149  int ret = 0;
150 
151  /*
152  * Iterate over the nodes, we only recurse for
153  * more complex operations.
154  */
155  for (node = jpath; node; node = node->next) switch (node->selector->type) {
157  if (!json_object_is_type(object, json_type_object)) return 0;
158  if (!json_object_object_get_ex(object, node->selector->field, &object)) return 0;
159  continue;
160 
163  /*
164  * There may be multiple selectors per node
165  */
166  for (selector = node->selector; selector; selector = selector->next) switch (selector->type) {
168  {
169  struct array_list *array_obj; /* Because array_list is a global... */
170 
171  fr_assert(selector->slice[0] != SELECTOR_INDEX_UNSET);
172 
173  if (!json_object_is_type(object, json_type_array)) return 0;
174  array_obj = json_object_get_array(object);
175  if ((selector->slice[0] < 0) ||
176  (selector->slice[0] >= (int32_t)(array_obj->length & INT32_MAX))) continue;
177 
178  ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
179  array_obj->array[selector->slice[0]], node->next);
180  if (ret < 0) return ret;
181  if (ret == 1) child_matched = true;
182  }
183  break;
184 
186  {
187  struct array_list *array_obj;
188  int32_t start, end, step, i;
189 
190  if (!json_object_is_type(object, json_type_array)) return 0;
191  array_obj = json_object_get_array(object);
192 
193  /*
194  * This logic may seem slightly odd, but it perfectly
195  * emulates python array slicing behaviour AFAICT
196  */
197  step = selector->slice[2];
198  if (step == SELECTOR_INDEX_UNSET) step = 1;
199 
200  start = selector->slice[0];
201  if (start == SELECTOR_INDEX_UNSET) start = (step < 0) ?
202  (int32_t)((array_obj->length - 1) & INT32_MAX) : 0;
203  else if (start < 0) start = array_obj->length + start;
204 
205  end = selector->slice[1];
206  if (end == SELECTOR_INDEX_UNSET) end = (step < 0) ?
207  -1 : (int32_t)((array_obj->length - 1) & INT32_MAX);
208  else if (end < 0) end = array_obj->length + end;
209 
210  /*
211  * Descending
212  */
213  if (step < 0) for (i = start; (i > end) && (i >= 0); i += step) {
214  fr_assert((i >= 0) && (i < (int32_t)(array_obj->length & INT32_MAX)));
215  ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
216  array_obj->array[i], node->next);
217  if (ret < 0) return ret;
218  if (ret == 1) child_matched = true;
219  /*
220  * Ascending
221  */
222  } else for (i = start; (i < end) && (i < (int32_t)(array_obj->length & INT32_MAX)); i += step) {
223  fr_assert((i >= 0) && (i < (int32_t)(array_obj->length & INT32_MAX)));
224  ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
225  array_obj->array[i], node->next);
226  if (ret < 0) return ret;
227  if (ret == 1) child_matched = true;
228  }
229  }
230  break;
231 
232  default:
233  fr_assert(0);
234  return -1;
235  }
236  return child_matched ? 1 : 0;
237 
238  /*
239  * Iterate over fields or array indices
240  */
242  {
243  int i;
244 
245  if (json_object_is_type(object, json_type_array)) {
246  struct array_list *array_obj;
247 
248  array_obj = json_object_get_array(object);
249  for (i = 0; i < (int32_t)(array_obj->length & INT32_MAX); i++) {
250  ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
251  array_obj->array[i], node->next);
252  if (ret < 0) return ret;
253  if (ret == 1) child_matched = true;
254  }
255  return child_matched ? 1 : 0;
256  } else if (json_object_is_type(object, json_type_object)) {
257  json_object_object_foreach(object, field_name, field_value) {
258 #ifndef NDEBUG
259  fr_assert(field_name);
260 #else
261  UNUSED_VAR(field_name);
262 #endif
263  ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
264  field_value, node->next);
265  if (ret < 0) return ret;
266  if (ret == 1) child_matched = true;
267  }
268  return child_matched ? 1 : 0;
269  } else return 0;
270  }
271 
272  /*
273  * @todo Brute force it more efficiently.
274  */
276  {
277  int i;
278 
279  if (json_object_is_type(object, json_type_array)) {
280  struct array_list *array_obj;
281 
282  /*
283  * Descend into each element of the array
284  */
285  array_obj = json_object_get_array(object);
286  for (i = 0; i < (int32_t)(array_obj->length & INT32_MAX); i++) {
287  ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
288  array_obj->array[i], node);
289  if (ret < 0) return ret;
290  if (ret == 1) child_matched = true;
291  }
292 
293  /*
294  * On the way back up, evaluate the object's fields
295  */
296  ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
297  object, node->next);
298  if (ret < 0) return ret;
299  if (ret == 1) child_matched = true;
300 
301  return child_matched ? 1 : 0;
302  } else if (json_object_is_type(object, json_type_object)) {
303  /*
304  * Descend into each field of the object
305  */
306  json_object_object_foreach(object, field_name, field_value) {
307 #ifndef NDEBUG
308  fr_assert(field_name);
309 #else
310  UNUSED_VAR(field_name);
311 #endif
312  ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
313  field_value, node);
314  if (ret < 0) return ret;
315  if (ret == 1) child_matched = true;
316  }
317 
318  /*
319  * On the way back up, evaluate the object's fields
320  */
321  ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
322  object, node->next);
323  if (ret < 0) return ret;
324  if (ret == 1) child_matched = true;
325 
326  return child_matched ? 1 : 0;
327  }
328 
329  /*
330  * Descend down to the level of the leaf
331  *
332  * Parser guarantees that the recursive descent operator
333  * is never the last in a jpath sequence.
334  */
335  return jpath_evaluate(ctx, tail, dst_type, dst_enumv, object, node->next);
336  }
337 
341  case JPATH_SELECTOR_ROOT:
343  fr_assert(0);
344  return -1; /* Not yet implemented */
345  }
346 
347  /*
348  * We've reached the end of the jpath sequence
349  * we now attempt conversion of the leaf to
350  * the specified value.
351  */
353  if (fr_json_object_to_value_box(value, value, object, dst_enumv, true) < 0) {
355  return -1;
356  }
357 
358  if (fr_value_box_cast_in_place(value, value, dst_type, dst_enumv) < 0) {
360  return -1;
361  }
362 
363  fr_value_box_list_insert_tail(tail, value);
364  return 1;
365 }
366 
367 /** Evaluate a parsed jpath expression against a json-c tree
368  *
369  * Will produce one or more fr_value_box_t structures of the desired type,
370  * or error out if the conversion between types fails.
371  *
372  * @param[in,out] ctx to allocate fr_value_box_t in.
373  * @param[out] out Where to write fr_value_box_t.
374  * @param[in] dst_type FreeRADIUS type to convert to.
375  * @param[in] dst_enumv Enumeration values to allow string to integer conversions.
376  * @param[in] root of the json-c tree.
377  * @param[in] jpath to evaluate.
378  * @return
379  * - 1 on match.
380  * - 0 on no match.
381  * - -1 on error.
382  */
383 int fr_jpath_evaluate_leaf(TALLOC_CTX *ctx, fr_value_box_list_t *out,
384  fr_type_t dst_type, fr_dict_attr_t const *dst_enumv,
385  json_object *root, fr_jpath_node_t const *jpath)
386 {
387  if (!root) return -1;
388 
389  switch (jpath->selector->type) {
390  case JPATH_SELECTOR_ROOT:
392  break;
393 
394  default:
395  fr_assert(0);
396  return -1;
397  }
398 
399  return jpath_evaluate(ctx, out, dst_type, dst_enumv, root, jpath->next);
400 }
401 
402 /** Print a node list to a string for debugging
403  *
404  * Will not be identical to the original parsed string, but should be sufficient
405  * for testing purposes.
406  *
407  * @param ctx to allocate string in.
408  * @param head of the node list.
409  * @return the string representation of the node list.
410  */
411 char *fr_jpath_asprint(TALLOC_CTX *ctx, fr_jpath_node_t const *head)
412 {
413  fr_jpath_node_t const *node;
414  jpath_selector_t *selector;
415  char *p;
416 
417  p = talloc_zero_array(ctx, char, 1);
418 
419  for (node = head; node; node = node->next) switch (node->selector->type) {
420  case JPATH_SELECTOR_ROOT:
421  p = talloc_strdup_append_buffer(p, "$");
422  break;
423 
425  p = talloc_strdup_append_buffer(p, "@");
426  break;
427 
429  p = talloc_strdup_append_buffer(p, ".*");
430  break;
431 
433  {
434  char buffer[257];
435 
436  fr_jpath_escape_func(NULL, buffer, sizeof(buffer), node->selector->field, NULL);
437  p = talloc_asprintf_append_buffer(p, ".%s", buffer);
438  }
439  break;
440 
441  /*
442  * Bracketed selectors can contain a mix of types
443  */
448  p = talloc_strdup_append_buffer(p, "[");
449  for (selector = node->selector; selector; selector = selector->next) switch (selector->type) {
451  p = talloc_asprintf_append_buffer(p, "%i%s", selector->slice[0], selector->next ? "," : "");
452  break;
453 
455  if (selector->slice[0] == SELECTOR_INDEX_UNSET) {
456  p = talloc_strdup_append_buffer(p, ":");
457  } else {
458  p = talloc_asprintf_append_buffer(p, "%i:", selector->slice[0]);
459  }
460  if (selector->slice[1] == SELECTOR_INDEX_UNSET) {
461  p = talloc_strdup_append_buffer(p, ":");
462  } else if (selector->slice[2] == SELECTOR_INDEX_UNSET) {
463  p = talloc_asprintf_append_buffer(p, "%i", selector->slice[1]);
464  } else {
465  p = talloc_asprintf_append_buffer(p, "%i:", selector->slice[1]);
466  }
467  if (selector->slice[2] != SELECTOR_INDEX_UNSET) {
468  p = talloc_asprintf_append_buffer(p, "%i", selector->slice[2]);
469  }
470  if (selector->next) p = talloc_strdup_append_buffer(p, ",");
471  break;
472 
473  default:
474  fr_assert(0); /* Not yet implemented */
475  break;
476  }
477  p = talloc_strdup_append_buffer(p, "]");
478  break;
479 
481  if (node->next) switch (node->next->selector->type) {
484  p = talloc_strdup_append_buffer(p, "..");
485  break;
486 
487  default:
488  p = talloc_strdup_append_buffer(p, ".");
489  break;
490  }
491  break;
492 
494  fr_assert(0);
495  return NULL;
496  }
497 
498  return p;
499 }
500 
501 /** Parse a jpath filter expression, which in our case, is a FreeRADIUS condition
502  *
503  * @note this requires reworking some of the condition code to accept
504  * callbacks to retrieve virtual attributes, so is not yet implemented.
505  */
507 {
508  /* selector->type = JPATH_SELECTOR_FILTER_EXPRESSION; */
509 
510  fr_strerror_const("Filter expressions not yet implemented");
511  return 0;
512 }
513 
514 /** Parse a jpath expression
515  *
516  * @note this requires reworking some of the condition code to accept
517  * callbacks to retrieve virtual attributes, so is not yet implemented.
518  */
519 static ssize_t jpath_expr_parse(UNUSED jpath_selector_t *selector, UNUSED char const *in, UNUSED size_t inlen)
520 {
521  /* selector->type = JPATH_SELECTOR_EXPRESSION; */
522 
523  fr_strerror_const("Expressions not yet implemented");
524  return 0;
525 }
526 
527 /** Parse index/slice notation
528  *
529  * Expects in to point to a buffer containing:
530  *
531  @verbatim
532  [<int0>:<int1>:<int2>]
533  @endverbatim
534  *
535  * Where each of the integers and its accompanying delimiter is optional.
536  *
537  * @param selector to populate with index/slice info.
538  * @param in input.
539  * @param inlen length of in.
540  * @return
541  * - > 0 on success.
542  * - <= 0 on error (* -1 to get offset error occurred at).
543  */
544 static ssize_t jpath_array_parse(jpath_selector_t *selector, char const *in, size_t inlen)
545 {
546  int idx = 0;
547  int32_t num;
548  char const *p, *end = in + inlen;
549  char *q;
550  ssize_t ret;
551 
552  char buffer[33]; /* Max (uin32_t * 3) + 2 + 1 */
553 
554  selector->type = JPATH_SELECTOR_INDEX;
555  selector->slice[0] = SELECTOR_INDEX_UNSET;
556  selector->slice[1] = SELECTOR_INDEX_UNSET;
557  selector->slice[2] = SELECTOR_INDEX_UNSET;
558 
559  /*
560  * Scan forward until we find a delimiter or terminator
561  */
562  for (p = in; p < end; p++) if ((p[0] == ',') || (p[0] == ']')) break;
563  if (p == end) {
564  fr_strerror_const("Missing selector delimiter ',' or terminator ']'");
565  return -inlen;
566  }
567 
568  ret = (p - in);
569  if (ret == 0) {
570  fr_strerror_const("Empty selector");
571  return 0;
572  }
573 
574  if (inlen > sizeof(buffer)) { /* - 1 for ] */
575  fr_strerror_const("Selector too long");
576  return -inlen;
577  }
578 
579  /*
580  * Have to use an intermediary buffer because strtol
581  * doesn't accept a terminating address via endptr.
582  */
583  memcpy(&buffer, in, p - in);
584  buffer[p - in] = '\0';
585  p = buffer;
586 
587  /*
588  * Index or start
589  */
590  num = (int32_t)strtol(p, &q, 10);
591  if (q > p) switch (q[0]) {
592  default:
593  no_term:
594  fr_strerror_const("Expected num, ':' or ']'");
595  return buffer - q;
596 
597  case ':': /* More integers to parse */
598  selector->slice[idx] = num;
599  break;
600 
601  case '\0': /* Array index */
602  selector->slice[idx] = num;
603  return ret;
604  }
605  if (q[0] != ':') goto no_term;
606  idx++;
607  p = q + 1;
608 
609  selector->type = JPATH_SELECTOR_SLICE;
610 
611  /*
612  * End
613  */
614  num = (int32_t)strtol(p, &q, 10);
615  if (q > p) switch (q[0]) {
616  default:
617  goto no_term;
618 
619  case ':': /* More integers to parse */
620  selector->slice[idx] = num;
621  break;
622 
623  case '\0': /* Array End */
624  selector->slice[idx] = num;
625  return ret;
626  }
627  if (q[0] != ':') goto no_term;
628  idx++;
629  p = q + 1;
630 
631  /*
632  * Step
633  */
634  num = (int32_t)strtol(p, &q, 10);
635  if (q[0] != '\0') {
636  fr_strerror_const("Expected num or ']'");
637  return buffer - q;
638  }
639  if (q > p) {
640  if (num == 0) {
641  fr_strerror_const("Step cannot be 0");
642  return buffer - p;
643  }
644  selector->slice[idx] = num;
645  }
646  return ret;
647 }
648 
649 /** Parse a jpath field
650  *
651  */
652 static size_t jpath_field_parse(fr_jpath_node_t *node, char const *in, size_t inlen)
653 {
654  char buffer[128];
655 
656  char const *p = in, *end = p + inlen;
657  char *buff_p = buffer, *buff_end = buff_p + sizeof(buffer);
658 
659  /*
660  * Field name with optional selector
661  */
662  while (p < end) {
663  int clen;
664 
665  if (buff_p == buff_end) {
666  name_too_big:
667  fr_strerror_const("Exceeded maximum field name length");
668  return in - p;
669  }
670 
671  clen = fr_utf8_char((uint8_t const *)p, end - p);
672  if (clen == 0) {
673  fr_strerror_const("Bad UTF8 char");
674  return in - p;
675  }
676 
677  /*
678  * Multibyte
679  */
680  if (clen > 1) {
681  if ((buff_p + clen) >= buff_end) goto name_too_big;
682  memcpy(buff_p, p, clen);
683  buff_p += clen;
684  p += clen;
685  continue;
686  }
687 
688  switch (p[0]) { /* Normal char */
689  default:
690  *buff_p++ = *p++;
691  continue;
692 
693  /*
694  * Escape sequence
695  */
696  case '\\':
697  if (++p == end) return p - in;
698 
699  if (memchr(escape_chars, p[0], sizeof(escape_chars))) {
700  *buff_p++ = *p++;
701  continue;
702  }
703  *buff_p++ = '\\';
704  continue;
705 
706  /*
707  * Things that mark the end of a field
708  */
709  case '[':
710  case ']': /* Not really, but it's probably not right */
711  case '.':
712  break;
713  }
714  break;
715  }
716 
717  if (buff_p == buffer) {
718  fr_strerror_const("Empty field specifier");
719  return 0;
720  }
721  node->selector->field = talloc_bstrndup(node, buffer, buff_p - buffer);
723 
724  return p - in;
725 }
726 
727 /** parse a jpath selector
728  *
729  */
730 static size_t jpath_selector_parse(fr_jpath_node_t *node, char const *in, size_t inlen)
731 {
732  ssize_t slen;
733  char const *p = in, *end = p + inlen;
734 
735  jpath_selector_t **stail;
736  jpath_selector_t *selector;
737 
738  stail = &node->selector->next;
739  selector = node->selector;
740 
741  if (++p == end) { /* Skip past [ */
742  missing_terminator:
743  fr_strerror_const("Missing selector terminator ']'");
744  return in - p;
745  }
746 
747  while (p < end) {
748  /*
749  * What kind of selector is it?
750  */
751  switch (p[0]) {
752  case '?': /* Filter expression */
753  slen = jpath_filter_expr_parse(selector, p, end - p);
754  break;
755 
756  case '(': /* Expression */
757  slen = jpath_expr_parse(selector, p, end - p);
758  break;
759 
760  default: /* Index or slice */
761  slen = jpath_array_parse(selector, p, end - p);
762  break;
763  }
764  if (slen <= 0) {
765  p += -slen;
766  return in - p; /* Error */
767  }
768  p += slen;
769  if (p == end) goto missing_terminator;
770  fr_assert(p < end);
771 
772  /*
773  * Things that terminate a selector
774  *
775  * - ']' a selector terminator.
776  * - ',' another selector.
777  */
778  if (p[0] == ']') break; /* We're done */
779  if (p[0] != ',') { /* There's more... */
780  fr_strerror_printf("Expected selector delimiter ','"
781  "or selector terminator ']'");
782  return in - p; /* Error */
783  }
784  if (++p == end) goto missing_terminator;
785 
786  /*
787  * Link in an additional selector
788  */
789  *stail = selector = talloc_zero(node, jpath_selector_t);
790  if (!selector) {
791  fr_strerror_const("Failed allocating selector");
792  return in - p;
793  }
794  stail = &selector->next;
795  }
796  if (p[0] != ']') goto missing_terminator;
797 
798  p++; /* Skip past ] */
799 
800  return p - in;
801 }
802 
803 /** Parse a jpath string
804  *
805  * Based on the syntax described here http://goessner.net/articles/JsonPath/
806  *
807  * Implements parser for everything except unions and expressions
808  *
809  * @return
810  * - > 0 on success.
811  * - <= 0 on error (* -1 to get offset error occurred at).
812  */
813 ssize_t fr_jpath_parse(TALLOC_CTX *ctx, fr_jpath_node_t **head, char const *in, size_t inlen)
814 {
815  TALLOC_CTX *tail_ctx = ctx;
816 
817  ssize_t slen;
818  fr_jpath_node_t *node, **tail = head;
819 
820  char const *p = in, *end = p + inlen;
821 
822  *head = NULL;
823 
824 #define NODE_NEW(_node) \
825 do { \
826  tail_ctx = *tail = (_node) = talloc_zero(tail_ctx, fr_jpath_node_t); \
827  (_node)->selector = talloc_zero((_node), jpath_selector_t); \
828  tail = &(_node)->next; \
829 } while (0)
830 
831  if (inlen < 1) {
832  bad_start:
833  fr_strerror_const("Expected root specifier '$', or current node specifier '@'");
834  return 0;
835  }
836 
837  /*
838  * Start of the jpath expression
839  *
840  * - '$' JSON root node specifier
841  * - '@' Current node specifier
842  */
843  switch (p[0]) {
844  case '$':
845  NODE_NEW(node);
847  p++;
848  break;
849 
850  case '@':
851  NODE_NEW(node);
853  p++;
854  break;
855 
856  default:
857  goto bad_start;
858  }
859 
860  /*
861  * Valid successions of '$' or '@'
862  *
863  * - '[' Start of a selector
864  * - '.' Start of a field specifier
865  */
866  while (p < end) {
867  NODE_NEW(node);
868 
869  switch (p[0]) {
870  case '.':
871  if (++p == end) {
872  fr_strerror_printf("Expected recursive descent '..' "
873  "wildcard '*' or field specifier");
874  error:
875  TALLOC_FREE(*head);
876  return in - p;
877  }
878 
879  /*
880  * Valid successions of '.'
881  *
882  * - '.' recursive descent.
883  * - '*' wildcard.
884  * - <name> (fieldname).
885  *
886  * This should probably be in its own function, but never mind...
887  */
888  switch (p[0]) {
889  case '.':
890  if ((p != end) && (p[1] == '.')) {
891  fr_strerror_printf("Recursive descent must not be "
892  "followed by child delimiter '.'");
893  p++;
894  goto error;
895  }
897 
898  if ((p + 1) == end) {
899  fr_strerror_const("Path may not end in recursive descent");
900  goto error;
901  }
902 
903  /*
904  * If and only if, the next char is the beginning
905  * of a selector, advance the pointer.
906  *
907  * Otherwise we leave it pointing to the second '.'
908  * allowing .* and .<field>
909  */
910  if (p[1] == '[') p++;
911  continue;
912 
913  case '*':
915  p++;
916  continue;
917 
918  default:
919  /*
920  * Field specifier is the only other valid possibility
921  */
922  slen = jpath_field_parse(node, p, (end - p));
923  if (slen <= 0) {
924  p += -(slen);
925  goto error;
926  }
927  p += slen;
928  if (p == end) return p - in; /* The end of string! */
929  fr_assert(p < end);
930  }
931  break;
932 
933  case '[':
934  slen = jpath_selector_parse(node, p, (end - p));
935  if (slen <= 0) {
936  p += -(slen);
937  goto error;
938  }
939  p += slen;
940  if (p == end) return p - in; /* The end of string! */
941  fr_assert(p < end);
942  break;
943 
944  default:
945  fr_strerror_const("Expected field specifier '.' or selector '['");
946  goto error;
947  }
948  }
949 
950  return p - in;
951 }
952 
static int const char char buffer[256]
Definition: acutest.h:574
#define UNUSED_VAR(_x)
Definition: build.h:301
#define UNUSED
Definition: build.h:313
static fr_slen_t in
Definition: dict.h:645
Test enumeration values.
Definition: dict_test.h:92
char const * literal
Operand is a literal (value)
Definition: jpath.c:56
jpath_operand_type
Definition: jpath.c:33
@ JPATH_OPERAND_JPATH
Operand is a jpath sequence.
Definition: jpath.c:35
@ JPATH_OPERAND_LITERAL
Operand is a literal.
Definition: jpath.c:34
jpath_operand_t rhs
RHS value.
Definition: jpath.c:66
fr_jpath_node_t * next
Next in the jpath chain.
Definition: jpath.c:89
jpath_operand_t lhs
LHS value.
Definition: jpath.c:64
char * fr_jpath_asprint(TALLOC_CTX *ctx, fr_jpath_node_t const *head)
Print a node list to a string for debugging.
Definition: jpath.c:411
static char const escape_chars[]
Definition: jpath.c:92
size_t fr_jpath_escape_func(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
Escapes special chars.
Definition: jpath.c:106
fr_jpath_node_t * jpath
Operand is a jpath expression.
Definition: jpath.c:57
#define SELECTOR_INDEX_UNSET
Definition: jpath.c:31
jpath_selector_t * selector
Jpath selector head (there may be multiple).
Definition: jpath.c:88
jpath_type_t
Definition: jpath.c:38
@ JPATH_SELECTOR_EXPRESSION
Expression (NYI).
Definition: jpath.c:47
@ JPATH_SELECTOR_CURRENT
Continue at the current node in the document.
Definition: jpath.c:41
@ JPATH_SELECTOR_INDEX
Array index, current JSON node must be an array.
Definition: jpath.c:44
@ JPATH_SELECTOR_SLICE
Array slice, current JSON node must be an array.
Definition: jpath.c:45
@ JPATH_SELECTOR_FIELD
A field, current JSON node must be an object.
Definition: jpath.c:43
@ JPATH_SELECTOR_INVALID
Definition: jpath.c:39
@ JPATH_SELECTOR_RECURSIVE_DESCENT
Descend through the JSON tree, looking for a node which matches the next one in the jpath sequence.
Definition: jpath.c:48
@ JPATH_SELECTOR_ROOT
Jump to the root of the document.
Definition: jpath.c:40
@ JPATH_SELECTOR_FILTER_EXPRESSION
Complex filter expression (NYI).
Definition: jpath.c:46
@ JPATH_SELECTOR_WILDCARD
Wildcard, operate over all array indices, or fields.
Definition: jpath.c:42
int fr_jpath_evaluate_leaf(TALLOC_CTX *ctx, fr_value_box_list_t *out, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, json_object *root, fr_jpath_node_t const *jpath)
Evaluate a parsed jpath expression against a json-c tree.
Definition: jpath.c:383
static size_t jpath_field_parse(fr_jpath_node_t *node, char const *in, size_t inlen)
Parse a jpath field.
Definition: jpath.c:652
jpath_selector_t * next
Definition: jpath.c:81
static ssize_t jpath_filter_expr_parse(UNUSED jpath_selector_t *selector, UNUSED char const *in, UNUSED size_t inlen)
Parse a jpath filter expression, which in our case, is a FreeRADIUS condition.
Definition: jpath.c:506
jpath_type_t type
Type of the Jpath node.
Definition: jpath.c:80
static size_t jpath_selector_parse(fr_jpath_node_t *node, char const *in, size_t inlen)
parse a jpath selector
Definition: jpath.c:730
static ssize_t jpath_expr_parse(UNUSED jpath_selector_t *selector, UNUSED char const *in, UNUSED size_t inlen)
Parse a jpath expression.
Definition: jpath.c:519
static ssize_t jpath_array_parse(jpath_selector_t *selector, char const *in, size_t inlen)
Parse index/slice notation.
Definition: jpath.c:544
ssize_t fr_jpath_parse(TALLOC_CTX *ctx, fr_jpath_node_t **head, char const *in, size_t inlen)
Parse a jpath string.
Definition: jpath.c:813
#define NODE_NEW(_node)
static int jpath_evaluate(TALLOC_CTX *ctx, fr_value_box_list_t *tail, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, json_object *object, fr_jpath_node_t const *jpath)
Recursive function for jpath_expr_evaluate.
Definition: jpath.c:141
Node in a jpath selector sequence.
Definition: jpath.c:87
A jpath expression for performing complex comparisons against field values.
Definition: jpath.c:63
Operand in a jpath expression.
Definition: jpath.c:55
int fr_json_object_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *out, json_object *object, fr_dict_attr_t const *enumv, bool tainted)
Convert json object to fr_value_box_t.
Definition: json.c:96
talloc_free(reap)
fr_type_t
Definition: merged_model.c:80
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
size_t fr_utf8_char(uint8_t const *str, ssize_t inlen)
Checks for utf-8, taken from http://www.w3.org/International/questions/qa-forms-utf-8.
Definition: print.c:39
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
Definition: talloc.c:452
static fr_slen_t head
Definition: xlat.h:408
Master include file to access all functions and structures in the library.
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition: strerror.h:64
#define fr_strerror_const(_msg)
Definition: strerror.h:223
int fr_value_box_cast_in_place(TALLOC_CTX *ctx, fr_value_box_t *vb, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv)
Convert one type of fr_value_box_t to another in place.
Definition: value.c:3521
static size_t char fr_sbuff_t size_t inlen
Definition: value.h:984
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
Definition: value.h:619
static size_t char ** out
Definition: value.h:984