summaryrefslogtreecommitdiffstats
path: root/src/util/support/json.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/support/json.c')
-rw-r--r--src/util/support/json.c536
1 files changed, 306 insertions, 230 deletions
diff --git a/src/util/support/json.c b/src/util/support/json.c
index f02fe263df..5151b84159 100644
--- a/src/util/support/json.c
+++ b/src/util/support/json.c
@@ -96,7 +96,7 @@ struct value_base {
#define PTR2BASE(ptr) (((struct value_base *)ptr) - 1)
#define BASE2PTR(ptr) ((void *)(((struct value_base *)ptr) + 1))
-void *
+k5_json_value
k5_json_retain(k5_json_value val)
{
struct value_base *p;
@@ -160,24 +160,29 @@ alloc_value(json_type type, size_t size)
static struct json_type_st null_type = { K5_JSON_TID_NULL, "null", NULL };
-k5_json_null
-k5_json_null_create(void)
+int
+k5_json_null_create(k5_json_null *val_out)
{
- return alloc_value(&null_type, 0);
+ *val_out = alloc_value(&null_type, 0);
+ return (*val_out == NULL) ? ENOMEM : 0;
}
/*** Boolean type ***/
static struct json_type_st bool_type = { K5_JSON_TID_BOOL, "bool", NULL };
-k5_json_bool
-k5_json_bool_create(int truth)
+int
+k5_json_bool_create(int truth, k5_json_bool *val_out)
{
k5_json_bool b;
+ *val_out = NULL;
b = alloc_value(&bool_type, 1);
+ if (b == NULL)
+ return ENOMEM;
*(unsigned char *)b = !!truth;
- return b;
+ *val_out = b;
+ return 0;
}
int
@@ -209,10 +214,11 @@ static struct json_type_st array_type = {
K5_JSON_TID_ARRAY, "array", array_dealloc
};
-k5_json_array
-k5_json_array_create(void)
+int
+k5_json_array_create(k5_json_array *val_out)
{
- return alloc_value(&array_type, sizeof(struct k5_json_array_st));
+ *val_out = alloc_value(&array_type, sizeof(struct k5_json_array_st));
+ return (*val_out == NULL) ? ENOMEM : 0;
}
int
@@ -289,10 +295,11 @@ static struct json_type_st object_type = {
K5_JSON_TID_OBJECT, "object", object_dealloc
};
-k5_json_object
-k5_json_object_create(void)
+int
+k5_json_object_create(k5_json_object *val_out)
{
- return alloc_value(&object_type, sizeof(struct k5_json_object_st));
+ *val_out = alloc_value(&object_type, sizeof(struct k5_json_object_st));
+ return (*val_out == NULL) ? ENOMEM : 0;
}
size_t
@@ -373,37 +380,42 @@ static struct json_type_st string_type = {
K5_JSON_TID_STRING, "string", NULL
};
-k5_json_string
-k5_json_string_create(const char *string)
+int
+k5_json_string_create(const char *cstring, k5_json_string *val_out)
{
- return k5_json_string_create_len(string, strlen(string));
+ return k5_json_string_create_len(cstring, strlen(cstring), val_out);
}
-k5_json_string
-k5_json_string_create_len(const void *data, size_t len)
+int
+k5_json_string_create_len(const void *data, size_t len,
+ k5_json_string *val_out)
{
char *s;
+ *val_out = NULL;
s = alloc_value(&string_type, len + 1);
if (s == NULL)
- return NULL;
+ return ENOMEM;
memcpy(s, data, len);
s[len] = '\0';
- return (k5_json_string)s;
+ *val_out = (k5_json_string)s;
+ return 0;
}
-k5_json_string
-k5_json_string_create_base64(const void *data, size_t len)
+int
+k5_json_string_create_base64(const void *data, size_t len,
+ k5_json_string *val_out)
{
char *base64;
- k5_json_string s;
+ int ret;
+ *val_out = NULL;
base64 = k5_base64_encode(data, len);
if (base64 == NULL)
- return NULL;
- s = k5_json_string_create(base64);
+ return ENOMEM;
+ ret = k5_json_string_create(base64, val_out);
free(base64);
- return s;
+ return ret;
}
const char *
@@ -412,10 +424,21 @@ k5_json_string_utf8(k5_json_string string)
return (const char *)string;
}
-void *
-k5_json_string_unbase64(k5_json_string string, size_t *len_out)
+int
+k5_json_string_unbase64(k5_json_string string, unsigned char **data_out,
+ size_t *len_out)
{
- return k5_base64_decode((const char *)string, len_out);
+ unsigned char *data;
+ size_t len;
+
+ *data_out = NULL;
+ *len_out = 0;
+ data = k5_base64_decode((const char *)string, &len);
+ if (data == NULL)
+ return (len == 0) ? ENOMEM : EINVAL;
+ *data_out = data;
+ *len_out = len;
+ return 0;
}
/*** Number type ***/
@@ -424,15 +447,18 @@ static struct json_type_st number_type = {
K5_JSON_TID_NUMBER, "number", NULL
};
-k5_json_number
-k5_json_number_create(long long number)
+int
+k5_json_number_create(long long number, k5_json_number *val_out)
{
k5_json_number n;
+ *val_out = NULL;
n = alloc_value(&number_type, sizeof(long long));
- if (n)
- *((long long *)n) = number;
- return n;
+ if (n == NULL)
+ return ENOMEM;
+ *((long long *)n) = number;
+ *val_out = n;
+ return 0;
}
long long
@@ -448,62 +474,61 @@ static const char quotemap_c[] = "\"\\/\b\f\n\r\t";
static const char needs_quote[] = "\\\"\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17"
"\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37";
-struct encode_ctx {
- struct k5buf buf;
- int ret;
- int first;
-};
-
-static int encode_value(struct encode_ctx *j, k5_json_value val);
+static int encode_value(struct k5buf *buf, k5_json_value val);
static void
-encode_string(struct encode_ctx *j, const char *str)
+encode_string(struct k5buf *buf, const char *str)
{
size_t n;
const char *p;
- krb5int_buf_add(&j->buf, "\"");
+ krb5int_buf_add(buf, "\"");
while (*str != '\0') {
n = strcspn(str, needs_quote);
- krb5int_buf_add_len(&j->buf, str, n);
+ krb5int_buf_add_len(buf, str, n);
str += n;
if (*str == '\0')
break;
- krb5int_buf_add(&j->buf, "\\");
+ krb5int_buf_add(buf, "\\");
p = strchr(quotemap_c, *str);
if (p != NULL)
- krb5int_buf_add_len(&j->buf, quotemap_json + (p - quotemap_c), 1);
+ krb5int_buf_add_len(buf, quotemap_json + (p - quotemap_c), 1);
else
- krb5int_buf_add_fmt(&j->buf, "u00%02X", (unsigned int)*str);
+ krb5int_buf_add_fmt(buf, "u00%02X", (unsigned int)*str);
str++;
}
- krb5int_buf_add(&j->buf, "\"");
+ krb5int_buf_add(buf, "\"");
}
+struct obj_ctx {
+ struct k5buf *buf;
+ int ret;
+ int first;
+};
+
static void
-encode_dict_entry(void *ctx, const char *key, k5_json_value value)
+encode_obj_entry(void *ctx, const char *key, k5_json_value value)
{
- struct encode_ctx *j = ctx;
+ struct obj_ctx *j = ctx;
if (j->ret)
return;
if (j->first)
j->first = 0;
else
- krb5int_buf_add(&j->buf, ",");
- encode_string(j, key);
- krb5int_buf_add(&j->buf, ":");
- j->ret = encode_value(j, value);
- if (j->ret)
- return;
+ krb5int_buf_add(j->buf, ",");
+ encode_string(j->buf, key);
+ krb5int_buf_add(j->buf, ":");
+ j->ret = encode_value(j->buf, value);
}
static int
-encode_value(struct encode_ctx *j, k5_json_value val)
+encode_value(struct k5buf *buf, k5_json_value val)
{
k5_json_tid type;
- int first = 0, ret;
+ int ret;
size_t i, len;
+ struct obj_ctx ctx;
if (val == NULL)
return EINVAL;
@@ -511,62 +536,63 @@ encode_value(struct encode_ctx *j, k5_json_value val)
type = k5_json_get_tid(val);
switch (type) {
case K5_JSON_TID_ARRAY:
- krb5int_buf_add(&j->buf, "[");
+ krb5int_buf_add(buf, "[");
len = k5_json_array_length(val);
for (i = 0; i < len; i++) {
if (i != 0)
- krb5int_buf_add(&j->buf, ",");
- ret = encode_value(j, k5_json_array_get(val, i));
+ krb5int_buf_add(buf, ",");
+ ret = encode_value(buf, k5_json_array_get(val, i));
if (ret)
return ret;
}
- krb5int_buf_add(&j->buf, "]");
- break;
+ krb5int_buf_add(buf, "]");
+ return 0;
case K5_JSON_TID_OBJECT:
- krb5int_buf_add(&j->buf, "{");
- first = j->first;
- j->first = 1;
- k5_json_object_iterate(val, encode_dict_entry, j);
- krb5int_buf_add(&j->buf, "}");
- j->first = first;
- break;
+ krb5int_buf_add(buf, "{");
+ ctx.buf = buf;
+ ctx.ret = 0;
+ ctx.first = 1;
+ k5_json_object_iterate(val, encode_obj_entry, &ctx);
+ krb5int_buf_add(buf, "}");
+ return ctx.ret;
case K5_JSON_TID_STRING:
- encode_string(j, k5_json_string_utf8(val));
- break;
+ encode_string(buf, k5_json_string_utf8(val));
+ return 0;
case K5_JSON_TID_NUMBER:
- krb5int_buf_add_fmt(&j->buf, "%lld", k5_json_number_value(val));
- break;
+ krb5int_buf_add_fmt(buf, "%lld", k5_json_number_value(val));
+ return 0;
case K5_JSON_TID_NULL:
- krb5int_buf_add(&j->buf, "null");
- break;
+ krb5int_buf_add(buf, "null");
+ return 0;
case K5_JSON_TID_BOOL:
- krb5int_buf_add(&j->buf, k5_json_bool_value(val) ? "true" : "false");
- break;
+ krb5int_buf_add(buf, k5_json_bool_value(val) ? "true" : "false");
+ return 0;
default:
- return 1;
+ return EINVAL;
}
- return 0;
}
-char *
-k5_json_encode(k5_json_value val)
+int
+k5_json_encode(k5_json_value val, char **json_out)
{
- struct encode_ctx j;
+ struct k5buf buf;
+ int ret;
- j.ret = 0;
- j.first = 1;
- krb5int_buf_init_dynamic(&j.buf);
- if (encode_value(&j, val)) {
- krb5int_free_buf(&j.buf);
- return NULL;
+ *json_out = NULL;
+ krb5int_buf_init_dynamic(&buf);
+ ret = encode_value(&buf, val);
+ if (ret) {
+ krb5int_free_buf(&buf);
+ return ret;
}
- return krb5int_buf_data(&j.buf);
+ *json_out = krb5int_buf_data(&buf);
+ return (*json_out == NULL) ? ENOMEM : 0;
}
/*** JSON decoding ***/
@@ -576,8 +602,7 @@ struct decode_ctx {
size_t depth;
};
-static k5_json_value
-parse_value(struct decode_ctx *ctx);
+static int parse_value(struct decode_ctx *ctx, k5_json_value *val_out);
/* Consume whitespace. Return 0 if there is anything left to parse after the
* whitespace, -1 if not. */
@@ -621,66 +646,70 @@ hexval(unsigned char c)
/* Parse a JSON number (which must be an integer in the signed 64-bit range; we
* do not allow floating-point numbers). */
-static k5_json_number
-parse_number(struct decode_ctx *ctx)
+static int
+parse_number(struct decode_ctx *ctx, k5_json_number *val_out)
{
const unsigned long long umax = ~0ULL, smax = (1ULL << 63) - 1;
unsigned long long number = 0;
int neg = 1;
+ *val_out = NULL;
+
if (*ctx->p == '-') {
neg = -1;
ctx->p++;
}
if (!is_digit(*ctx->p))
- return NULL;
+ return EINVAL;
/* Read the number into an unsigned 64-bit container, ensuring that we
* don't overflow it. */
while (is_digit(*ctx->p)) {
if (number + 1 > umax / 10)
- return NULL;
+ return EOVERFLOW;
number = (number * 10) + (*ctx->p - '0');
ctx->p++;
}
/* Make sure the unsigned 64-bit value fits in the signed 64-bit range. */
if (number > smax + 1 || (number > smax && neg == 1))
- return NULL;
+ return EOVERFLOW;
- return k5_json_number_create(number * neg);
+ return k5_json_number_create(number * neg, val_out);
}
/* Parse a JSON string (which must not quote Unicode code points above 256). */
-static char *
-parse_string(struct decode_ctx *ctx)
+static int
+parse_string(struct decode_ctx *ctx, char **str_out)
{
const unsigned char *p, *start, *end = NULL;
const char *q;
char *buf, *pos;
unsigned int code;
+ *str_out = NULL;
+
/* Find the start and end of the string. */
if (*ctx->p != '"')
- return NULL;
+ return EINVAL;
start = ++ctx->p;
for (; *ctx->p != '\0'; ctx->p++) {
if (*ctx->p == '\\') {
ctx->p++;
if (*ctx->p == '\0')
- return NULL;
+ return EINVAL;
} else if (*ctx->p == '"') {
end = ctx->p++;
break;
}
}
if (end == NULL)
- return NULL;
+ return EINVAL;
pos = buf = malloc(end - start + 1);
if (buf == NULL)
- return NULL;
+ return ENOMEM;
for (p = start; p < end;) {
if (*p == '\\') {
p++;
@@ -694,7 +723,7 @@ parse_string(struct decode_ctx *ctx)
/* Code points above 0xff don't need to be quoted, so we
* don't implement translating those into UTF-8. */
free(buf);
- return NULL;
+ return EINVAL;
}
p += 5;
} else {
@@ -703,7 +732,7 @@ parse_string(struct decode_ctx *ctx)
*pos++ = quotemap_c[q - quotemap_json];
} else {
free(buf);
- return NULL;
+ return EINVAL;
}
p++;
}
@@ -712,198 +741,245 @@ parse_string(struct decode_ctx *ctx)
}
}
*pos = '\0';
- return buf;
+ *str_out = buf;
+ return 0;
}
-/*
- * Parse an object association and the following comma. Return 1 if an
- * association was parsed, 0 if the end of the object was reached, and -1 on
- * error.
- */
+/* Parse an object association and place it into obj. */
static int
-parse_pair(k5_json_object obj, struct decode_ctx *ctx)
+parse_object_association(k5_json_object obj, struct decode_ctx *ctx)
{
char *key = NULL;
- k5_json_value value;
-
- if (white_spaces(ctx))
- goto err;
-
- /* Check for the end of the object. */
- if (*ctx->p == '}') {
- ctx->p++;
- return 0;
- }
+ k5_json_value val;
+ int ret;
/* Parse the key and value. */
- key = parse_string(ctx);
- if (key == NULL)
- goto err;
+ ret = parse_string(ctx, &key);
+ if (ret)
+ return ret;
if (white_spaces(ctx))
- goto err;
+ goto invalid;
if (*ctx->p != ':')
- goto err;
+ goto invalid;
ctx->p++;
if (white_spaces(ctx))
- goto err;
- value = parse_value(ctx);
- if (value == NULL) {
+ goto invalid;
+ ret = parse_value(ctx, &val);
+ if (ret) {
free(key);
- return -1;
+ return ret;
}
- /* Add the key and value to the object. */
- k5_json_object_set(obj, key, value);
+ /* Add the key and value to obj. */
+ ret = k5_json_object_set(obj, key, val);
free(key);
- key = NULL;
- k5_json_release(value);
-
- /* Consume the following comma if this isn't the last item. */
- if (white_spaces(ctx))
- goto err;
- if (*ctx->p == ',')
- ctx->p++;
- else if (*ctx->p != '}')
- goto err;
+ k5_json_release(val);
+ return ret;
- return 1;
-
-err:
+invalid:
free(key);
- return -1;
+ return EINVAL;
}
/* Parse a JSON object. */
-static k5_json_object
-parse_object(struct decode_ctx *ctx)
+static int
+parse_object(struct decode_ctx *ctx, k5_json_object *val_out)
{
- k5_json_object obj;
+ k5_json_object obj = NULL;
int ret;
- obj = k5_json_object_create();
- if (obj == NULL)
- return NULL;
+ *val_out = NULL;
+ /* Parse past the opening brace. */
+ if (*ctx->p != '{')
+ return EINVAL;
ctx->p++;
- while ((ret = parse_pair(obj, ctx)) > 0)
- ;
- if (ret < 0) {
- k5_json_release(obj);
- return NULL;
- }
- return obj;
-}
-
-/* Parse a JSON array item and the following comma. Return 1 if an item was
- * parsed, 0 if the end of the array was reached, and -1 on error. */
-static int
-parse_item(k5_json_array array, struct decode_ctx *ctx)
-{
- k5_json_value value;
-
if (white_spaces(ctx))
- return -1;
+ return EINVAL;
- if (*ctx->p == ']') {
- ctx->p++;
- return 0;
- }
+ ret = k5_json_object_create(&obj);
+ if (ret)
+ return ret;
- value = parse_value(ctx);
- if (value == NULL)
- return -1;
+ /* Pairs associations until we reach the terminating brace. */
+ if (*ctx->p != '}') {
+ while (1) {
+ ret = parse_object_association(obj, ctx);
+ if (ret) {
+ k5_json_release(obj);
+ return ret;
+ }
+ if (white_spaces(ctx))
+ goto invalid;
+ if (*ctx->p == '}')
+ break;
+ if (*ctx->p != ',')
+ goto invalid;
+ ctx->p++;
+ if (white_spaces(ctx))
+ goto invalid;
+ }
+ }
+ ctx->p++;
+ *val_out = obj;
+ return 0;
- k5_json_array_add(array, value);
- k5_json_release(value);
+invalid:
+ k5_json_release(obj);
+ return EINVAL;
+}
- if (white_spaces(ctx))
- return -1;
+/* Parse an value and place it into array. */
+static int
+parse_array_item(k5_json_array array, struct decode_ctx *ctx)
+{
+ k5_json_value val;
+ int ret;
- if (*ctx->p == ',')
- ctx->p++;
- else if (*ctx->p != ']')
- return -1;
- return 1;
+ ret = parse_value(ctx, &val);
+ if (ret)
+ return ret;
+ ret = k5_json_array_add(array, val);
+ k5_json_release(val);
+ return ret;
}
/* Parse a JSON array. */
-static k5_json_array
-parse_array(struct decode_ctx *ctx)
+static int
+parse_array(struct decode_ctx *ctx, k5_json_array *val_out)
{
- k5_json_array array = k5_json_array_create();
+ k5_json_array array = NULL;
int ret;
- assert(*ctx->p == '[');
- ctx->p += 1;
+ *val_out = NULL;
- while ((ret = parse_item(array, ctx)) > 0)
- ;
- if (ret < 0) {
- k5_json_release(array);
- return NULL;
+ /* Parse past the opening bracket. */
+ if (*ctx->p != '[')
+ return EINVAL;
+ ctx->p++;
+ if (white_spaces(ctx))
+ return EINVAL;
+
+ ret = k5_json_array_create(&array);
+ if (ret)
+ return ret;
+
+ /* Pairs values until we reach the terminating bracket. */
+ if (*ctx->p != ']') {
+ while (1) {
+ ret = parse_array_item(array, ctx);
+ if (ret) {
+ k5_json_release(array);
+ return ret;
+ }
+ if (white_spaces(ctx))
+ goto invalid;
+ if (*ctx->p == ']')
+ break;
+ if (*ctx->p != ',')
+ goto invalid;
+ ctx->p++;
+ if (white_spaces(ctx))
+ goto invalid;
+ }
}
- return array;
+ ctx->p++;
+ *val_out = array;
+ return 0;
+
+invalid:
+ k5_json_release(array);
+ return EINVAL;
}
/* Parse a JSON value of any type. */
-static k5_json_value
-parse_value(struct decode_ctx *ctx)
+static int
+parse_value(struct decode_ctx *ctx, k5_json_value *val_out)
{
- k5_json_value v;
- char *str;
+ k5_json_null null;
+ k5_json_bool bval;
+ k5_json_number num;
+ k5_json_string str;
+ k5_json_object obj;
+ k5_json_array array;
+ char *cstring;
+ int ret;
+
+ *val_out = NULL;
if (white_spaces(ctx))
- return NULL;
+ return EINVAL;
if (*ctx->p == '"') {
- str = parse_string(ctx);
- if (str == NULL)
- return NULL;
- v = k5_json_string_create(str);
- free(str);
- return v;
+ ret = parse_string(ctx, &cstring);
+ if (ret)
+ return ret;
+ ret = k5_json_string_create(cstring, &str);
+ free(cstring);
+ if (ret)
+ return ret;
+ *val_out = str;
} else if (*ctx->p == '{') {
if (ctx->depth-- == 1)
- return NULL;
- v = parse_object(ctx);
+ return EINVAL;
+ ret = parse_object(ctx, &obj);
+ if (ret)
+ return ret;
ctx->depth++;
- return v;
+ *val_out = obj;
} else if (*ctx->p == '[') {
if (ctx->depth-- == 1)
- return NULL;
- v = parse_array(ctx);
+ return EINVAL;
+ ret = parse_array(ctx, &array);
ctx->depth++;
- return v;
+ *val_out = array;
} else if (is_digit(*ctx->p) || *ctx->p == '-') {
- return parse_number(ctx);
- }
-
- if (strncmp((char *)ctx->p, "null", 4) == 0) {
+ ret = parse_number(ctx, &num);
+ if (ret)
+ return ret;
+ *val_out = num;
+ } else if (strncmp((char *)ctx->p, "null", 4) == 0) {
ctx->p += 4;
- return k5_json_null_create();
+ ret = k5_json_null_create(&null);
+ if (ret)
+ return ret;
+ *val_out = null;
} else if (strncmp((char *)ctx->p, "true", 4) == 0) {
ctx->p += 4;
- return k5_json_bool_create(1);
+ ret = k5_json_bool_create(1, &bval);
+ if (ret)
+ return ret;
+ *val_out = bval;
} else if (strncmp((char *)ctx->p, "false", 5) == 0) {
ctx->p += 5;
- return k5_json_bool_create(0);
+ ret = k5_json_bool_create(0, &bval);
+ if (ret)
+ return ret;
+ *val_out = bval;
+ } else {
+ return EINVAL;
}
- return NULL;
+ return 0;
}
-k5_json_value
-k5_json_decode(const char *string)
+int
+k5_json_decode(const char *string, k5_json_value *val_out)
{
struct decode_ctx ctx;
- k5_json_value v;
+ k5_json_value val;
+ int ret;
+ *val_out = NULL;
ctx.p = (unsigned char *)string;
ctx.depth = MAX_DECODE_DEPTH;
- v = parse_value(&ctx);
+ ret = parse_value(&ctx, &val);
+ if (ret)
+ return ret;
if (white_spaces(&ctx) == 0) {
- k5_json_release(v);
- return NULL;
+ k5_json_release(val);
+ return EINVAL;
}
- return v;
+ *val_out = val;
+ return 0;
}