/src/freeradius-server/src/lib/json/json.c
Line | Count | Source |
1 | | /* |
2 | | * This program 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: 1c0db6c5857b81925cd5cc19da95f7f652bb4d4b $ |
19 | | * @file json.c |
20 | | * @brief Common functions for working with json-c |
21 | | * |
22 | | * @author Arran Cudbard-Bell |
23 | | * @author Matthew Newton |
24 | | * |
25 | | * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
26 | | * @copyright 2015,2020 Network RADIUS SAS (legal@networkradius.com) |
27 | | * @copyright 2015 The FreeRADIUS Server Project |
28 | | */ |
29 | | #include <freeradius-devel/util/debug.h> |
30 | | #include <freeradius-devel/util/base16.h> |
31 | | #include <freeradius-devel/util/base64.h> |
32 | | #include <freeradius-devel/util/types.h> |
33 | | #include <freeradius-devel/util/value.h> |
34 | | #include "base.h" |
35 | | #include "lib/util/strerror.h" |
36 | | |
37 | | fr_table_num_sorted_t const fr_json_format_table[] = { |
38 | | { L("array"), JSON_MODE_ARRAY }, |
39 | | { L("array_of_names"), JSON_MODE_ARRAY_OF_NAMES }, |
40 | | { L("array_of_values"), JSON_MODE_ARRAY_OF_VALUES }, |
41 | | { L("object"), JSON_MODE_OBJECT }, |
42 | | { L("object_simple"), JSON_MODE_OBJECT_SIMPLE }, |
43 | | }; |
44 | | size_t fr_json_format_table_len = NUM_ELEMENTS(fr_json_format_table); |
45 | | |
46 | | fr_table_num_sorted_t const fr_json_binary_format_table[] = { |
47 | | { L("base16"), JSON_BINARY_FORMAT_BASE16 }, |
48 | | { L("base64"), JSON_BINARY_FORMAT_BASE64 }, |
49 | | { L("raw"), JSON_BINARY_FORMAT_RAW }, |
50 | | }; |
51 | | size_t fr_json_binary_format_table_len = NUM_ELEMENTS(fr_json_binary_format_table); |
52 | | |
53 | | static fr_json_format_t const default_json_format = { |
54 | | .attr = { .prefix = NULL }, |
55 | | .value = { .value_is_always_array = true }, |
56 | | .output_mode = JSON_MODE_OBJECT |
57 | | }; |
58 | | |
59 | | static conf_parser_t const json_format_attr_config[] = { |
60 | | { FR_CONF_OFFSET("prefix", fr_json_format_attr_t, prefix) }, |
61 | | CONF_PARSER_TERMINATOR |
62 | | }; |
63 | | |
64 | | static conf_parser_t const json_format_value_config[] = { |
65 | | { FR_CONF_OFFSET("single_value_as_array", fr_json_format_value_t, value_is_always_array), .dflt = "no" }, |
66 | | { FR_CONF_OFFSET("enum_as_integer", fr_json_format_value_t, enum_as_int), .dflt = "no" }, |
67 | | { FR_CONF_OFFSET("always_string", fr_json_format_value_t, always_string), .dflt = "no" }, |
68 | | { FR_CONF_OFFSET("binary_format", fr_json_format_value_t, binary_format), .dflt = "raw", |
69 | | .func = cf_table_parse_int, |
70 | | .uctx = &(cf_table_parse_ctx_t){ .table = fr_json_binary_format_table, .len = &fr_json_binary_format_table_len } }, |
71 | | CONF_PARSER_TERMINATOR |
72 | | }; |
73 | | |
74 | | conf_parser_t const fr_json_format_config[] = { |
75 | | { FR_CONF_OFFSET("output_mode", fr_json_format_t, output_mode_str), .dflt = "object" }, |
76 | | { FR_CONF_OFFSET_SUBSECTION("attribute", 0, fr_json_format_t, attr, json_format_attr_config) }, |
77 | | { FR_CONF_OFFSET_SUBSECTION("value", 0, fr_json_format_t, value, json_format_value_config) }, |
78 | | |
79 | | CONF_PARSER_TERMINATOR |
80 | | }; |
81 | | |
82 | | static inline CC_HINT(always_inline) |
83 | | void json_object_put_assert(json_object *obj) |
84 | 0 | { |
85 | 0 | int ret; |
86 | |
|
87 | 0 | ret = json_object_put(obj); |
88 | 0 | if (ret == 1) return; |
89 | | |
90 | 0 | fr_assert_fail("json_object_put did not free object (returned %d), likely leaking memory", ret); |
91 | 0 | } |
92 | | |
93 | | /** Convert json object to fr_value_box_t |
94 | | * |
95 | | * @param[in] ctx to allocate any value buffers in (should usually be the same as out). |
96 | | * @param[in] out Where to write value. Must be initialised. |
97 | | * @param[in] object to convert. |
98 | | * @param[in] enumv Any string values are assumed to be in PRESENTATION format, meaning |
99 | | * that if an enumv is specified, they'll be checked against the list |
100 | | * of aliases for that enumeration, and possibly converted into one of |
101 | | * the enumeration values (which may not be a string). |
102 | | * @param[in] tainted Whether the data source is untrusted. |
103 | | * @return |
104 | | * - 0 on success. |
105 | | * - -1 on failure. |
106 | | */ |
107 | | int fr_json_object_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *out, json_object *object, |
108 | | fr_dict_attr_t const *enumv, bool tainted) |
109 | 0 | { |
110 | 0 | switch (json_object_get_type(object)) { |
111 | 0 | case json_type_string: |
112 | 0 | { |
113 | 0 | char const *value; |
114 | 0 | size_t len; |
115 | 0 | fr_dict_enum_value_t const *found; |
116 | |
|
117 | 0 | value = json_object_get_string(object); |
118 | 0 | len = json_object_get_string_len(object); |
119 | |
|
120 | 0 | if (!enumv) goto no_enumv; |
121 | | |
122 | 0 | if (fr_dict_valid_name(value, len) < 0) goto no_enumv; |
123 | | |
124 | | /* |
125 | | * If an alias exists, use that value instead |
126 | | */ |
127 | 0 | found = fr_dict_enum_by_name(enumv, value, len); |
128 | 0 | if (found) { |
129 | 0 | if (unlikely(fr_value_box_copy(ctx, out, found->value)) < 0) return -1; |
130 | 0 | return 0; |
131 | 0 | } |
132 | | |
133 | 0 | no_enumv: |
134 | | /* |
135 | | * Just copy the string to the box. |
136 | | */ |
137 | 0 | if (fr_value_box_bstrndup(out, out, NULL, value, len, tainted) < 0) return -1; |
138 | 0 | } |
139 | 0 | break; |
140 | | |
141 | 0 | case json_type_double: |
142 | 0 | fr_value_box(out, json_object_get_double(object), tainted); |
143 | 0 | break; |
144 | | |
145 | 0 | case json_type_int: |
146 | 0 | { |
147 | 0 | #ifdef HAVE_JSON_OBJECT_GET_INT64 |
148 | 0 | int64_t num; |
149 | | #else |
150 | | int32_t num; |
151 | | #endif |
152 | |
|
153 | | #ifndef HAVE_JSON_OBJECT_GET_INT64 |
154 | | num = json_object_get_int(object); |
155 | | #else |
156 | 0 | num = json_object_get_int64(object); |
157 | 0 | if (num < INT32_MIN) { /* 64bit signed*/ |
158 | 0 | fr_value_box(out, (int64_t)num, tainted); |
159 | 0 | } else if (num > UINT32_MAX) { /* 64bit unsigned */ |
160 | 0 | fr_value_box(out, (uint64_t)num, tainted); |
161 | 0 | } else |
162 | 0 | #endif |
163 | 0 | if (num < INT16_MIN) { /* 32bit signed */ |
164 | 0 | fr_value_box(out, (int32_t)num, tainted); |
165 | 0 | } else if (num < INT8_MIN) { /* 16bit signed */ |
166 | 0 | fr_value_box(out, (int16_t)num, tainted); |
167 | 0 | } else if (num < 0) { /* 8bit signed */ |
168 | 0 | fr_value_box(out, (int8_t)num, tainted); |
169 | 0 | } else if (num > UINT16_MAX) { /* 32bit unsigned */ |
170 | 0 | fr_value_box(out, (uint32_t)num, tainted); |
171 | 0 | } else if (num > UINT8_MAX) { /* 16bit unsigned */ |
172 | 0 | fr_value_box(out, (uint16_t)num, tainted); |
173 | 0 | } else { /* 8bit unsigned */ |
174 | 0 | fr_value_box(out, (uint8_t)num, tainted); |
175 | 0 | } |
176 | 0 | } |
177 | 0 | break; |
178 | | |
179 | 0 | case json_type_boolean: |
180 | | /* Must be cast to bool for correct generic case selection */ |
181 | 0 | fr_value_box(out, ((bool)(json_object_get_boolean(object) > 0)), tainted); |
182 | 0 | break; |
183 | | |
184 | 0 | case json_type_null: |
185 | 0 | case json_type_array: |
186 | 0 | case json_type_object: |
187 | 0 | { |
188 | 0 | char const *value = json_object_to_json_string(object); |
189 | |
|
190 | 0 | if (fr_value_box_bstrndup(out, out, NULL, value, strlen(value), tainted) < 0) return -1; |
191 | 0 | } |
192 | 0 | break; |
193 | 0 | } |
194 | | |
195 | 0 | out->tainted = tainted; |
196 | |
|
197 | 0 | return 0; |
198 | 0 | } |
199 | | |
200 | | /** Convert boxed value_box to a JSON object |
201 | | * |
202 | | * @param[in] data to convert. |
203 | | */ |
204 | | json_object *json_object_from_value_box(fr_value_box_t const *data) |
205 | 0 | { |
206 | | /* |
207 | | * We're converting to PRESENTATION format |
208 | | * so any attributes with enumeration values |
209 | | * should be converted to string types. |
210 | | */ |
211 | 0 | if (data->enumv) { |
212 | 0 | fr_dict_enum_value_t const *enumv; |
213 | |
|
214 | 0 | enumv = fr_dict_enum_by_value(data->enumv, data); |
215 | 0 | if (enumv) return json_object_new_string(enumv->name); |
216 | 0 | } |
217 | | |
218 | 0 | switch (data->type) { |
219 | 0 | default: |
220 | 0 | do_string: |
221 | 0 | { |
222 | 0 | char buffer[64]; |
223 | 0 | fr_sbuff_t sbuff = FR_SBUFF_OUT(buffer, sizeof(buffer)); |
224 | |
|
225 | 0 | if (fr_value_box_print(&sbuff, data, NULL) <= 0) return NULL; |
226 | | |
227 | 0 | return json_object_new_string_len(buffer, fr_sbuff_used(&sbuff)); |
228 | 0 | } |
229 | | |
230 | 0 | case FR_TYPE_STRING: |
231 | 0 | return json_object_new_string_len(data->vb_strvalue, data->vb_length); |
232 | | |
233 | 0 | case FR_TYPE_OCTETS: |
234 | 0 | return json_object_new_string_len((char const *)data->vb_octets, data->vb_length); |
235 | | |
236 | 0 | case FR_TYPE_BOOL: |
237 | 0 | return json_object_new_boolean(data->vb_bool); |
238 | | |
239 | 0 | case FR_TYPE_UINT8: |
240 | 0 | return json_object_new_int(data->vb_uint8); |
241 | | |
242 | 0 | case FR_TYPE_UINT16: |
243 | 0 | return json_object_new_int(data->vb_uint16); |
244 | | |
245 | 0 | #ifdef HAVE_JSON_OBJECT_GET_INT64 |
246 | 0 | case FR_TYPE_UINT32: |
247 | 0 | return json_object_new_int64((int64_t)data->vb_uint32); /* uint32_t (max) > int32_t (max) */ |
248 | | |
249 | 0 | case FR_TYPE_UINT64: |
250 | 0 | if (data->vb_uint64 > INT64_MAX) goto do_string; |
251 | 0 | return json_object_new_int64(data->vb_uint64); |
252 | | #else |
253 | | case FR_TYPE_UINT32: |
254 | | if (data->vb_uint32 > INT32_MAX) goto do_string; |
255 | | return json_object_new_int(data->vb_uint32); |
256 | | #endif |
257 | | |
258 | 0 | case FR_TYPE_INT8: |
259 | 0 | return json_object_new_int(data->vb_int8); |
260 | | |
261 | 0 | case FR_TYPE_INT16: |
262 | 0 | return json_object_new_int(data->vb_int16); |
263 | | |
264 | 0 | case FR_TYPE_INT32: |
265 | 0 | return json_object_new_int(data->vb_int32); |
266 | | |
267 | 0 | #ifdef HAVE_JSON_OBJECT_GET_INT64 |
268 | 0 | case FR_TYPE_INT64: |
269 | 0 | return json_object_new_int64(data->vb_int64); |
270 | | |
271 | 0 | case FR_TYPE_SIZE: |
272 | 0 | return json_object_new_int64(data->vb_size); |
273 | 0 | #endif |
274 | | |
275 | 0 | case FR_TYPE_STRUCTURAL: |
276 | 0 | fr_strerror_const("Can't convert structural type to JSON"); |
277 | 0 | return NULL; |
278 | 0 | } |
279 | 0 | } |
280 | | |
281 | | /** Print a value box as its equivalent JSON format without going via a struct json_object (in most cases) |
282 | | * |
283 | | * @param[out] out buffer to write to. |
284 | | * @param[in] vb to print. |
285 | | * @param[in] include_quotes whether we should wrap string values, |
286 | | * or non-native types like IPv4 addresses in quotes. |
287 | | * @return |
288 | | * - <0 on error. |
289 | | * - >= number of bytes written. |
290 | | */ |
291 | | fr_slen_t fr_json_str_from_value(fr_sbuff_t *out, fr_value_box_t const *vb, bool include_quotes) |
292 | 0 | { |
293 | 0 | fr_sbuff_t our_out = FR_SBUFF(out); |
294 | |
|
295 | 0 | switch (vb->type) { |
296 | 0 | case FR_TYPE_NULL: |
297 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "null"); |
298 | 0 | break; |
299 | | |
300 | 0 | case FR_TYPE_BOOL: |
301 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, vb->vb_bool ? "true" : "false"); |
302 | 0 | break; |
303 | | |
304 | | /* |
305 | | * This is identical to JSON-C's escaping function |
306 | | * but we avoid creating JSON objects just to be able |
307 | | * to escape strings. |
308 | | */ |
309 | 0 | case FR_TYPE_STRING: |
310 | 0 | case FR_TYPE_OCTETS: |
311 | 0 | { |
312 | 0 | char const *last_app, *p, *end; |
313 | |
|
314 | 0 | if (include_quotes) FR_SBUFF_IN_CHAR_RETURN(&our_out, '"'); |
315 | | |
316 | 0 | last_app = p = vb->vb_strvalue; |
317 | 0 | end = p + vb->vb_length; |
318 | |
|
319 | 0 | while (p < end) { |
320 | 0 | if ((*p < ' ') || (*p == '"') || (*p == '\\') || (*p == '/')) { |
321 | 0 | if (p > last_app) FR_SBUFF_IN_BSTRNCPY_RETURN(&our_out, last_app, p - last_app); |
322 | | |
323 | 0 | switch (*p) { |
324 | 0 | case '\b': |
325 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "\\b"); |
326 | 0 | break; |
327 | | |
328 | 0 | case '\n': |
329 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "\\n"); |
330 | 0 | break; |
331 | | |
332 | 0 | case '\r': |
333 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "\\r"); |
334 | 0 | break; |
335 | | |
336 | 0 | case '\t': |
337 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "\\t"); |
338 | 0 | break; |
339 | | |
340 | 0 | case '\f': |
341 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "\\f"); |
342 | 0 | break; |
343 | | |
344 | 0 | case '"': |
345 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "\\\""); |
346 | 0 | break; |
347 | | |
348 | 0 | case '\\': |
349 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "\\\\"); |
350 | 0 | break; |
351 | | |
352 | 0 | case '/': |
353 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "\\/"); |
354 | 0 | break; |
355 | | |
356 | 0 | default: |
357 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "\\u00"); |
358 | 0 | fr_base16_encode(&our_out, &FR_DBUFF_TMP((uint8_t const *)p, 1)); |
359 | 0 | } |
360 | | |
361 | 0 | last_app = p + 1; |
362 | 0 | } |
363 | 0 | p++; |
364 | 0 | } |
365 | 0 | if (end > last_app) FR_SBUFF_IN_BSTRNCPY_RETURN(&our_out, last_app, end - last_app); |
366 | 0 | if (include_quotes) FR_SBUFF_IN_CHAR_RETURN(&our_out, '"'); |
367 | 0 | } |
368 | 0 | break; |
369 | | |
370 | 0 | case FR_TYPE_UINT8: |
371 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%u", vb->vb_uint8); |
372 | 0 | break; |
373 | | |
374 | 0 | case FR_TYPE_UINT16: |
375 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%u", vb->vb_uint16); |
376 | 0 | break; |
377 | | |
378 | 0 | case FR_TYPE_UINT32: |
379 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%u", vb->vb_uint32); |
380 | 0 | break; |
381 | | |
382 | 0 | case FR_TYPE_UINT64: |
383 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%" PRIu64, vb->vb_uint64); |
384 | 0 | break; |
385 | | |
386 | 0 | case FR_TYPE_INT8: |
387 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%i", vb->vb_int8); |
388 | 0 | break; |
389 | | |
390 | 0 | case FR_TYPE_INT16: |
391 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%i", vb->vb_int16); |
392 | 0 | break; |
393 | | |
394 | 0 | case FR_TYPE_INT32: |
395 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%i", vb->vb_int32); |
396 | 0 | break; |
397 | | |
398 | 0 | case FR_TYPE_INT64: |
399 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%" PRIi64, vb->vb_int64); |
400 | 0 | break; |
401 | | |
402 | 0 | case FR_TYPE_SIZE: |
403 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%zu", vb->vb_size); |
404 | 0 | break; |
405 | | |
406 | | /* |
407 | | * It's too complex to replicate the float/double printing |
408 | | * here, so pass it off to JSON-C's printing functions. |
409 | | */ |
410 | 0 | case FR_TYPE_FLOAT32: |
411 | 0 | { |
412 | 0 | struct json_object *obj; |
413 | 0 | fr_slen_t slen; |
414 | |
|
415 | 0 | obj = json_object_new_double((double)vb->vb_float32); |
416 | 0 | if (unlikely(obj == NULL)) return -1; |
417 | 0 | slen = fr_sbuff_in_strcpy(&our_out, json_object_to_json_string(obj)); |
418 | 0 | json_object_put_assert(obj); |
419 | |
|
420 | 0 | if (slen < 0) return slen; |
421 | 0 | break; |
422 | 0 | } |
423 | | |
424 | 0 | case FR_TYPE_FLOAT64: |
425 | 0 | { |
426 | 0 | struct json_object *obj; |
427 | 0 | fr_slen_t slen; |
428 | |
|
429 | 0 | obj = json_object_new_double((double)vb->vb_float64); |
430 | 0 | if (unlikely(obj == NULL)) return -1; |
431 | 0 | slen = fr_sbuff_in_strcpy(&our_out, json_object_to_json_string(obj)); |
432 | 0 | json_object_put_assert(obj); |
433 | |
|
434 | 0 | if (slen < 0) return slen; |
435 | 0 | break; |
436 | 0 | } |
437 | | |
438 | 0 | case FR_TYPE_IPV4_ADDR: |
439 | 0 | case FR_TYPE_IPV4_PREFIX: |
440 | 0 | case FR_TYPE_IPV6_ADDR: |
441 | 0 | case FR_TYPE_IPV6_PREFIX: |
442 | 0 | case FR_TYPE_COMBO_IP_ADDR: |
443 | 0 | case FR_TYPE_COMBO_IP_PREFIX: |
444 | 0 | case FR_TYPE_IFID: |
445 | 0 | case FR_TYPE_ETHERNET: |
446 | 0 | case FR_TYPE_DATE: |
447 | 0 | case FR_TYPE_TIME_DELTA: |
448 | 0 | case FR_TYPE_ATTR: |
449 | 0 | { |
450 | 0 | fr_slen_t slen; |
451 | |
|
452 | 0 | if (include_quotes) FR_SBUFF_IN_CHAR_RETURN(&our_out, '"'); |
453 | 0 | slen = fr_value_box_print(&our_out, vb, NULL); |
454 | 0 | if (include_quotes) FR_SBUFF_IN_CHAR_RETURN(&our_out, '"'); |
455 | | /* Intentionally after writing second dquote, so the buffer is always has a properly terminated string */ |
456 | 0 | if (slen < 0) return slen; |
457 | 0 | } |
458 | 0 | break; |
459 | | |
460 | 0 | case FR_TYPE_STRUCTURAL: |
461 | 0 | fr_strerror_const("Structural boxes not yet supported"); |
462 | 0 | return -1; |
463 | | |
464 | 0 | case FR_TYPE_INTERNAL: |
465 | 0 | fr_strerror_printf("Box type %s cannot be converted to string", fr_type_to_str(vb->type)); |
466 | 0 | return -1; |
467 | 0 | } |
468 | | |
469 | 0 | return fr_sbuff_set(out, &our_out); |
470 | 0 | } |
471 | | |
472 | | /** Print JSON-C version |
473 | | * |
474 | | */ |
475 | | void fr_json_version_print(void) |
476 | 0 | { |
477 | 0 | #ifdef HAVE_JSON_C_VERSION |
478 | 0 | INFO("libfreeradius-json: json-c version: %s", json_c_version()); |
479 | | #else |
480 | | INFO("libfreeradius-json: json-c version: Unknown (less than 0.10) - Please upgrade"); |
481 | | #endif |
482 | 0 | } |
483 | | |
484 | | |
485 | | /** Convert a value box into a JSON object |
486 | | * |
487 | | * If format.value.always_string is set then a numeric value |
488 | | * will be returned as a JSON string object. |
489 | | * |
490 | | * @param[in] ctx Talloc context. |
491 | | * @param[out] out returned json object. |
492 | | * @param[in] vb to get the value of. |
493 | | * @param[in] format format definition, or NULL. |
494 | | * @return |
495 | | * - 0 on success. |
496 | | * - -1 on error. |
497 | | */ |
498 | | static inline CC_HINT(always_inline) |
499 | | int json_afrom_value_box(TALLOC_CTX *ctx, json_object **out, |
500 | | fr_value_box_t const *vb, fr_json_format_t const *format) |
501 | | { |
502 | | if (!format) { |
503 | | MEM(*out = json_object_from_value_box(vb)); |
504 | | return 0; |
505 | | } |
506 | | |
507 | | /* |
508 | | * Evaluated before "always_string". |
509 | | */ |
510 | | if (vb->type == FR_TYPE_OCTETS) { |
511 | | switch (format->value.binary_format) { |
512 | | case JSON_BINARY_FORMAT_BASE16: |
513 | | case JSON_BINARY_FORMAT_BASE64: |
514 | | { |
515 | | fr_sbuff_t *encoded; |
516 | | FR_SBUFF_TALLOC_THREAD_LOCAL(&encoded, 256, SIZE_MAX); |
517 | | |
518 | | switch (format->value.binary_format) { |
519 | | /* |
520 | | * Hex encode octets values when requested. |
521 | | */ |
522 | | case JSON_BINARY_FORMAT_BASE16: |
523 | | fr_base16_encode(encoded, &FR_DBUFF_TMP(vb->vb_octets, vb->vb_length)); |
524 | | break; |
525 | | |
526 | | /* |
527 | | * Base64-encode octets values when requested. |
528 | | */ |
529 | | case JSON_BINARY_FORMAT_BASE64: |
530 | | fr_base64_encode(encoded, &FR_DBUFF_TMP(vb->vb_octets, vb->vb_length), true); |
531 | | break; |
532 | | |
533 | | default: |
534 | | break; |
535 | | } |
536 | | |
537 | | MEM(*out = json_object_new_string_len(fr_sbuff_start(encoded), fr_sbuff_used(encoded))); |
538 | | return 0; |
539 | | } |
540 | | |
541 | | case JSON_BINARY_FORMAT_RAW: |
542 | | break; |
543 | | } |
544 | | } |
545 | | |
546 | | if (format->value.always_string) { |
547 | | fr_value_box_t vb_str = FR_VALUE_BOX_INITIALISER_NULL(vb_str); |
548 | | |
549 | | if (unlikely(fr_value_box_cast(ctx, &vb_str, FR_TYPE_STRING, NULL, vb) < 0)) return -1; |
550 | | |
551 | | MEM(*out = json_object_from_value_box(&vb_str)); |
552 | | fr_value_box_clear(&vb_str); |
553 | | |
554 | | return 0; |
555 | | } |
556 | | |
557 | | MEM(*out = json_object_from_value_box(vb)); |
558 | | return 0; |
559 | | } |
560 | | |
561 | | /** Convert fr_pair_t into a JSON object |
562 | | * |
563 | | * If format.value.enum_as_int is set, and the given VP is an enum |
564 | | * value, the integer value is returned as a json_object rather |
565 | | * than the text representation. |
566 | | * |
567 | | * If format.value.always_string is set then a numeric value |
568 | | * will be returned as a JSON string object. |
569 | | * |
570 | | * @param[in] ctx Talloc context. |
571 | | * @param[out] out returned json object. |
572 | | * @param[in] vp to get the value of. |
573 | | * @param[in] format format definition, or NULL. |
574 | | * @return |
575 | | * - 0 on success. |
576 | | * - -1 on error. |
577 | | */ |
578 | | static int json_afrom_pair(TALLOC_CTX *ctx, json_object **out, |
579 | | fr_pair_t *vp, fr_json_format_t const *format) |
580 | 0 | { |
581 | 0 | fr_value_box_t const *vb; |
582 | |
|
583 | 0 | fr_assert(vp); |
584 | |
|
585 | 0 | vb = &vp->data; |
586 | |
|
587 | 0 | if (format->value.enum_as_int) { |
588 | 0 | (void)fr_pair_value_enum_box(&vb, vp); |
589 | 0 | } |
590 | |
|
591 | 0 | return json_afrom_value_box(ctx, out, vb, format); |
592 | 0 | } |
593 | | |
594 | | |
595 | | /** Get attribute name with optional prefix |
596 | | * |
597 | | * If the format "attr.prefix" string is set then prepend this |
598 | | * to the given attribute name, otherwise just return name alone. |
599 | | * |
600 | | * @param[out] out sbuff to write the new name |
601 | | * @param[in] da dictionary attribute to get name of |
602 | | * @param[in] format json format structure |
603 | | * @return length of attribute name |
604 | | */ |
605 | | static inline ssize_t attr_name_with_prefix(fr_sbuff_t *out, fr_dict_attr_t const *da, fr_json_format_t const *format) |
606 | 0 | { |
607 | 0 | fr_sbuff_t our_out; |
608 | |
|
609 | 0 | if (!out) return 0; |
610 | | |
611 | 0 | our_out = FR_SBUFF(out); |
612 | |
|
613 | 0 | if (format->attr.prefix) { |
614 | 0 | FR_SBUFF_IN_STRCPY_RETURN(&our_out, format->attr.prefix); |
615 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, ':'); |
616 | 0 | } |
617 | | |
618 | 0 | FR_SBUFF_IN_BSTRNCPY_RETURN(&our_out, da->name, da->name_len); |
619 | | |
620 | 0 | FR_SBUFF_SET_RETURN(out, &our_out); |
621 | 0 | } |
622 | | |
623 | | |
624 | | /** Verify that the options in fr_json_format_t are valid |
625 | | * |
626 | | * Warnings are optional, will fatal error if the format is corrupt. |
627 | | * |
628 | | * @param[in] format the format structure to check |
629 | | * @param[in] verbose print out warnings if set |
630 | | * @return true if format is good, otherwise false |
631 | | */ |
632 | | bool fr_json_format_verify(fr_json_format_t const *format, bool verbose) |
633 | 0 | { |
634 | 0 | bool ret = true; |
635 | |
|
636 | 0 | fr_assert(format); |
637 | |
|
638 | 0 | switch (format->output_mode) { |
639 | 0 | case JSON_MODE_OBJECT: |
640 | 0 | case JSON_MODE_OBJECT_SIMPLE: |
641 | 0 | case JSON_MODE_ARRAY: |
642 | | /* all options are valid */ |
643 | 0 | return true; |
644 | 0 | case JSON_MODE_ARRAY_OF_VALUES: |
645 | 0 | if (format->attr.prefix) { |
646 | 0 | if (verbose) WARN("attribute name prefix not valid in output_mode 'array_of_values' and will be ignored"); |
647 | 0 | ret = false; |
648 | 0 | } |
649 | 0 | if (format->value.value_is_always_array) { |
650 | 0 | if (verbose) WARN("'value_is_always_array' not valid in output_mode 'array_of_values' and will be ignored"); |
651 | 0 | ret = false; |
652 | 0 | } |
653 | 0 | return ret; |
654 | 0 | case JSON_MODE_ARRAY_OF_NAMES: |
655 | 0 | if (format->value.value_is_always_array) { |
656 | 0 | if (verbose) WARN("'value_is_always_array' not valid in output_mode 'array_of_names' and will be ignored"); |
657 | 0 | ret = false; |
658 | 0 | } |
659 | 0 | if (format->value.enum_as_int) { |
660 | 0 | if (verbose) WARN("'enum_as_int' not valid in output_mode 'array_of_names' and will be ignored"); |
661 | 0 | ret = false; |
662 | 0 | } |
663 | 0 | if (format->value.always_string) { |
664 | 0 | if (verbose) WARN("'always_string' not valid in output_mode 'array_of_names' and will be ignored"); |
665 | 0 | ret = false; |
666 | 0 | } |
667 | 0 | return ret; |
668 | 0 | default: |
669 | 0 | ERROR("JSON format output mode is invalid"); |
670 | 0 | } |
671 | | |
672 | | /* If we get here, something has gone wrong */ |
673 | 0 | fr_assert(0); |
674 | |
|
675 | 0 | return false; |
676 | 0 | } |
677 | | |
678 | 0 | #define INVALID_TYPE \ |
679 | 0 | do { \ |
680 | 0 | fr_assert(0); \ |
681 | 0 | fr_strerror_printf("Invalid type %s for attribute %s", fr_type_to_str(vp->vp_type), vp->da->name); \ |
682 | 0 | goto error; \ |
683 | 0 | } while (0) |
684 | | |
685 | | /** Returns a JSON object representation of a list of value pairs |
686 | | * |
687 | | * The result is a struct json_object, which should be free'd with |
688 | | * json_object_put() by the caller. Intended to only be called by |
689 | | * fr_json_afrom_pair_list(). |
690 | | * |
691 | | * This function generates the "object" format, JSON_MODE_OBJECT. |
692 | | * @see fr_json_format_s |
693 | | * |
694 | | @verbatim |
695 | | { |
696 | | "<attribute0>":{ |
697 | | "type":"<type0>", |
698 | | "value":[<value0>,<value1>,<valueN>] // if value_is_always_array is true |
699 | | }, // or |
700 | | "<attribute1>":{ |
701 | | "type":"<type1>", |
702 | | "value":<value0> // if value_is_always_array is false |
703 | | // and there is only one value |
704 | | }, |
705 | | "<attributeN>":{ |
706 | | "type":"<typeN>", |
707 | | "value":[...] |
708 | | } |
709 | | } |
710 | | @endverbatim |
711 | | * |
712 | | * @param[in] ctx Talloc context. |
713 | | * @param[in] vps a list of value pairs. |
714 | | * @param[in] format Formatting control, must be set. |
715 | | * @return JSON object with the generated representation. |
716 | | */ |
717 | | static CC_HINT(warn_unused_result) |
718 | | json_object *json_object_afrom_pair_list(TALLOC_CTX *ctx, fr_pair_list_t *vps, fr_json_format_t const *format) |
719 | | { |
720 | | fr_pair_t *vp; |
721 | | struct json_object *obj; |
722 | | char buf[FR_DICT_ATTR_MAX_NAME_LEN + 32]; |
723 | | |
724 | | /* Check format and type */ |
725 | | fr_assert(format); |
726 | | fr_assert(format->output_mode == JSON_MODE_OBJECT); |
727 | | |
728 | | MEM(obj = json_object_new_object()); |
729 | | |
730 | | for (vp = fr_pair_list_head(vps); |
731 | | vp; |
732 | | vp = fr_pair_list_next(vps, vp)) { |
733 | | fr_sbuff_t attr_name; |
734 | | struct json_object *vp_object, *values, *value, *type_name; |
735 | | |
736 | | if (vp->vp_raw) continue; |
737 | | |
738 | | /* |
739 | | * Get attribute name and value. |
740 | | */ |
741 | | fr_sbuff_init_in(&attr_name, buf, sizeof(buf) - 1); |
742 | | if (attr_name_with_prefix(&attr_name, vp->da, format) < 0) { |
743 | | error: |
744 | | json_object_put_assert(obj); |
745 | | return NULL; |
746 | | } |
747 | | |
748 | | switch (vp->vp_type) { |
749 | | case FR_TYPE_LEAF: |
750 | | if (json_afrom_pair(ctx, &value, vp, format) < 0) { |
751 | | fr_strerror_const("Failed to convert attribute value to JSON object"); |
752 | | goto error; |
753 | | } |
754 | | break; |
755 | | /* |
756 | | * For nested attributes we recurse. The nesting is represented |
757 | | * as a table, either as the single value, or as an element in |
758 | | * an array. |
759 | | * |
760 | | * ... |
761 | | * "value" : { "nested_attr" : { "type" : "<nested_type>", "value" : "<nested_attr_value>" } } |
762 | | * ... |
763 | | * |
764 | | * ... |
765 | | * "value" : [ { "nested_attr" : { "type" : "<nested_type>", "value" : "<nested_attr_value>" } } ] |
766 | | * ... |
767 | | * |
768 | | * The formatting of nested attributes and their structure is |
769 | | * identical to top level attributes. |
770 | | */ |
771 | | case FR_TYPE_STRUCTURAL: |
772 | | value = json_object_afrom_pair_list(ctx, &vp->vp_group, format); |
773 | | if (unlikely(value == NULL)) goto error; |
774 | | break; |
775 | | |
776 | | default: |
777 | | INVALID_TYPE; |
778 | | } |
779 | | |
780 | | /* |
781 | | * Look in the table to see if we already have a key for the attribute |
782 | | * we're working on. |
783 | | * |
784 | | * If we don't we create a new object in either the form: |
785 | | * |
786 | | * "<attribute>": { |
787 | | * "type": "<type>", |
788 | | * "value": [<value>] // if value_is_always_array is true |
789 | | * // or |
790 | | * "value": <value> // if value_is_always_array is false |
791 | | * // and there is only one value |
792 | | * } |
793 | | */ |
794 | | if (!json_object_object_get_ex(obj, fr_sbuff_start(&attr_name), &vp_object)) { |
795 | | /* |
796 | | * Wasn't there, so create a new object for this attribute. |
797 | | */ |
798 | | MEM(vp_object = json_object_new_object()); |
799 | | json_object_object_add(obj, fr_sbuff_start(&attr_name), vp_object); |
800 | | |
801 | | /* |
802 | | * Add "type" to newly created keys. |
803 | | */ |
804 | | MEM(type_name = json_object_new_string(fr_type_to_str(vp->vp_type))); |
805 | | json_object_object_add_ex(vp_object, "type", type_name, JSON_C_OBJECT_KEY_IS_CONSTANT); |
806 | | |
807 | | /* |
808 | | * Create a "value" array to hold any attribute values for this attribute... |
809 | | */ |
810 | | if (format->value.value_is_always_array) { |
811 | | MEM(values = json_object_new_array()); |
812 | | json_object_object_add_ex(vp_object, "value", values, JSON_C_OBJECT_KEY_IS_CONSTANT); |
813 | | json_object_array_add(values, value); |
814 | | continue; |
815 | | } |
816 | | |
817 | | /* |
818 | | * ...or just add the value directly. |
819 | | */ |
820 | | json_object_object_add_ex(vp_object, "value", value, JSON_C_OBJECT_KEY_IS_CONSTANT); |
821 | | |
822 | | continue; /* Next attribute! */ |
823 | | } |
824 | | |
825 | | /* |
826 | | * Find the 'values' array to add the current value to. |
827 | | */ |
828 | | if (!fr_cond_assert(json_object_object_get_ex(vp_object, "value", &values))) { |
829 | | fr_strerror_const("Inconsistent JSON tree"); |
830 | | goto error; |
831 | | } |
832 | | |
833 | | /* |
834 | | * If value_is_always_array is no set then "values" may not be an array, so it will |
835 | | * need converting to an array to add this extra attribute. |
836 | | */ |
837 | | if (!format->value.value_is_always_array) { |
838 | | json_type type; |
839 | | struct json_object *convert_value = values; |
840 | | |
841 | | /* Check "values" type */ |
842 | | type = json_object_get_type(values); |
843 | | |
844 | | /* It wasn't an array, so turn it into one with the old value as the first entry */ |
845 | | if (type != json_type_array) { |
846 | | MEM(values = json_object_new_array()); |
847 | | json_object_array_add(values, json_object_get(convert_value)); |
848 | | json_object_object_del(vp_object, "value"); |
849 | | json_object_object_add_ex(vp_object, "value", values, |
850 | | JSON_C_OBJECT_KEY_IS_CONSTANT); |
851 | | } |
852 | | } |
853 | | json_object_array_add(values, value); |
854 | | } |
855 | | |
856 | | return obj; |
857 | | } |
858 | | |
859 | | |
860 | | /** Returns a JSON object representation of a list of value pairs |
861 | | * |
862 | | * The result is a struct json_object, which should be free'd with |
863 | | * json_object_put() by the caller. Intended to only be called by |
864 | | * fr_json_afrom_pair_list(). |
865 | | * |
866 | | * This function generates the "simple object" format, JSON_MODE_OBJECT_SIMPLE. |
867 | | * @see fr_json_format_s |
868 | | * |
869 | | @verbatim |
870 | | { |
871 | | "<attribute0>":[<value0>,<value1>,<valueN>] // if value_is_always_array is true |
872 | | // or |
873 | | "<attribute1>":<value0> // if value_is_always_array is false, |
874 | | // and there is only one value |
875 | | "<attributeN>":[<value0>,<value1>,<valueN>] |
876 | | } |
877 | | @endverbatim |
878 | | * |
879 | | * @param[in] ctx Talloc context. |
880 | | * @param[in] vps a list of value pairs. |
881 | | * @param[in] format Formatting control, must be set. |
882 | | * @return JSON object with the generated representation. |
883 | | */ |
884 | | static json_object *json_simple_obj_afrom_pair_list(TALLOC_CTX *ctx, fr_pair_list_t *vps, |
885 | | fr_json_format_t const *format) |
886 | 0 | { |
887 | 0 | fr_pair_t *vp; |
888 | 0 | struct json_object *obj; |
889 | 0 | char buf[FR_DICT_ATTR_MAX_NAME_LEN + 32]; |
890 | 0 | json_type type; |
891 | | |
892 | | /* Check format and type */ |
893 | 0 | fr_assert(format); |
894 | 0 | fr_assert(format->output_mode == JSON_MODE_OBJECT_SIMPLE); |
895 | |
|
896 | 0 | MEM(obj = json_object_new_object()); |
897 | |
|
898 | 0 | for (vp = fr_pair_list_head(vps); |
899 | 0 | vp; |
900 | 0 | vp = fr_pair_list_next(vps, vp)) { |
901 | 0 | fr_sbuff_t attr_name; |
902 | 0 | struct json_object *vp_object, *value; |
903 | 0 | struct json_object *values = NULL; |
904 | 0 | bool add_single = false; |
905 | |
|
906 | 0 | if (vp->vp_raw) continue; |
907 | | |
908 | | /* |
909 | | * Get attribute name and value. |
910 | | */ |
911 | 0 | fr_sbuff_init_in(&attr_name, buf, sizeof(buf) - 1); |
912 | 0 | if (attr_name_with_prefix(&attr_name, vp->da, format) < 0) { |
913 | 0 | error: |
914 | 0 | json_object_put_assert(obj); |
915 | 0 | return NULL; |
916 | 0 | } |
917 | | |
918 | 0 | switch (vp->vp_type) { |
919 | 0 | case FR_TYPE_LEAF: |
920 | 0 | if (json_afrom_pair(ctx, &value, vp, format) < 0) { |
921 | 0 | fr_strerror_const("Failed to convert attribute value to JSON object"); |
922 | 0 | goto error; |
923 | 0 | } |
924 | 0 | break; |
925 | | /* |
926 | | * For nested attributes we recurse. The nesting is represented |
927 | | * as a table, either as the single value, or as an element in |
928 | | * an array. |
929 | | * |
930 | | * ... |
931 | | * "<parent>" : { "<nested_attr>" : <nested_attr_value> } |
932 | | * ... |
933 | | * |
934 | | * ... |
935 | | * "<parent>" : [ { "<nested_attr>" : "<nested_attr_value>" } ] |
936 | | * ... |
937 | | * |
938 | | * The formatting of nested attributes and their structure is |
939 | | * identical to top level attributes. |
940 | | */ |
941 | 0 | case FR_TYPE_STRUCTURAL: |
942 | 0 | value = json_simple_obj_afrom_pair_list(ctx, &vp->vp_group, format); |
943 | 0 | if (unlikely(value == NULL)) goto error; |
944 | 0 | break; |
945 | | |
946 | 0 | default: |
947 | 0 | INVALID_TYPE; |
948 | 0 | } |
949 | | |
950 | | /* |
951 | | * See if we already have a key in the table we're working on, |
952 | | * if not then create a new one. |
953 | | */ |
954 | 0 | if (!json_object_object_get_ex(obj, fr_sbuff_start(&attr_name), &vp_object)) { |
955 | 0 | if (format->value.value_is_always_array) { |
956 | | /* |
957 | | * We have been asked to ensure /all/ values are lists, |
958 | | * even if there's only one attribute. |
959 | | */ |
960 | 0 | MEM(values = json_object_new_array()); |
961 | 0 | json_object_object_add(obj, fr_sbuff_start(&attr_name), values); |
962 | 0 | } else { |
963 | | /* |
964 | | * Deal with it later on. |
965 | | */ |
966 | 0 | add_single = true; |
967 | 0 | } |
968 | | /* |
969 | | * If we do have the key already, get its value array. |
970 | | */ |
971 | 0 | } else { |
972 | 0 | type = json_object_get_type(vp_object); |
973 | |
|
974 | 0 | if (type == json_type_array) { |
975 | 0 | values = vp_object; |
976 | 0 | } else { |
977 | | /* |
978 | | * We've seen one of these before, but didn't add |
979 | | * it as an array the first time. Sort that out. |
980 | | */ |
981 | 0 | MEM(values = json_object_new_array()); |
982 | 0 | json_object_array_add(values, json_object_get(vp_object)); |
983 | | |
984 | | /* |
985 | | * Existing key will have refcount decremented |
986 | | * and will be freed if this drops to zero. |
987 | | */ |
988 | 0 | json_object_object_add(obj, fr_sbuff_start(&attr_name), values); |
989 | 0 | } |
990 | 0 | } |
991 | |
|
992 | 0 | if (add_single) { |
993 | | /* |
994 | | * Only ever used the first time adding a new |
995 | | * attribute when "value_is_always_array" is not set. |
996 | | */ |
997 | 0 | json_object_object_add(obj, fr_sbuff_start(&attr_name), value); |
998 | 0 | } else { |
999 | | /* |
1000 | | * Otherwise we're always appending to a JSON array. |
1001 | | */ |
1002 | 0 | json_object_array_add(values, value); |
1003 | 0 | } |
1004 | 0 | } |
1005 | | |
1006 | 0 | return obj; |
1007 | 0 | } |
1008 | | |
1009 | | |
1010 | | /** Returns a JSON array representation of a list of value pairs |
1011 | | * |
1012 | | * The result is a struct json_object, which should be free'd with |
1013 | | * json_object_put() by the caller. Intended to only be called by |
1014 | | * fr_json_afrom_pair_list(). |
1015 | | * |
1016 | | * This function generates the "array" format, JSON_MODE_ARRAY. |
1017 | | * @see fr_json_format_s |
1018 | | * |
1019 | | * @param[in] ctx Talloc context. |
1020 | | * @param[in] vps a list of value pairs. |
1021 | | * @param[in] format Formatting control, must be set. |
1022 | | * @return JSON object with the generated representation. |
1023 | | */ |
1024 | | static struct json_object *json_array_afrom_pair_list(TALLOC_CTX *ctx, fr_pair_list_t *vps, |
1025 | | fr_json_format_t const *format) |
1026 | 0 | { |
1027 | 0 | fr_pair_t *vp; |
1028 | 0 | struct json_object *obj; |
1029 | 0 | struct json_object *seen_attributes = NULL; |
1030 | 0 | char buf[FR_DICT_ATTR_MAX_NAME_LEN + 32]; |
1031 | | |
1032 | | /* Check format and type */ |
1033 | 0 | fr_assert(format); |
1034 | 0 | fr_assert(format->output_mode == JSON_MODE_ARRAY); |
1035 | |
|
1036 | 0 | MEM(obj = json_object_new_array()); |
1037 | | |
1038 | | /* |
1039 | | * If attribute values should be in a list format, then keep track |
1040 | | * of the attributes we've previously seen in a JSON object. |
1041 | | */ |
1042 | 0 | if (format->value.value_is_always_array) { |
1043 | 0 | seen_attributes = json_object_new_object(); |
1044 | 0 | } |
1045 | |
|
1046 | 0 | for (vp = fr_pair_list_head(vps); |
1047 | 0 | vp; |
1048 | 0 | vp = fr_pair_list_next(vps, vp)) { |
1049 | 0 | fr_sbuff_t attr_name; |
1050 | 0 | struct json_object *name, *value, *type_name; |
1051 | 0 | struct json_object *values = NULL; |
1052 | 0 | struct json_object *attrobj = NULL; |
1053 | 0 | bool already_seen = false; |
1054 | |
|
1055 | 0 | if (vp->vp_raw) continue; |
1056 | | |
1057 | | /* |
1058 | | * Get attribute name and value. |
1059 | | */ |
1060 | 0 | fr_sbuff_init_in(&attr_name, buf, sizeof(buf) - 1); |
1061 | 0 | if (attr_name_with_prefix(&attr_name, vp->da, format) < 0) { |
1062 | 0 | error: |
1063 | 0 | json_object_put_assert(seen_attributes); |
1064 | 0 | json_object_put_assert(obj); |
1065 | 0 | return NULL; |
1066 | 0 | } |
1067 | | |
1068 | 0 | switch (vp->vp_type) { |
1069 | 0 | case FR_TYPE_LEAF: |
1070 | 0 | if (json_afrom_pair(ctx, &value, vp, format) < 0) { |
1071 | 0 | fr_strerror_const("Failed to convert attribute value to JSON object"); |
1072 | 0 | goto error; |
1073 | 0 | } |
1074 | 0 | break; |
1075 | | |
1076 | 0 | case FR_TYPE_STRUCTURAL: |
1077 | 0 | value = json_array_afrom_pair_list(ctx, &vp->vp_group, format); |
1078 | 0 | if (unlikely(value == NULL)) goto error; |
1079 | 0 | break; |
1080 | | |
1081 | 0 | default: |
1082 | 0 | INVALID_TYPE; |
1083 | 0 | } |
1084 | | |
1085 | 0 | if (format->value.value_is_always_array) { |
1086 | | /* |
1087 | | * Try and find this attribute in the "seen_attributes" object. If it is |
1088 | | * there then get the "values" array to add this attribute value to. |
1089 | | */ |
1090 | 0 | already_seen = json_object_object_get_ex(seen_attributes, fr_sbuff_start(&attr_name), &values); |
1091 | 0 | } |
1092 | | |
1093 | | /* |
1094 | | * If we're adding all attributes to the toplevel array, or we're adding values |
1095 | | * to an array of an existing attribute but haven't seen it before, then we need |
1096 | | * to create a new JSON object for this attribute. |
1097 | | */ |
1098 | 0 | if (!format->value.value_is_always_array || !already_seen) { |
1099 | | /* |
1100 | | * Create object and add it to top-level array |
1101 | | */ |
1102 | 0 | MEM(attrobj = json_object_new_object()); |
1103 | 0 | json_object_array_add(obj, attrobj); |
1104 | | |
1105 | | /* |
1106 | | * Add the attribute name in the "name" key and the type in the "type" key |
1107 | | */ |
1108 | 0 | MEM(name = json_object_new_string(fr_sbuff_start(&attr_name))); |
1109 | 0 | json_object_object_add_ex(attrobj, "name", name, JSON_C_OBJECT_KEY_IS_CONSTANT); |
1110 | |
|
1111 | 0 | MEM(type_name = json_object_new_string(fr_type_to_str(vp->vp_type))); |
1112 | 0 | json_object_object_add_ex(attrobj, "type", type_name, JSON_C_OBJECT_KEY_IS_CONSTANT); |
1113 | 0 | } |
1114 | |
|
1115 | 0 | if (format->value.value_is_always_array) { |
1116 | | /* |
1117 | | * We're adding values to an array for the first copy of this attribute |
1118 | | * that we saw. First time around we need to create an array. |
1119 | | */ |
1120 | 0 | if (!already_seen) { |
1121 | 0 | MEM(values = json_object_new_array()); |
1122 | | /* |
1123 | | * Add "value":[] key to the attribute object |
1124 | | */ |
1125 | 0 | json_object_object_add_ex(attrobj, "value", values, JSON_C_OBJECT_KEY_IS_CONSTANT); |
1126 | | |
1127 | | /* |
1128 | | * Also add to "seen_attributes" to check later |
1129 | | */ |
1130 | 0 | json_object_object_add(seen_attributes, fr_sbuff_start(&attr_name), json_object_get(values)); |
1131 | 0 | } |
1132 | | |
1133 | | /* |
1134 | | * Always add the value to the respective "values" array. |
1135 | | */ |
1136 | 0 | json_object_array_add(values, value); |
1137 | 0 | } else { |
1138 | | /* |
1139 | | * This is simpler; just add a "value": key to the attribute object. |
1140 | | */ |
1141 | 0 | json_object_object_add_ex(attrobj, "value", value, JSON_C_OBJECT_KEY_IS_CONSTANT); |
1142 | 0 | } |
1143 | |
|
1144 | 0 | } |
1145 | | |
1146 | | /* |
1147 | | * No longer need the "seen_attributes" object, it was just used for tracking. |
1148 | | */ |
1149 | 0 | if (format->value.value_is_always_array) { |
1150 | 0 | json_object_put_assert(seen_attributes); |
1151 | 0 | } |
1152 | |
|
1153 | 0 | return obj; |
1154 | 0 | } |
1155 | | |
1156 | | |
1157 | | /** Returns a JSON array of a list of value pairs |
1158 | | * |
1159 | | * The result is a struct json_object, which should be free'd with |
1160 | | * json_object_put() by the caller. Intended to only be called by |
1161 | | * fr_json_afrom_pair_list(). |
1162 | | * |
1163 | | * This function generates the "array_of_values" format, |
1164 | | * JSON_MODE_ARRAY_OF_VALUES, listing just the attribute values. |
1165 | | * @see fr_json_format_s |
1166 | | * |
1167 | | * @param[in] ctx Talloc context. |
1168 | | * @param[in] vps a list of value pairs. |
1169 | | * @param[in] format Formatting control, must be set. |
1170 | | * @return JSON object with the generated representation. |
1171 | | */ |
1172 | | static struct json_object *json_value_array_afrom_pair_list(TALLOC_CTX *ctx, fr_pair_list_t *vps, |
1173 | | fr_json_format_t const *format) |
1174 | 0 | { |
1175 | 0 | fr_pair_t *vp; |
1176 | 0 | struct json_object *obj; |
1177 | | |
1178 | | /* Check format and type */ |
1179 | 0 | fr_assert(format); |
1180 | 0 | fr_assert(format->output_mode == JSON_MODE_ARRAY_OF_VALUES); |
1181 | |
|
1182 | 0 | MEM(obj = json_object_new_array()); |
1183 | | |
1184 | | /* |
1185 | | * This array format is very simple - just add all the |
1186 | | * attribute values to the array in order. |
1187 | | */ |
1188 | 0 | for (vp = fr_pair_list_head(vps); |
1189 | 0 | vp; |
1190 | 0 | vp = fr_pair_list_next(vps, vp)) { |
1191 | 0 | struct json_object *value; |
1192 | |
|
1193 | 0 | if (vp->vp_raw) continue; |
1194 | | |
1195 | 0 | switch (vp->vp_type) { |
1196 | 0 | case FR_TYPE_LEAF: |
1197 | 0 | if (json_afrom_pair(ctx, &value, vp, format) < 0) { |
1198 | 0 | fr_strerror_const("Failed to convert attribute value to JSON object"); |
1199 | 0 | error: |
1200 | 0 | json_object_put_assert(obj); |
1201 | 0 | return NULL; |
1202 | 0 | } |
1203 | 0 | break; |
1204 | | |
1205 | 0 | case FR_TYPE_STRUCTURAL: |
1206 | 0 | value = json_value_array_afrom_pair_list(ctx, &vp->vp_group, format); |
1207 | 0 | if (value == NULL) goto error; |
1208 | 0 | break; |
1209 | | |
1210 | 0 | default: |
1211 | 0 | INVALID_TYPE; |
1212 | 0 | } |
1213 | | |
1214 | 0 | json_object_array_add(obj, value); |
1215 | 0 | } |
1216 | | |
1217 | 0 | return obj; |
1218 | 0 | } |
1219 | | |
1220 | | |
1221 | | /** Returns a JSON array of a list of value pairs |
1222 | | * |
1223 | | * The result is a struct json_object, which should be free'd with |
1224 | | * json_object_put() by the caller. Intended to only be called by |
1225 | | * fr_json_afrom_pair_list(). |
1226 | | * |
1227 | | * This function generates the "array_of_names" format, |
1228 | | * JSON_MODE_ARRAY_OF_NAMES, listing just the attribute names. |
1229 | | * @see fr_json_format_s |
1230 | | * |
1231 | | * @param[in] ctx Talloc context. |
1232 | | * @param[in] vps a list of value pairs. |
1233 | | * @param[in] format Formatting control, must be set. |
1234 | | * @return JSON object with the generated representation. |
1235 | | */ |
1236 | | static struct json_object *json_attr_array_afrom_pair_list(TALLOC_CTX *ctx, fr_pair_list_t *vps, |
1237 | | fr_json_format_t const *format) |
1238 | 0 | { |
1239 | 0 | fr_pair_t *vp; |
1240 | 0 | struct json_object *obj; |
1241 | 0 | char buf[FR_DICT_ATTR_MAX_NAME_LEN + 32]; |
1242 | | |
1243 | | /* Check format and type */ |
1244 | 0 | fr_assert(format); |
1245 | 0 | fr_assert(format->output_mode == JSON_MODE_ARRAY_OF_NAMES); |
1246 | |
|
1247 | 0 | MEM(obj = json_object_new_array()); |
1248 | | |
1249 | | /* |
1250 | | * Add all the attribute names to the array in order. |
1251 | | */ |
1252 | 0 | for (vp = fr_pair_list_head(vps); |
1253 | 0 | vp; |
1254 | 0 | vp = fr_pair_list_next(vps, vp)) { |
1255 | 0 | struct json_object *value; |
1256 | 0 | fr_sbuff_t attr_name; |
1257 | |
|
1258 | 0 | if (vp->vp_raw) continue; |
1259 | | |
1260 | 0 | fr_sbuff_init_in(&attr_name, buf, sizeof(buf) - 1); |
1261 | 0 | if (attr_name_with_prefix(&attr_name, vp->da, format) < 0) { |
1262 | 0 | error: |
1263 | 0 | json_object_put_assert(obj); |
1264 | 0 | return NULL; |
1265 | 0 | } |
1266 | 0 | value = json_object_new_string(fr_sbuff_start(&attr_name)); |
1267 | |
|
1268 | 0 | switch (vp->vp_type) { |
1269 | 0 | case FR_TYPE_LEAF: |
1270 | 0 | break; |
1271 | | |
1272 | 0 | case FR_TYPE_STRUCTURAL: |
1273 | 0 | json_object_array_add(obj, value); |
1274 | 0 | value = json_attr_array_afrom_pair_list(ctx, &vp->vp_group, format); |
1275 | 0 | if (value == NULL) goto error; |
1276 | 0 | break; |
1277 | | |
1278 | 0 | default: |
1279 | 0 | INVALID_TYPE; |
1280 | 0 | } |
1281 | | |
1282 | 0 | json_object_array_add(obj, value); |
1283 | 0 | } |
1284 | | |
1285 | 0 | return obj; |
1286 | 0 | } |
1287 | | |
1288 | | |
1289 | | /** Returns a JSON string of a list of value pairs |
1290 | | * |
1291 | | * The result is a talloc-ed string, freeing the string is |
1292 | | * the responsibility of the caller. |
1293 | | * |
1294 | | * The 'format' struct contains settings to configure the output |
1295 | | * JSON document format. |
1296 | | * @see fr_json_format_s |
1297 | | * |
1298 | | * Default output, when format is NULL, is: |
1299 | | @verbatim |
1300 | | { |
1301 | | "<attribute0>":{ |
1302 | | "type":"<type0>", |
1303 | | "value":[<value0>,<value1>,<valueN>] |
1304 | | }, |
1305 | | "<attribute1>":{ |
1306 | | "type":"<type1>", |
1307 | | "value":[...] |
1308 | | }, |
1309 | | "<attributeN>":{ |
1310 | | "type":"<typeN>", |
1311 | | "value":[...] |
1312 | | } |
1313 | | } |
1314 | | @endverbatim |
1315 | | * |
1316 | | * @param[in] ctx Talloc context. |
1317 | | * @param[in] vps a list of value pairs. |
1318 | | * @param[in] format Formatting control, can be NULL to use default format. |
1319 | | * @return JSON string representation of the value pairs |
1320 | | */ |
1321 | | char *fr_json_afrom_pair_list(TALLOC_CTX *ctx, fr_pair_list_t *vps, |
1322 | | fr_json_format_t const *format) |
1323 | | { |
1324 | | struct json_object *obj = NULL; |
1325 | | const char *p; |
1326 | | char *out; |
1327 | | |
1328 | | if (!format) format = &default_json_format; |
1329 | | |
1330 | | switch (format->output_mode) { |
1331 | | case JSON_MODE_OBJECT: |
1332 | | MEM(obj = json_object_afrom_pair_list(ctx, vps, format)); |
1333 | | break; |
1334 | | case JSON_MODE_OBJECT_SIMPLE: |
1335 | | MEM(obj = json_simple_obj_afrom_pair_list(ctx, vps, format)); |
1336 | | break; |
1337 | | case JSON_MODE_ARRAY: |
1338 | | MEM(obj = json_array_afrom_pair_list(ctx, vps, format)); |
1339 | | break; |
1340 | | case JSON_MODE_ARRAY_OF_VALUES: |
1341 | | MEM(obj = json_value_array_afrom_pair_list(ctx, vps, format)); |
1342 | | break; |
1343 | | case JSON_MODE_ARRAY_OF_NAMES: |
1344 | | MEM(obj = json_attr_array_afrom_pair_list(ctx, vps, format)); |
1345 | | break; |
1346 | | default: |
1347 | | /* This should never happen */ |
1348 | | fr_assert(0); |
1349 | | } |
1350 | | |
1351 | | /* |
1352 | | * p is a buff inside obj, and will be freed |
1353 | | * when it is freed. |
1354 | | */ |
1355 | | MEM(p = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN)); |
1356 | | MEM(out = talloc_strdup(ctx, p)); |
1357 | | |
1358 | | /* |
1359 | | * Free the JSON structure, it's not needed any more |
1360 | | */ |
1361 | | json_object_put_assert(obj); |
1362 | | |
1363 | | return out; |
1364 | | } |