mirror of https://github.com/vmware/tdnf.git
422 lines
9.1 KiB
C
422 lines
9.1 KiB
C
/*
|
|
* Copyright (C) 2022-2023 VMware, Inc. All Rights Reserved.
|
|
*
|
|
* Licensed under the GNU General Public License v2 (the "License");
|
|
* you may not use this file except in compliance with the License. The terms
|
|
* of the License are located in the COPYING file of this distribution.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "jsondump.h"
|
|
|
|
#define SIZE_INC 256
|
|
|
|
/*
|
|
* This is a very simple libary to simplify creating json strings.
|
|
* It only allows for creating json output. It is not intended
|
|
* for access of these json data in a structured way, and there is
|
|
* no parsing back into json data. There are other libraries
|
|
* for that purpose, but they are too complex if all you need is
|
|
* a simple dump of json.
|
|
*
|
|
* Look at test.c for usage examples.
|
|
*/
|
|
|
|
/* Example 'make_message' from man 3 snprintf,
|
|
* modified to accept va_list.
|
|
* The allocated string returned must be free'd by the caller.
|
|
*/
|
|
static
|
|
char *_alloc_vsprintf(const char *fmt, va_list ap)
|
|
{
|
|
int size = 0;
|
|
char *p = NULL;
|
|
va_list aq;
|
|
va_copy(aq, ap);
|
|
|
|
/* Determine required size */
|
|
size = vsnprintf(p, size, fmt, ap);
|
|
if (size < 0)
|
|
goto err;
|
|
|
|
size++; /* For '\0' */
|
|
p = (char *)calloc(1, size);
|
|
if (p == NULL)
|
|
goto err;
|
|
|
|
size = vsnprintf(p, size, fmt, aq);
|
|
if (size < 0)
|
|
goto err;
|
|
|
|
va_end(aq);
|
|
return p;
|
|
|
|
err:
|
|
if (p)
|
|
free(p);
|
|
|
|
va_end(aq);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Create a context to hold a json buffer. After use it needs to be
|
|
* free'd using json_destroy().
|
|
* The buffer will be allocated with size bytes, or SIZE_INC bytes if 0.
|
|
*/
|
|
|
|
struct json_dump *jd_create(unsigned int size)
|
|
{
|
|
struct json_dump *jd = NULL;
|
|
|
|
jd = calloc(1, sizeof(struct json_dump));
|
|
if (!jd)
|
|
return NULL;
|
|
|
|
if (size == 0)
|
|
size = SIZE_INC;
|
|
|
|
jd->buf = (char *)calloc(size, sizeof(char));
|
|
if (!jd->buf) {
|
|
jd_destroy(jd);
|
|
return NULL;
|
|
}
|
|
jd->buf_size = size;
|
|
|
|
return jd;
|
|
}
|
|
|
|
void jd_destroy(struct json_dump *jd)
|
|
{
|
|
if (jd) {
|
|
if (jd->buf)
|
|
free(jd->buf);
|
|
free(jd);
|
|
}
|
|
}
|
|
|
|
/* make sure we have at least add_size bytes left in buffer by reallocating
|
|
* if needed */
|
|
static
|
|
int _jd_realloc(struct json_dump *jd, unsigned int add_size)
|
|
{
|
|
if (jd == NULL)
|
|
return -1;
|
|
|
|
if (jd->pos + add_size >= jd->buf_size - 1) {
|
|
add_size += SIZE_INC;
|
|
jd->buf = (char *)realloc((void *)jd->buf, jd->buf_size + add_size);
|
|
if (!jd->buf)
|
|
return -1;
|
|
jd->buf_size = jd->buf_size + add_size;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Convert string to json format, escaping characters if needed and add
|
|
* quotes.
|
|
* The string will be allocated using calloc() and it's the caller's
|
|
* responsibilty to free it.
|
|
*/
|
|
|
|
char *jsonify_string(const char *str)
|
|
{
|
|
char *p;
|
|
const char *q = str;
|
|
char *buf;
|
|
|
|
/* allocate for worst case - every char escaped plus quotes + nul */
|
|
buf = calloc(strlen(str)*2+3, sizeof(char));
|
|
if(!buf)
|
|
return NULL;
|
|
|
|
p = buf;
|
|
*p++ = '\"';
|
|
while(*q) {
|
|
switch (*q) {
|
|
case '"':
|
|
*p++ = '\\';
|
|
*p++ = '"';
|
|
break;
|
|
case '\\':
|
|
*p++ = '\\';
|
|
*p++ = '\\';
|
|
break;
|
|
case '\b':
|
|
*p++ = '\\';
|
|
*p++ ='b';
|
|
break;
|
|
case '\f':
|
|
*p++ = '\\';
|
|
*p++ ='f';
|
|
break;
|
|
case '\n':
|
|
*p++ = '\\';
|
|
*p++ ='n';
|
|
break;
|
|
case '\r':
|
|
*p++ = '\\';
|
|
*p++ ='r';
|
|
break;
|
|
case '\t':
|
|
*p++ = '\\';
|
|
*p++ ='t';
|
|
break;
|
|
default:
|
|
*p++ = *q;
|
|
}
|
|
q++;
|
|
}
|
|
*p++ = '\"';
|
|
*p = 0;
|
|
return buf;
|
|
}
|
|
|
|
/* add an empty map to the buffer */
|
|
int jd_map_start(struct json_dump *jd)
|
|
{
|
|
if(_jd_realloc(jd, 2))
|
|
return -1;
|
|
jd->buf[jd->pos++] = '{';
|
|
jd->buf[jd->pos++] = '}';
|
|
jd->buf[jd->pos] = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* before adding an item to a map, we need to rewind and replace the
|
|
* closing bracket with a comma. */
|
|
static
|
|
void _jd_map_prep_append(struct json_dump *jd)
|
|
{
|
|
if (jd->pos > 1 && jd->buf[jd->pos-1] == '}') {
|
|
jd->pos--;
|
|
if (jd->buf[jd->pos-1] != '{')
|
|
jd->buf[jd->pos++] = ',';
|
|
}
|
|
}
|
|
|
|
/* helper to add a key value pair with the string 'as-is'
|
|
* (w/out jsonifying and quotes)
|
|
*/
|
|
static
|
|
int _jd_map_add_raw(struct json_dump *jd, const char *key, const char *value)
|
|
{
|
|
int add_size = strlen(key) + strlen(value) + 5; /* lengths plus quotes plus colon plus closing bracket plus nul*/
|
|
int l;
|
|
|
|
if (jd == NULL)
|
|
return -1;
|
|
|
|
if(_jd_realloc(jd, add_size))
|
|
return -1;
|
|
_jd_map_prep_append(jd);
|
|
|
|
l = snprintf(&(jd->buf[jd->pos]), jd->buf_size - jd->pos, "\"%s\":%s}", key, value);
|
|
if (l < 0)
|
|
return -1;
|
|
jd->pos += l;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Add a string value to a map with key. The string will be processed
|
|
* to convert it to json format. The string can be NULL, in which case
|
|
* a json 'null' will be added instead.
|
|
*/
|
|
int jd_map_add_string(struct json_dump *jd, const char *key, const char *value)
|
|
{
|
|
int rc;
|
|
|
|
if (!value)
|
|
return jd_map_add_null(jd, key);
|
|
|
|
char *json_value = jsonify_string(value);
|
|
if (json_value == NULL)
|
|
return -1;
|
|
|
|
rc = _jd_map_add_raw(jd, key, json_value);
|
|
|
|
free(json_value);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int jd_map_add_int(struct json_dump *jd, const char *key, int value)
|
|
{
|
|
char buf[22]; /* 22 = length of 2^64 + 1 */
|
|
|
|
if (snprintf(buf, sizeof(buf), "%d", value) < 0)
|
|
return -1;
|
|
return _jd_map_add_raw(jd, key, buf);
|
|
}
|
|
|
|
int jd_map_add_int64(struct json_dump *jd, const char *key, int64_t value)
|
|
{
|
|
char buf[22]; /* 22 = length of 2^64 + 1 */
|
|
|
|
if (snprintf(buf, sizeof(buf), "%ld", value) < 0)
|
|
return -1;
|
|
return _jd_map_add_raw(jd, key, buf);
|
|
}
|
|
|
|
int jd_map_add_bool(struct json_dump *jd, const char *key, int value)
|
|
{
|
|
return _jd_map_add_raw(jd, key, value ? "true": "false");
|
|
}
|
|
|
|
int jd_map_add_null(struct json_dump *jd, const char *key)
|
|
{
|
|
return _jd_map_add_raw(jd, key, "null");
|
|
}
|
|
|
|
int jd_map_add_fmt(struct json_dump *jd, const char *key, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
char *buf;
|
|
int rc;
|
|
|
|
va_start(args, format);
|
|
buf = _alloc_vsprintf(format, args);
|
|
va_end(args);
|
|
|
|
if (buf == NULL)
|
|
return -1;
|
|
|
|
rc = jd_map_add_string(jd, key, buf);
|
|
|
|
free(buf);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int jd_map_add_child(struct json_dump *jd, const char *key, const struct json_dump *jd_child)
|
|
{
|
|
return _jd_map_add_raw(jd, key, jd_child->buf);
|
|
}
|
|
|
|
/* create an empty list */
|
|
int jd_list_start(struct json_dump *jd)
|
|
{
|
|
if(_jd_realloc(jd, 2))
|
|
return -1;
|
|
jd->buf[jd->pos++] = '[';
|
|
jd->buf[jd->pos++] = ']';
|
|
jd->buf[jd->pos] = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
int _jd_list_prep_append(struct json_dump *jd)
|
|
{
|
|
if (jd->pos > 1 && jd->buf[jd->pos-1] == ']') {
|
|
jd->pos--;
|
|
if (jd->buf[jd->pos-1] != '[')
|
|
jd->buf[jd->pos++] = ',';
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* similar to _jd_map_add_raw() but for a list */
|
|
static
|
|
int _jd_list_add_raw(struct json_dump *jd, const char *value)
|
|
{
|
|
int add_size = strlen(value) + 2; /* length plus closing bracket plus nul */
|
|
int l;
|
|
|
|
if (jd == NULL)
|
|
return -1;
|
|
|
|
if(_jd_realloc(jd, add_size))
|
|
return -1;
|
|
_jd_list_prep_append(jd);
|
|
|
|
l = snprintf(&(jd->buf[jd->pos]), jd->buf_size - jd->pos, "%s]", value);
|
|
if (l < 0)
|
|
return -1;
|
|
jd->pos += l;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* similar to jd_map_add_string() but for a list */
|
|
int jd_list_add_string(struct json_dump *jd, const char *value)
|
|
{
|
|
char *json_value;
|
|
int rc;
|
|
|
|
if (!value)
|
|
return jd_list_add_null(jd);
|
|
|
|
json_value = jsonify_string(value);
|
|
if (json_value == NULL)
|
|
return -1;
|
|
|
|
rc = _jd_list_add_raw(jd, json_value);
|
|
|
|
free(json_value);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int jd_list_add_int(struct json_dump *jd, int value)
|
|
{
|
|
char buf[22]; /* 22 = length of 2^64 + 1 */
|
|
|
|
if (snprintf(buf, sizeof(buf), "%d", value) < 0)
|
|
return -1;
|
|
return _jd_list_add_raw(jd, buf);
|
|
}
|
|
|
|
int jd_list_add_int64(struct json_dump *jd, int64_t value)
|
|
{
|
|
char buf[22]; /* 22 = length of 2^64 + 1 */
|
|
|
|
if (snprintf(buf, sizeof(buf), "%ld", value) < 0)
|
|
return -1;
|
|
return _jd_list_add_raw(jd, buf);
|
|
}
|
|
|
|
int jd_list_add_bool(struct json_dump *jd, int value)
|
|
{
|
|
return _jd_list_add_raw(jd, value? "true" : "false");
|
|
}
|
|
|
|
int jd_list_add_null(struct json_dump *jd)
|
|
{
|
|
return _jd_list_add_raw(jd, "null");
|
|
}
|
|
|
|
int jd_list_add_fmt(struct json_dump *jd, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
char *buf = NULL;
|
|
int rc;
|
|
|
|
va_start(args, format);
|
|
buf = _alloc_vsprintf(format, args);
|
|
va_end(args);
|
|
|
|
if (buf == NULL)
|
|
return -1;
|
|
|
|
rc = jd_list_add_string(jd, buf);
|
|
|
|
free(buf);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int jd_list_add_child(struct json_dump *jd, const struct json_dump *jd_child)
|
|
{
|
|
return _jd_list_add_raw(jd, jd_child->buf);
|
|
}
|
|
|