From: Stephan Bosch Date: Wed, 7 Aug 2019 18:29:25 +0000 (+0200) Subject: lib-json: Add definitions and functions for JSON types, values, and nodes X-Git-Tag: 2.4.0~2396 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=491da411251766e8c3805d0c2a647e70e382a129;p=thirdparty%2Fdovecot%2Fcore.git lib-json: Add definitions and functions for JSON types, values, and nodes --- diff --git a/src/lib-json/Makefile.am b/src/lib-json/Makefile.am index 71589c6573..a1d011aa44 100644 --- a/src/lib-json/Makefile.am +++ b/src/lib-json/Makefile.am @@ -5,11 +5,13 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib-test libjson_la_SOURCES = \ - json-syntax.c + json-syntax.c \ + json-types.c libjson_la_LIBADD = -lm headers = \ - json-syntax.h + json-syntax.h \ + json-types.h test_programs = diff --git a/src/lib-json/json-types.c b/src/lib-json/json-types.c new file mode 100644 index 0000000000..9a2f37a99c --- /dev/null +++ b/src/lib-json/json-types.c @@ -0,0 +1,82 @@ +/* Copyright (c) 2017-2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" + +#include "json-types.h" + +static const char *json_type_names[] = { + [JSON_TYPE_NONE] = "", + [JSON_TYPE_OBJECT] = "object", + [JSON_TYPE_ARRAY] = "array", + [JSON_TYPE_STRING] = "string", + [JSON_TYPE_NUMBER] = "number", + [JSON_TYPE_TRUE] = "true", + [JSON_TYPE_FALSE] = "false", + [JSON_TYPE_NULL] = "null", + [JSON_TYPE_TEXT] = "", +}; +static_assert_array_size(json_type_names, JSON_TYPE_TEXT + 1); + +static const char *json_content_type_names[] = { + [JSON_CONTENT_TYPE_NONE] = "", + [JSON_CONTENT_TYPE_LIST] = "", + [JSON_CONTENT_TYPE_STRING] = "", + [JSON_CONTENT_TYPE_DATA] = "", + [JSON_CONTENT_TYPE_INTEGER] = "", +}; +static_assert_array_size(json_content_type_names, + JSON_CONTENT_TYPE_INTEGER + 1); + +const char *json_type_get_name(enum json_type type) +{ + i_assert(type <= JSON_TYPE_TEXT); + return json_type_names[type]; +} + +const char *json_content_type_get_name(enum json_content_type ctype) +{ + i_assert(ctype <= JSON_CONTENT_TYPE_INTEGER); + return json_content_type_names[ctype]; +} + +const char *json_node_get_label(const struct json_node *jnode) +{ + switch (jnode->type) { + case JSON_TYPE_NONE: + return ""; + case JSON_TYPE_OBJECT: + switch (jnode->value.content_type) { + case JSON_CONTENT_TYPE_NONE: + return "object end"; + case JSON_CONTENT_TYPE_LIST: + return "object"; + default: + break; + } + break; + case JSON_TYPE_ARRAY: + switch (jnode->value.content_type) { + case JSON_CONTENT_TYPE_NONE: + return "array end"; + case JSON_CONTENT_TYPE_LIST: + return "array"; + default: + break; + } + break; + case JSON_TYPE_STRING: + case JSON_TYPE_NUMBER: + case JSON_TYPE_TEXT: + return t_strconcat( + json_type_get_name(jnode->type), " (", + json_content_type_get_name(jnode->value.content_type), + ")", NULL); + case JSON_TYPE_TRUE: + return "true"; + case JSON_TYPE_FALSE: + return "false"; + case JSON_TYPE_NULL: + return "null"; + } + i_unreached(); +} diff --git a/src/lib-json/json-types.h b/src/lib-json/json-types.h new file mode 100644 index 0000000000..42d28b4b8d --- /dev/null +++ b/src/lib-json/json-types.h @@ -0,0 +1,533 @@ +#ifndef JSON_TYPES_H +#define JSON_TYPES_H + +struct json_data; +struct json_value; +struct json_node; + +/* + * Value + */ + +/* The type of the JSON value */ +enum json_type { + /* no value */ + JSON_TYPE_NONE = 0, + /* object */ + JSON_TYPE_OBJECT, + /* array */ + JSON_TYPE_ARRAY, + /* string */ + JSON_TYPE_STRING, + /* number */ + JSON_TYPE_NUMBER, + /* true */ + JSON_TYPE_TRUE, + /* false */ + JSON_TYPE_FALSE, + /* null */ + JSON_TYPE_NULL, + /* JSON-text */ + JSON_TYPE_TEXT, +}; + +/* The content representation of the JSON value */ +enum json_content_type { + /* true, false, null */ + JSON_CONTENT_TYPE_NONE = 0, + /* object, array */ + JSON_CONTENT_TYPE_LIST, + /* literal/string/text */ + JSON_CONTENT_TYPE_STRING, + /* data buffer (for string/text containing \u0000) */ + JSON_CONTENT_TYPE_DATA, + /* integer number */ + JSON_CONTENT_TYPE_INTEGER, +}; + +struct json_data { + /* data[size] == 0x00 */ + const unsigned char *data; + size_t size; + + /* data contains 0x00 */ + bool contains_nul:1; + /* data contains control character */ + bool contains_control:1; +}; + +struct json_value { + enum json_content_type content_type; + union { + /* JSON_CONTENT_TYPE_STRING */ + const char *str; + /* JSON_CONTENT_TYPE_DATA */ + struct json_data *data; + /* JSON_CONTENT_TYPE_INTEGER */ + intmax_t intnum; + } content; +}; + +const char *json_type_get_name(enum json_type type); +const char *json_content_type_get_name(enum json_content_type ctype); + +/* number */ + +static inline int +json_value_get_uinteger(const struct json_value *jvalue, uintmax_t *num_r) +{ + i_assert(jvalue->content_type == JSON_CONTENT_TYPE_INTEGER); + if (jvalue->content.intnum < 0) + return -1; + *num_r = (uintmax_t)jvalue->content.intnum; + return 0; +} + +static inline int +json_value_get_integer(const struct json_value *jvalue, intmax_t *num_r) +{ + i_assert(jvalue->content_type == JSON_CONTENT_TYPE_INTEGER); + *num_r = jvalue->content.intnum; + return 0; +} + +#define JSON_VALUE_GET_U__TEMPLATE(name, type, uint_max) \ +static inline int \ +name(const struct json_value *jvalue, type *num_r) \ +{ \ + intmax_t l; \ + i_assert(jvalue->content_type == JSON_CONTENT_TYPE_INTEGER); \ + l = jvalue->content.intnum; \ + if (l < 0 || (uintmax_t)l > uint_max) \ + return -1; \ + *num_r = (type)l; \ + return 0; \ +} + +JSON_VALUE_GET_U__TEMPLATE(json_value_get_uint, + unsigned int, UINT_MAX) +JSON_VALUE_GET_U__TEMPLATE(json_value_get_ulong, + unsigned long, ULONG_MAX) +JSON_VALUE_GET_U__TEMPLATE(json_value_get_ullong, + unsigned long long, ULLONG_MAX) +JSON_VALUE_GET_U__TEMPLATE(json_value_get_uint32, + uint32_t, UINT32_MAX) +JSON_VALUE_GET_U__TEMPLATE(json_value_get_uint64, + uint64_t, UINT64_MAX) + +#define JSON_VALUE_GET_S__TEMPLATE(name, type, int_min, int_max) \ +static inline int \ +name(const struct json_value *jvalue, type *num_r) \ +{ \ + intmax_t l; \ + i_assert(jvalue->content_type == JSON_CONTENT_TYPE_INTEGER); \ + l = jvalue->content.intnum; \ + if (l < int_min || l > int_max) \ + return -1; \ + *num_r = (type)l; \ + return 0; \ +} + +JSON_VALUE_GET_S__TEMPLATE(json_value_get_int, + int, INT_MIN, INT_MAX) +JSON_VALUE_GET_S__TEMPLATE(json_value_get_long, + long, LONG_MIN, LONG_MAX) +JSON_VALUE_GET_S__TEMPLATE(json_value_get_llong, + long long, LLONG_MIN, LLONG_MAX) +JSON_VALUE_GET_S__TEMPLATE(json_value_get_int32, + int32_t, INT32_MIN, INT32_MAX) +JSON_VALUE_GET_S__TEMPLATE(json_value_get_int64, + int64_t, INT64_MIN, INT64_MAX) + +#define JSON_VALUE_GET_F__TEMPLATE(name, type) \ +static inline int \ +name(const struct json_value *jvalue, type *num_r) \ +{ \ + if (jvalue->content_type == JSON_CONTENT_TYPE_INTEGER) \ + *num_r = (type)jvalue->content.intnum; \ + else \ + i_unreached(); \ + return 0; \ +} + +/* string */ + +static inline ATTR_PURE const char * +json_value_get_str(const struct json_value *jvalue) +{ + i_assert(jvalue->content_type == JSON_CONTENT_TYPE_STRING); + return jvalue->content.str; +} +static inline ATTR_PURE const char * +json_value_as_str(const struct json_value *jvalue) +{ + switch (jvalue->content_type) { + case JSON_CONTENT_TYPE_STRING: + break; + case JSON_CONTENT_TYPE_INTEGER: + return t_strdup_printf("%"PRIdMAX, jvalue->content.intnum); + default: + i_unreached(); + } + return jvalue->content.str; +} +static inline ATTR_PURE const unsigned char * +json_value_get_data(const struct json_value *jvalue, size_t *size_r) +{ + switch (jvalue->content_type) { + case JSON_CONTENT_TYPE_STRING: + *size_r = strlen(jvalue->content.str); + return (const unsigned char *)jvalue->content.str; + case JSON_CONTENT_TYPE_DATA: + *size_r = jvalue->content.data->size; + return jvalue->content.data->data; + default: + break; + } + i_unreached(); +} + +/* + * Node + */ + +struct json_node { + /* object member name */ + const char *name; + /* node type */ + enum json_type type; + /* node value */ + struct json_value value; +}; + +static inline ATTR_PURE bool +json_node_is_none(const struct json_node *jnode) +{ + return (jnode->type == JSON_TYPE_NONE); +} + +const char *json_node_get_label(const struct json_node *jnode); + +/* object, array */ + +static inline ATTR_PURE bool +json_node_is_object(const struct json_node *jnode) +{ + return (jnode->type == JSON_TYPE_OBJECT && + jnode->value.content_type != JSON_CONTENT_TYPE_NONE); +} + +static inline ATTR_PURE bool +json_node_is_array(const struct json_node *jnode) +{ + return (jnode->type == JSON_TYPE_ARRAY && + jnode->value.content_type != JSON_CONTENT_TYPE_NONE); +} + +static inline ATTR_PURE bool +json_node_is_object_end(const struct json_node *jnode) +{ + return (jnode->type == JSON_TYPE_OBJECT && + jnode->value.content_type == JSON_CONTENT_TYPE_NONE); +} + +static inline ATTR_PURE bool +json_node_is_array_end(const struct json_node *jnode) +{ + return (jnode->type == JSON_TYPE_ARRAY && + jnode->value.content_type == JSON_CONTENT_TYPE_NONE); +} + +static inline ATTR_PURE bool +json_node_is_end(const struct json_node *jnode) +{ + switch (jnode->type) { + case JSON_TYPE_OBJECT: + case JSON_TYPE_ARRAY: + return (jnode->value.content_type == JSON_CONTENT_TYPE_NONE); + default: + break; + } + return FALSE; +} + +/* string */ + +static inline ATTR_PURE bool +json_node_is_string(const struct json_node *jnode) +{ + return (jnode->type == JSON_TYPE_STRING); +} + +static inline ATTR_PURE const char * +json_node_get_str(const struct json_node *jnode) +{ + switch (jnode->type) { + case JSON_TYPE_STRING: + case JSON_TYPE_NUMBER: + case JSON_TYPE_TEXT: + break; + case JSON_TYPE_TRUE: + return "true"; + case JSON_TYPE_FALSE: + return "false"; + case JSON_TYPE_NULL: + return "null"; + default: + i_unreached(); + } + return json_value_get_str(&jnode->value); +} +static inline ATTR_PURE const char * +json_node_as_str(const struct json_node *jnode) +{ + switch (jnode->type) { + case JSON_TYPE_STRING: + case JSON_TYPE_NUMBER: + case JSON_TYPE_TEXT: + break; + case JSON_TYPE_TRUE: + return "true"; + case JSON_TYPE_FALSE: + return "false"; + case JSON_TYPE_NULL: + return "null"; + default: + i_unreached(); + } + return json_value_as_str(&jnode->value); +} + +static inline ATTR_PURE const unsigned char * +json_node_get_data(const struct json_node *jnode, size_t *size_r) +{ + const char *literal; + + switch (jnode->type) { + case JSON_TYPE_STRING: + case JSON_TYPE_NUMBER: + case JSON_TYPE_TEXT: + break; + case JSON_TYPE_TRUE: + literal = "true"; + *size_r = strlen(literal); + return (const unsigned char *)literal; + case JSON_TYPE_FALSE: + literal = "false"; + *size_r = strlen(literal); + return (const unsigned char *)literal; + case JSON_TYPE_NULL: + literal = "null"; + *size_r = strlen(literal); + return (const unsigned char *)literal; + default: + i_unreached(); + } + return json_value_get_data(&jnode->value, size_r); +} + +/* number */ + +static inline ATTR_PURE bool +json_node_is_number(const struct json_node *jnode) +{ + return (jnode->type == JSON_TYPE_NUMBER); +} + +static inline int +json_node_get_intmax(const struct json_node *jnode, intmax_t *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_integer(&jnode->value, num_r); +} +static inline int +json_node_get_int(const struct json_node *jnode, int *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_int(&jnode->value, num_r); +} +static inline int +json_node_get_long(const struct json_node *jnode, long *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_long(&jnode->value, num_r); +} +static inline int +json_node_get_llong(const struct json_node *jnode, long long *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_llong(&jnode->value, num_r); +} +static inline int +json_node_get_int32(const struct json_node *jnode, int32_t *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_int32(&jnode->value, num_r); +} +static inline int +json_node_get_int64(const struct json_node *jnode, int64_t *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_int64(&jnode->value, num_r); +} + +static inline int +json_node_get_uintmax(const struct json_node *jnode, uintmax_t *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_uinteger(&jnode->value, num_r); +} +static inline int +json_node_get_uint(const struct json_node *jnode, unsigned int *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_uint(&jnode->value, num_r); +} +static inline int +json_node_get_ulong(const struct json_node *jnode, unsigned long *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_ulong(&jnode->value, num_r); +} +static inline int +json_node_get_ullong(const struct json_node *jnode, unsigned long long *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_ullong(&jnode->value, num_r); +} +static inline int +json_node_get_uint32(const struct json_node *jnode, uint32_t *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_uint32(&jnode->value, num_r); +} +static inline int +json_node_get_uint64(const struct json_node *jnode, uint64_t *num_r) +{ + if (jnode->type != JSON_TYPE_NUMBER) + return -1; + if (jnode->value.content_type != JSON_CONTENT_TYPE_INTEGER) + return -1; + return json_value_get_uint64(&jnode->value, num_r); +} + +/* true, false */ + +static inline ATTR_PURE bool +json_node_is_true(const struct json_node *jnode) +{ + return (jnode->type == JSON_TYPE_TRUE); +} + +static inline ATTR_PURE bool +json_node_is_false(const struct json_node *jnode) +{ + return (jnode->type == JSON_TYPE_FALSE); +} + +static inline ATTR_PURE bool +json_node_is_boolean(const struct json_node *jnode) +{ + switch (jnode->type) { + case JSON_TYPE_TRUE: + case JSON_TYPE_FALSE: + return TRUE; + default: + break; + } + return FALSE; +} + +static inline ATTR_PURE int +json_node_get_boolean(const struct json_node *jnode, bool *bool_r) +{ + switch (jnode->type) { + case JSON_TYPE_TRUE: + *bool_r = TRUE; + return 0; + case JSON_TYPE_FALSE: + *bool_r = FALSE; + return 0; + default: + break; + } + return -1; +} + +/* null */ + +static inline ATTR_PURE bool +json_node_is_null(const struct json_node *jnode) +{ + return (jnode->type == JSON_TYPE_NULL); +} + +/* utility */ + +static inline ATTR_PURE bool +json_node_is_singular(const struct json_node *jnode) +{ + switch (jnode->type) { + case JSON_TYPE_OBJECT: + case JSON_TYPE_ARRAY: + return FALSE; + default: + break; + } + return TRUE; +} + +/* + * Limits + */ + +#define JSON_DEFAULT_MAX_NAME_SIZE 1024 +#define JSON_DEFAULT_MAX_STRING_SIZE 32*1024 +#define JSON_DEFAULT_MAX_NESTING 32 +#define JSON_DEFAULT_MAX_LIST_ITEMS 1024 + +struct json_limits { + /* Maximum size of object member name */ + size_t max_name_size; + /* Maximum length of a string */ + size_t max_string_size; + /* Maximum depth of object/array nesting */ + unsigned int max_nesting; + /* Maximum number of object/array items */ + unsigned int max_list_items; +}; + +#endif