From c449dd2589e36db082e70c5724d7a6a2cdc2ec8d Mon Sep 17 00:00:00 2001 From: Matthew Newton Date: Mon, 17 Feb 2020 00:16:52 +0000 Subject: [PATCH] lib/json: ability to generate several different JSON formats --- src/lib/json/base.h | 190 ++++++++++- src/lib/json/json.c | 634 ++++++++++++++++++++++++++++++++---- src/lib/util/pair.c | 26 ++ src/lib/util/pair.h | 1 + src/modules/rlm_rest/rest.c | 2 +- 5 files changed, 782 insertions(+), 71 deletions(-) diff --git a/src/lib/json/base.h b/src/lib/json/base.h index 9bf5052a966d..a7270eaf02cd 100644 --- a/src/lib/json/base.h +++ b/src/lib/json/base.h @@ -21,9 +21,10 @@ * @brief Implements the evaluation and parsing functions for the FreeRADIUS version of jpath. * * @author Arran Cudbard-Bell + * @author Matthew Newton * * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org) - * @copyright 2015 Network RADIUS SARL (legal@networkradius.com) + * @copyright 2015,2020 Network RADIUS SARL (legal@networkradius.com) * @copyright 2015 The FreeRADIUS Server Project */ RCSIDH(json_h, "$Id$") @@ -42,6 +43,190 @@ RCSIDH(json_h, "$Id$") # include +extern fr_table_num_sorted_t const fr_json_format_table[]; +extern size_t fr_json_format_table_len; + +/** List of possible JSON format output modes. + * + * @see fr_json_format_s + */ +typedef enum { + JSON_MODE_UNSET = 0, + JSON_MODE_OBJECT, + JSON_MODE_OBJECT_SIMPLE, + JSON_MODE_ARRAY, + JSON_MODE_ARRAY_OF_VALUES, + JSON_MODE_ARRAY_OF_NAMES +} json_mode_type_t; + + +/** Attribute formatting options for fr_json_afrom_pair_list() + * + * Controls how attributes are formatted in JSON documents + * produced from fr_json_afrom_pair_list(). + * + * **prefix** adds a string prefix to all attribute names in the + * JSON document, with a colon delimiter. + * + * Example, when prefix is NULL: +@verbatim +{"User-Name":{"type":"string","value":["john"]}} +@endverbatim + * + * Example, when prefix is set to `foo`: +@verbatim +{"foo:User-Name":{"type":"string","value":["john"]}} +@endverbatim + * + * @see struct fr_json_format_s + * + */ +typedef struct { + char const *prefix; //!< Prefix to add to all attribute names +} fr_json_format_attr_t; + + +/** Value formatting options for fr_json_afrom_pair_list() + * + * Controls how values are formatted in JSON documents + * produced from fr_json_afrom_pair_list(). + * + * Not all these options are valid for all output modes. + * @see fr_json_format_verify(), fr_json_format_s + * + * + * If an attribute appears only once then the value will normally + * be written as an object. When an attribute appears more than + * once then the values will be added as an array instead. Setting + * **value_as_array** will ensure that values are always written as + * an array, even if containing only a single entry. + * + * Example with output_mode `JSON_MODE_OBJECT_SIMPLE` and `value_as_array` is false: +@verbatim +{"User-Name":"john","Filter-Id":["f1","f2"]} +@endverbatim + * + * Example with output_mode `JSON_MODE_OBJECT_SIMPLE` and `value_as_array` is true: +@verbatim +{"User-Name":["john"],"Filter-Id":["f1","f2"]} +@endverbatim + * + * + * Set **enum_as_int** to write enumerated values in their integer form. + * + * When false, the string form is output: +@verbatim +{"Service-Type":{"type":"uint32","value":"Login-User"}} +@endverbatim + * + * When true, the integer is output: +@verbatim +{"Service-Type":{"type":"uint32","value":1}} +@endverbatim + * + * + * Numeric data types will usually be written to the JSON document + * as numbers. **always_string** ensures that all values are written as + * strings: + * + * Example when `always_string` is false: +@verbatim +{"NAS-Port":{"type":"uint32","value":999}} +@endverbatim + * + * Example when `always_string` is true: +@verbatim +{"NAS-Port":{"type":"uint32","value":"999"}} +@endverbatim + * + */ +typedef struct { + bool value_as_array; //!< Use JSON array for multiple attribute values. + bool enum_as_int; //!< Output enums as value, not their string representation. + bool always_string; //!< Output all data types as strings. +} fr_json_format_value_t; + + +/** JSON document formatting options + * + * These options control the format of JSON document which is + * produced by fr_json_afrom_pair_list(). + * + * The **output_mode** determines the format of JSON that is created: + * + * When JSON_MODE_OBJECT: +@verbatim +{ + "": { + "type":"", + "value":["value0"] + }, + "": { + "type":"", + "value":["value1.0", "value1.1"] + }, + "": { + "type":"", + "value":[2] + }, +} +@endverbatim + * + * When JSON_MODE_OBJECT_SIMPLE: +@verbatim +{ + "":"", + "":["",""], + "":2 +} +@endverbatim + * + * When JSON_MODE_ARRAY: +@verbatim +[ + {"name":"","type":"","value":""}, + {"name":"","type":"","value":""}, + {"name":"","type":"","value":""}, + {"name":"","type":"","value":2} +] +@endverbatim + * + * When JSON_MODE_ARRAY_OF_VALUES: +@verbatim +[ + , + , + , + +] +@endverbatim + * + * When JSON_MODE_ARRAY_OF_NAMES: +@verbatim +[ + , + , + , + +] +@endverbatim + * + */ +struct fr_json_format_s { + char const *output_mode_str; //!< For CONF_PARSER only. + + json_mode_type_t output_mode; //!< Determine the format of JSON document + //!< to generate. + + fr_json_format_attr_t attr; //!< Formatting options for attribute names. + fr_json_format_value_t value; //!< Formatting options for attribute values. + + bool include_type; //!< Include attribute type where possible. +}; + +typedef struct fr_json_format_s fr_json_format_t; + + /* jpath .c */ typedef struct fr_jpath_node fr_jpath_node_t; @@ -68,5 +253,6 @@ size_t fr_json_from_pair(char *out, size_t outlen, VALUE_PAIR const *vp); void fr_json_version_print(void); -char *fr_json_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR **vps, const char *prefix); +char *fr_json_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + fr_json_format_t const *format); #endif diff --git a/src/lib/json/json.c b/src/lib/json/json.c index 65ec26459b72..11d63dd6cc89 100644 --- a/src/lib/json/json.c +++ b/src/lib/json/json.c @@ -20,14 +20,30 @@ * @brief Common functions for working with json-c * * @author Arran Cudbard-Bell + * @author Matthew Newton * * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org) - * @copyright 2015 Network RADIUS SARL (legal@networkradius.com) + * @copyright 2015,2020 Network RADIUS SARL (legal@networkradius.com) * @copyright 2015 The FreeRADIUS Server Project */ #include #include "base.h" +fr_table_num_sorted_t const fr_json_format_table[] = { + { "array", JSON_MODE_ARRAY }, + { "array_of_names", JSON_MODE_ARRAY_OF_NAMES }, + { "array_of_values", JSON_MODE_ARRAY_OF_VALUES }, + { "object", JSON_MODE_OBJECT }, + { "object_simple", JSON_MODE_OBJECT_SIMPLE }, +}; +size_t fr_json_format_table_len = NUM_ELEMENTS(fr_json_format_table); + +static fr_json_format_t const default_json_format = { + .attr = { .prefix = NULL }, + .value = { .value_as_array = true }, +}; + + /** Convert json object to fr_value_box_t * * @param[in] ctx to allocate any value buffers in (should usually be the same as out). @@ -333,123 +349,605 @@ void fr_json_version_print(void) #endif } -/** Returns a JSON string of a list of value pairs + +/** Convert VALUE_PAIR into a JSON object * - * The result is a talloc-ed string, freeing the string is the responsibility - * of the caller. + * If format.value.enum_as_int is set, and the given VP is an enum + * value, the integer value is returned as a json_object rather + * than the text representation. * - * Output format is: -@verbatim + * If format.value.always_string is set then a numeric value pair + * will be returned as a JSON string object. + * + * @param[in] ctx Talloc context. + * @param[out] out returned json object. + * @param[in] vp to get the value of. + * @param[in] format format definition, or NULL. + * @return + * - 1 if 'out' is the integer enum value, 0 otherwise + * - -1 on error. + */ +static int json_afrom_value_box(TALLOC_CTX *ctx, json_object **out, + VALUE_PAIR *vp, fr_json_format_t const *format) { - "":{ - "type":"", - "value":[,,], - "mapping":[,,] - }, - "":{ - "type":"", - "value":[...] - }, - "":{ - "type":"", - "value":[...] - }, + struct json_object *obj; + fr_value_box_t const *vb; + fr_value_box_t vb_str; + int is_enum = 0; + + rad_assert(vp); + + vb = &vp->data; + + if (format && format->value.enum_as_int) { + is_enum = fr_pair_value_enum_box(&vb, vp); + rad_assert(is_enum >= 0); + } + + if (format && format->value.always_string) { + if (fr_value_box_cast(ctx, &vb_str, FR_TYPE_STRING, NULL, vb) == 0) { + vb = &vb_str; + } else { + return -1; + } + } + + MEM(obj = json_object_from_value_box(ctx, vb)); + + if (format && format->value.always_string) { + fr_value_box_clear(&vb_str); + } + + *out = obj; + return is_enum; } -@endverbatim + + +/** Add prefix to attribute name * - * @note Mapping element is only present for attributes with enumerated values. + * If the format "attr.prefix" string is set then prepend this + * to the given attribute name, otherwise return name unchanged. + * + * @param[out] buf where to write the new name, if set + * @param[in] buf_len length of buf + * @param[in] name original attribute name + * @param[in] format json format structure + * @return pointer to name, or buf if the prefix was added + */ +static inline char const *attr_name_with_prefix(char *buf, size_t buf_len, const char *name, fr_json_format_t const *format) +{ + int len; + + if (!format->attr.prefix) return name; + + len = snprintf(buf, buf_len, "%s:%s", format->attr.prefix, name); + + if (len == (int)strlen(buf)) { + return buf; + } + + return name; +} + + +/** Returns a JSON object representation of a list of value pairs + * + * The result is a struct json_object, which should be free'd with + * json_object_put() by the caller. Intended to only be called by + * fr_json_afrom_pair_list(). + * + * This function generates the "object" format, JSON_MODE_OBJECT. + * @see fr_json_format_s * * @param[in] ctx Talloc context. * @param[in] vps a list of value pairs. - * @param[in] prefix The prefix to use, can be NULL to skip the prefix. - * @return JSON string representation of the value pairs + * @param[in] format Formatting control, must be set. + * @return JSON object with the generated representation. */ -char *fr_json_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR **vps, const char *prefix) +static json_object *json_object_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + fr_json_format_t const *format) { fr_cursor_t cursor; - VALUE_PAIR *vp; + VALUE_PAIR *vp; struct json_object *obj; - const char *p; - char *out; char buf[FR_DICT_ATTR_MAX_NAME_LEN + 32]; + /* Check format and type */ + rad_assert(format); + rad_assert(format->output_mode == JSON_MODE_OBJECT); + MEM(obj = json_object_new_object()); - for (vp = fr_cursor_init(&cursor, vps); + for (vp = fr_cursor_init(&cursor, &vps); vp; vp = fr_cursor_next(&cursor)) { - char const *name_with_prefix; - fr_dict_enum_t const *dv; + char const *attr_name; struct json_object *vp_object, *values, *value, *type_name; - name_with_prefix = vp->da->name; - if (prefix) { - int len = snprintf(buf, sizeof(buf), "%s:%s", prefix, vp->da->name); - if (len == (int)strlen(buf)) { - name_with_prefix = buf; - } + /* + * Get attribute name and value. + */ + attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, format); + + if (json_afrom_value_box(ctx, &value, vp, format) < 0) { + fr_strerror_printf("Failed to convert attribute value to JSON object"); + error: + json_object_put(obj); + return NULL; } /* - * See if we already have a key in the table we're working on, - * if we don't, create a new one... + * Look in the table to see if we already have + * a key for the attribute we're working on. */ - if (!json_object_object_get_ex(obj, name_with_prefix, &vp_object)) { + if (!json_object_object_get_ex(obj, attr_name, &vp_object)) { + /* + * Wasn't there, so create a new object for this attribute. + */ MEM(vp_object = json_object_new_object()); - json_object_object_add(obj, name_with_prefix, vp_object); + json_object_object_add(obj, attr_name, vp_object); - MEM(type_name = json_object_new_string(fr_table_str_by_value(fr_value_box_type_table, vp->vp_type, ""))); + /* + * Add "type" to newly created keys. + */ + MEM(type_name = json_object_new_string(fr_table_str_by_value(fr_value_box_type_table, + vp->vp_type, ""))); json_object_object_add(vp_object, "type", type_name); - MEM(values = json_object_new_array()); - json_object_object_add(vp_object, "value", values); + /* + * Create a "value" array to hold any attribute values for this attribute... + */ + if (format->value.value_as_array) { + MEM(values = json_object_new_array()); + json_object_object_add(vp_object, "value", values); + } else { + /* + * ...unless this is the first time we've seen the attribute and + * value_as_array is false, in which case just add the value directly + * and move on to the next attribute. + */ + json_object_object_add(vp_object, "value", value); + continue; + } + } else { + /* + * Find the 'values' array to add the current value to. + */ + if (!fr_cond_assert(json_object_object_get_ex(vp_object, "value", &values))) { + fr_strerror_printf("Inconsistent JSON tree"); + goto error; + } + + /* + * If value_as_array is no set then "values" may not be an array, so it will + * need converting to an array to add this extra attribute. + */ + if (!format->value.value_as_array) { + json_type type; + struct json_object *convert_value = values; + + /* Check "values" type */ + type = json_object_get_type(values); + + /* It wasn't an array, so turn it into one with the old value as the first entry */ + if (type != json_type_array) { + MEM(values = json_object_new_array()); + json_object_array_add(values, json_object_get(convert_value)); + json_object_object_del(vp_object, "value"); + json_object_object_add(vp_object, "value", values); + } + } + } + + /* + * Append to the JSON array. + */ + json_object_array_add(values, value); + } + + return obj; +} + + +/** Returns a JSON object representation of a list of value pairs + * + * The result is a struct json_object, which should be free'd with + * json_object_put() by the caller. Intended to only be called by + * fr_json_afrom_pair_list(). + * + * This function generates the "simple object" format, JSON_MODE_OBJECT_SIMPLE. + * @see fr_json_format_s + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] format Formatting control, must be set. + * @return JSON object with the generated representation. + */ +static json_object *json_smplobj_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + fr_json_format_t const *format) +{ + fr_cursor_t cursor; + VALUE_PAIR *vp; + struct json_object *obj; + char buf[FR_DICT_ATTR_MAX_NAME_LEN + 32]; + json_type type; + + /* Check format and type */ + rad_assert(format); + rad_assert(format->output_mode == JSON_MODE_OBJECT_SIMPLE); + + MEM(obj = json_object_new_object()); + + for (vp = fr_cursor_init(&cursor, &vps); + vp; + vp = fr_cursor_next(&cursor)) { + char const *attr_name; + struct json_object *vp_object, *values, *value; + bool add_single = false; + /* - * If we do, get its value array... + * Get attribute name and value. */ - } else if (!fr_cond_assert(json_object_object_get_ex(vp_object, "value", &values))) { - fr_strerror_printf("Inconsistent JSON tree"); + attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, format); + + if (json_afrom_value_box(ctx, &value, vp, format) < 0) { + fr_strerror_printf("Failed to convert attribute value to JSON object"); json_object_put(obj); + return NULL; + } + + /* + * See if we already have a key in the table we're working on, + * if not then create a new one. + */ + if (!json_object_object_get_ex(obj, attr_name, &vp_object)) { + if (format->value.value_as_array) { + /* + * We have been asked to ensure /all/ values are lists, + * even if there's only one attribute. + */ + MEM(values = json_object_new_array()); + json_object_object_add(obj, attr_name, values); + } else { + /* + * Deal with it later on. + */ + add_single = true; + } + /* + * If we do have the key already, get its value array. + */ + } else { + type = json_object_get_type(vp_object); + + if (type == json_type_array) { + values = vp_object; + } else { + /* + * We've seen one of these before, but didn't add + * it as an array the first time. Sort that out. + */ + MEM(values = json_object_new_array()); + json_object_array_add(values, json_object_get(vp_object)); + json_object_object_del(obj, attr_name); + json_object_object_add(obj, attr_name, values); + } + } + + if (add_single) { + /* + * Only ever used the first time adding a new + * attribute when "value_as_array" is not set. + */ + json_object_object_add(obj, attr_name, value); + } else { + /* + * Otherwise we're always appending to a JSON array. + */ + json_object_array_add(values, value); + } + } + + return obj; +} + + +/** Returns a JSON array representation of a list of value pairs + * + * The result is a struct json_object, which should be free'd with + * json_object_put() by the caller. Intended to only be called by + * fr_json_afrom_pair_list(). + * + * This function generates the "array" format, JSON_MODE_ARRAY. + * @see fr_json_format_s + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] format Formatting control, must be set. + * @return JSON object with the generated representation. + */ +static struct json_object *json_array_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + fr_json_format_t const *format) +{ + fr_cursor_t cursor; + VALUE_PAIR *vp; + struct json_object *obj; + struct json_object *seen_attributes; + char buf[FR_DICT_ATTR_MAX_NAME_LEN + 32]; + + /* Check format and type */ + rad_assert(format); + rad_assert(format->output_mode == JSON_MODE_ARRAY); + + MEM(obj = json_object_new_array()); + /* + * If attribute values should be in a list format, then keep track + * of the attributes we've previously seen in a JSON object. + */ + if (format->value.value_as_array) { + seen_attributes = json_object_new_object(); + } + + for (vp = fr_cursor_init(&cursor, &vps); + vp; + vp = fr_cursor_next(&cursor)) { + char const *attr_name; + struct json_object *values, *name, *value, *type_name; + struct json_object *attrobj = NULL; + bool already_seen = false; + + /* + * Get attribute name and value. + */ + attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, format); + + if (json_afrom_value_box(ctx, &value, vp, format) < 0) { + fr_strerror_printf("Failed to convert attribute value to JSON object"); + json_object_put(obj); return NULL; } - MEM(value = json_object_from_value_box(ctx, &vp->data)); - json_object_array_add(values, value); + if (format->value.value_as_array) { + /* + * Try and find this attribute in the "seen_attributes" object. If it is + * there then get the "values" array to add this attribute value to. + */ + already_seen = json_object_object_get_ex(seen_attributes, attr_name, &values); + } /* - * Add a mapping array + * If we're adding all attributes to the toplevel array, or we're adding values + * to an array of an existing attribute but haven't seen it before, then we need + * to create a new JSON object for this attribute. */ - if (vp->da->flags.has_value) { - struct json_object *mapping; + if (!format->value.value_as_array || !already_seen) { + /* + * Create object and add it to top-level array + */ + MEM(attrobj = json_object_new_object()); + json_object_array_add(obj, attrobj); - if (!json_object_object_get_ex(vp_object, "mapping", &mapping)) { - MEM(mapping = json_object_new_array()); - json_object_object_add(vp_object, "mapping", mapping); - } + /* + * Add the attribute name in the "name" key and the type in the "type" key + */ + MEM(name = json_object_new_string(attr_name)); + json_object_object_add(attrobj, "name", name); - dv = fr_dict_enum_by_value(vp->da, &vp->data); - if (dv) { - struct json_object *mapped_value; + MEM(type_name = json_object_new_string(fr_table_str_by_value(fr_value_box_type_table, + vp->vp_type, ""))); + json_object_object_add(attrobj, "type", type_name); + } - /* Add to mapping array */ - MEM(mapped_value = json_object_from_value_box(ctx, dv->value)); - json_object_array_add(mapping, mapped_value); + if (format->value.value_as_array) { /* - * Add NULL value to mapping array + * We're adding values to an array for the first copy of this attribute + * that we saw. First time around we need to create an array. */ - } else { - if (json_object_object_get_ex(vp_object, "mapping", &mapping)) { - json_object_array_add(mapping, NULL); - } + if (!already_seen) { + MEM(values = json_object_new_array()); + /* + * Add "value":[] key to the attribute object + */ + json_object_object_add(attrobj, "value", values); + + /* + * Also add to "seen_attributes" to check later + */ + json_object_object_add(seen_attributes, attr_name, json_object_get(values)); } + + /* + * Always add the value to the respective "values" array. + */ + json_object_array_add(values, value); + } else { + /* + * This is simpler; just add a "value": key to the attribute object. + */ + json_object_object_add(attrobj, "value", value); + } + + } + + /* + * No longer need the "seen_attributes" object, it was just used for tracking. + */ + if (format->value.value_as_array) { + json_object_put(seen_attributes); + } + + return obj; +} + + +/** Returns a JSON array of a list of value pairs + * + * The result is a struct json_object, which should be free'd with + * json_object_put() by the caller. Intended to only be called by + * fr_json_afrom_pair_list(). + * + * This function generates the "array_of_values" format, + * JSON_MODE_ARRAY_OF_VALUES, listing just the attribute values. + * @see fr_json_format_s + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] format Formatting control, must be set. + * @return JSON object with the generated representation. + */ +static struct json_object *json_value_array_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + fr_json_format_t const *format) +{ + fr_cursor_t cursor; + VALUE_PAIR *vp; + struct json_object *obj; + + /* Check format and type */ + rad_assert(format); + rad_assert(format->output_mode == JSON_MODE_ARRAY_OF_VALUES); + + MEM(obj = json_object_new_array()); + + /* + * This array format is very simple - just add all the + * attribute values to the array in order. + */ + for (vp = fr_cursor_init(&cursor, &vps); + vp; + vp = fr_cursor_next(&cursor)) { + struct json_object *value; + + if (json_afrom_value_box(ctx, &value, vp, format) < 0) { + fr_strerror_printf("Failed to convert attribute value to JSON object"); + json_object_put(obj); + return NULL; } + + json_object_array_add(obj, value); + } + + return obj; +} + + +/** Returns a JSON array of a list of value pairs + * + * The result is a struct json_object, which should be free'd with + * json_object_put() by the caller. Intended to only be called by + * fr_json_afrom_pair_list(). + * + * This function generates the "array_of_names" format, + * JSON_MODE_ARRAY_OF_NAMES, listing just the attribute names. + * @see fr_json_format_s + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] format Formatting control, must be set. + * @return JSON object with the generated representation. + */ +static struct json_object *json_attr_array_afrom_pair_list(UNUSED TALLOC_CTX *ctx, VALUE_PAIR *vps, + fr_json_format_t const *format) +{ + fr_cursor_t cursor; + VALUE_PAIR *vp; + struct json_object *obj; + char buf[FR_DICT_ATTR_MAX_NAME_LEN + 32]; + + /* Check format and type */ + rad_assert(format); + rad_assert(format->output_mode == JSON_MODE_ARRAY_OF_NAMES); + + MEM(obj = json_object_new_array()); + + /* + * Add all the attribute names to the array in order. + */ + for (vp = fr_cursor_init(&cursor, &vps); + vp; + vp = fr_cursor_next(&cursor)) { + char const *attr_name; + struct json_object *value; + + attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, format); + value = json_object_new_string(attr_name); + + json_object_array_add(obj, value); + } + + return obj; +} + + +/** Returns a JSON string of a list of value pairs + * + * The result is a talloc-ed string, freeing the string is + * the responsibility of the caller. + * + * The 'format' struct contains settings to configure the output + * JSON document format. + * @see fr_json_format_s + * + * Default output, when format is NULL, is: +@verbatim +{ + "":{ + "type":"", + "value":[,,] + }, + "":{ + "type":"", + "value":[...] + }, + "":{ + "type":"", + "value":[...] + } +} +@endverbatim + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] format Formatting control, can be NULL to use default format. + * @return JSON string representation of the value pairs + */ +char *fr_json_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + fr_json_format_t const *format) +{ + struct json_object *obj; + const char *p; + char *out; + + if (!format) format = &default_json_format; + + switch (format->output_mode) { + case JSON_MODE_OBJECT: + MEM(obj = json_object_afrom_pair_list(ctx, vps, format)); + break; + case JSON_MODE_OBJECT_SIMPLE: + MEM(obj = json_smplobj_afrom_pair_list(ctx, vps, format)); + break; + case JSON_MODE_ARRAY: + MEM(obj = json_array_afrom_pair_list(ctx, vps, format)); + break; + case JSON_MODE_ARRAY_OF_VALUES: + MEM(obj = json_value_array_afrom_pair_list(ctx, vps, format)); + break; + case JSON_MODE_ARRAY_OF_NAMES: + MEM(obj = json_attr_array_afrom_pair_list(ctx, vps, format)); + break; + default: + /* This should never happen */ + rad_assert(0); } MEM(p = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN)); MEM(out = talloc_strdup(ctx, p)); - json_object_put(obj); /* Should also free string buff from above */ + /* + * Free the JSON structure, it's not needed any more + */ + json_object_put(obj); return out; } - diff --git a/src/lib/util/pair.c b/src/lib/util/pair.c index 6d43a7323ba8..45de88a09929 100644 --- a/src/lib/util/pair.c +++ b/src/lib/util/pair.c @@ -2656,6 +2656,32 @@ char const *fr_pair_value_enum(VALUE_PAIR const *vp, char buff[20]) return str; } +/** Get value box of a VP, optionally prefer enum value. + * + * Get the data value box of the given VP. If 'e' is set to 1 and the VP has an + * enum value, this will be returned instead. Otherwise it will be set to the + * value box of the VP itself. + * + * @param[out] out pointer to a value box. + * @param[in] vp to print. + * @return 1 if the enum value has been used, 0 otherwise, -1 on error. + */ +int fr_pair_value_enum_box(fr_value_box_t const **out, VALUE_PAIR *vp) +{ + fr_dict_enum_t const *dv; + + if (!out || !vp ) return -1; + + if (vp->da && vp->da->flags.has_value && + (dv = fr_dict_enum_by_value(vp->da, &vp->data))) { + *out = dv->value; + return 1; + } + + *out = &vp->data; + return 0; +} + char *fr_pair_type_asprint(TALLOC_CTX *ctx, fr_type_t type) { switch (type) { diff --git a/src/lib/util/pair.h b/src/lib/util/pair.h index 74aaa0fbaaa8..d4aed8146519 100644 --- a/src/lib/util/pair.h +++ b/src/lib/util/pair.h @@ -316,6 +316,7 @@ void fr_pair_value_snprintf(VALUE_PAIR *vp, char const *fmt, ...) CC_HINT(forma size_t fr_pair_value_snprint(char *out, size_t outlen, VALUE_PAIR const *vp, char quote); char *fr_pair_value_asprint(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote); char const *fr_pair_value_enum(VALUE_PAIR const *vp, char buff[static 20]); +int fr_pair_value_enum_box(fr_value_box_t const **out, VALUE_PAIR *vp); size_t fr_pair_snprint(char *out, size_t outlen, VALUE_PAIR const *vp); void fr_pair_fprint(FILE *, VALUE_PAIR const *vp); diff --git a/src/modules/rlm_rest/rest.c b/src/modules/rlm_rest/rest.c index 92325a7ddd66..73f220513fd1 100644 --- a/src/modules/rlm_rest/rest.c +++ b/src/modules/rlm_rest/rest.c @@ -562,7 +562,7 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd rad_assert(freespace > 0); if (ctx->state == READ_STATE_INIT) { - encoded = fr_json_afrom_pair_list(data, &request->packet->vps, NULL); + encoded = fr_json_afrom_pair_list(data, request->packet->vps, NULL); if (!encoded) return -1; data->start = data->p = encoded;