From 6e487707a47d5d0d27c20439161674aa69bba376 Mon Sep 17 00:00:00 2001 From: Mark Ellzey Date: Thu, 19 May 2016 20:57:23 -0700 Subject: [PATCH] Added initial evhtp_json API --- evhtp_json.c | 1678 ++++++++++++++++++++++++++++++++++++++++++++++++++ evhtp_json.h | 345 +++++++++++ 2 files changed, 2023 insertions(+) create mode 100644 evhtp_json.c create mode 100644 evhtp_json.h diff --git a/evhtp_json.c b/evhtp_json.c new file mode 100644 index 0000000..dd2440f --- /dev/null +++ b/evhtp_json.c @@ -0,0 +1,1678 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" + +#include "evhtp_heap.h" +#include "evhtp_json.h" + +enum evhtp_j_state { + evhtp_j_s_start = 0, + evhtp_j_s_end +}; + +enum evhtp_j_arr_state { + evhtp_j_arr_s_val = 0, + evhtp_j_arr_s_comma, + evhtp_j_arr_s_end +}; + +enum evhtp_j_obj_state { + evhtp_j_obj_s_key = 0, + evhtp_j_obj_s_delim, + evhtp_j_obj_s_val, + evhtp_j_obj_s_comma, + evhtp_j_obj_s_end +}; + +typedef enum evhtp_j_state evhtp_j_state; +typedef enum evhtp_j_arr_state evhtp_j_arr_state; +typedef enum evhtp_j_obj_state evhtp_j_obj_state; + +static __thread void * __js_heap = NULL; + +struct evhtp_json_s { + evhtp_json_vtype type; + union { + evhtp_kvmap * object; + evhtp_tailq * array; + char * string; + unsigned int number; + bool boolean; + }; + + size_t slen; + void (* freefn)(void *); +}; + +#define j_type_(j) (evhtp_likely(j) ? j->type : -1) + +evhtp_json_vtype +evhtp_json_get_type(evhtp_json * js) { + if (evhtp_unlikely(js == NULL)) { + return -1; + } else { + return js->type; + } +} + +ssize_t +evhtp_json_get_size(evhtp_json * js) { + if (evhtp_unlikely(js == NULL)) { + return -1; + } + + if (js == NULL) { + return -1; + } + + switch (j_type_(js)) { + case evhtp_json_vtype_string: + return (ssize_t)js->slen; + case evhtp_json_vtype_array: + return evhtp_tailq_size(js->array); + case evhtp_json_vtype_object: + return evhtp_kvmap_get_size(js->object); + default: + return 0; + } + + return 0; +} + +evhtp_kvmap * +evhtp_json_get_object(evhtp_json * js) { + if (evhtp_unlikely(js == NULL)) { + return NULL; + } + + if (evhtp_unlikely(j_type_(js) != evhtp_json_vtype_object)) { + return NULL; + } else { + return js->object; + } +} + +evhtp_tailq * +evhtp_json_get_array(evhtp_json * js) { + if (js == NULL) { + return NULL; + } + + if (evhtp_unlikely(j_type_(js) != evhtp_json_vtype_array)) { + return NULL; + } + + return js->array; +} + +unsigned int +evhtp_json_get_number(evhtp_json * js) { + if (js == NULL) { + return 0; + } + + if (evhtp_unlikely(j_type_(js) != evhtp_json_vtype_number)) { + return 0; + } else { + return js->number; + } +} + +const char * +evhtp_json_get_string(evhtp_json * js) { + if (evhtp_unlikely(js == NULL)) { + return NULL; + } + + if (evhtp_likely(j_type_(js) == evhtp_json_vtype_string)) { + return js->string; + } else { + return NULL; + } +} + +bool +evhtp_json_get_boolean(evhtp_json * js) { + if (js == NULL) { + return false; + } + + if (evhtp_unlikely(j_type_(js) != evhtp_json_vtype_bool)) { + return false; + } else { + return js->boolean; + } +} + +char +evhtp_json_get_null(evhtp_json * js) { + if (js == NULL) { + return -1; + } + + if (evhtp_unlikely(j_type_(js) != evhtp_json_vtype_null)) { + return -1; + } else { + return 1; + } +} + +static evhtp_json * +j_new_(evhtp_json_vtype type) { + evhtp_json * evhtp_j; + + if (evhtp_unlikely(__js_heap == NULL)) { + __js_heap = evhtp_heap_new(sizeof(evhtp_json), 1024); + } + + evhtp_j = evhtp_heap_alloc(__js_heap); + evhtp_alloc_assert(evhtp_j); + + evhtp_j->type = type; + evhtp_j->freefn = NULL; + return evhtp_j; +} + +static evhtp_json * +j_object_new_(void) { + evhtp_json * js; + + if (!(js = j_new_(evhtp_json_vtype_object))) { + return NULL; + } + + js->object = evhtp_kvmap_new(10); + js->freefn = (void (*))evhtp_kvmap_free; + + return js; +} + +static inline evhtp_json * +j_array_new_(void) { + evhtp_json * js; + + if (!(js = j_new_(evhtp_json_vtype_array))) { + return NULL; + } + + js->array = evhtp_tailq_new(); + js->freefn = (void (*))evhtp_tailq_free; + + return js; +} + +static evhtp_json * +j_string_new_(const char * str, size_t slen) { + evhtp_json * js; + + js = j_new_(evhtp_json_vtype_string); + evhtp_alloc_assert(js); + + if (evhtp_unlikely(str == NULL || slen == 0)) { + slen = 0; + } + + js->string = malloc(slen + 1); + evhtp_alloc_assert(js->string); + + js->string[slen] = '\0'; + + memcpy(js->string, str, slen); + + js->slen = slen; + js->freefn = free; + + return js; +} + +static evhtp_json * +j_number_new_(unsigned int num) { + evhtp_json * js; + + js = j_new_(evhtp_json_vtype_number); + js->number = num; + + return js; +} + +static evhtp_json * +j_boolean_new_(bool boolean) { + evhtp_json * js; + + js = j_new_(evhtp_json_vtype_bool); + js->boolean = boolean; + + return js; +} + +static evhtp_json * +j_null_new_(void) { + return j_new_(evhtp_json_vtype_null); +} + +static int +j_object_add_(evhtp_json * dst, const char * key, evhtp_json * val) { + if (evhtp_unlikely(j_type_(dst) != evhtp_json_vtype_object)) { + return -1; + } + + if (!evhtp_kvmap_add(dst->object, key, val, (void (*))evhtp_json_free)) { + return -1; + } + + return 0; +} + +static int +j_object_add_klen_(evhtp_json * dst, const char * key, size_t klen, evhtp_json * val) { + if (evhtp_unlikely(j_type_(dst) != evhtp_json_vtype_object)) { + return -1; + } + + if (!evhtp_kvmap_add_wklen(dst->object, + key, klen, val, (void (*))evhtp_json_free)) { + return -1; + } + + return 0; +} + +static int +j_array_add_(evhtp_json * dst, evhtp_json * src) { + if (evhtp_unlikely(j_type_(dst) != evhtp_json_vtype_array)) { + return -1; + } + + if (!evhtp_tailq_append(dst->array, src, 1, (void (*))evhtp_json_free)) { + return -1; + } + + return 0; +} + +evhtp_json * +evhtp_json_parse_string(const char * data, size_t len, size_t * n_read) { + unsigned char ch; + size_t i; + size_t buflen; + char buf[len + 128]; + int buf_idx; + int escaped; + bool error; + evhtp_json * js; + + if (!data || !len || *data != '"') { + /* *n_read = 0; */ + return NULL; + } + + escaped = 0; + buf_idx = 0; + error = false; + js = NULL; + buflen = len + 128; + + len--; + data++; + + for (i = 0; i < len; i++) { + if (buf_idx >= buflen) { + error = true; + errno = ENOBUFS; + goto end; + } + + ch = data[i]; + + if (!evhtp_isascii(ch)) { + error = true; + goto end; + } + + if (escaped) { + switch (ch) { + case '"': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + case '\\': + escaped = 0; + buf[buf_idx++] = ch; + break; + default: + error = true; + goto end; + } + continue; + } + + if (ch == '\\') { + escaped = 1; + continue; + } + + if (ch == '"') { + js = j_string_new(buf, buf_idx); + i += 1; + break; + } + + buf[buf_idx++] = ch; + } + +end: + *n_read += i; + + if (error == true) { + evhtp_safe_free(js, evhtp_json_free); + return NULL; + } + + return js; +} /* evhtp_json_parse_string */ + +inline evhtp_json * +evhtp_json_parse_key(const char * data, size_t len, size_t * n_read) { + return evhtp_json_parse_string(data, len, n_read); +} + +evhtp_json * +evhtp_json_parse_number(const char * data, size_t len, size_t * n_read) { + unsigned char ch; + char buf[len]; + int buf_idx; + size_t i; + evhtp_json * js; + + if (!data || !len) { + return NULL; + } + + js = NULL; + buf_idx = 0; + + memset(buf, 0, sizeof(buf)); + + for (i = 0; i < len; i++) { + ch = data[i]; + + if (!isdigit(ch) || (len == 1 && isdigit(ch))) { + js = j_number_new((unsigned int)evhtp_atoi(buf, buf_idx)); + break; + } + + buf[buf_idx++] = ch; + } + + *n_read += (len == 1) ? 1 : i - 1; + + return js; +} + +#define J_TRUE_CMP 0x657572 +#define J_FALSE_CMP 0x65736c61 +#define J_TRUE_MASK 0xFFFFFF +#define J_FALSE_MASK 0xFFFFFFFF + +inline evhtp_json * +evhtp_json_parse_boolean(const char * data, size_t len, size_t * n_read) { + evhtp_json * js; + + if (evhtp_unlikely(len < 4)) { + /* need at LEAST 'true' */ + return NULL; + } + + js = NULL; + + /* here we cast our data string to an integer, mask it by the + * number of words we want to see, then match the integer version + * of the string. + */ + switch (*data) { + case 't': + if ((*((uint32_t *)(data + 1)) & J_TRUE_MASK) == J_TRUE_CMP) { + *n_read += 3; + js = j_boolean_new(true); + } + + break; + case 'f': + if (len < 5) { + return NULL; + } + + if ((*((uint32_t *)(data + 1)) & J_FALSE_MASK) == J_FALSE_CMP) { + *n_read += 4; + js = j_boolean_new(false); + } + + break; + default: + return NULL; + } /* switch */ + + return js; +} /* evhtp_json_parse_boolean */ + +evhtp_json * +evhtp_json_parse_null(const char * data, size_t len, size_t * n_read) { + if (len < 4) { + return NULL; + } + + if (!evhtp_str30_cmp(data, 'n', 'u', 'l', 'l')) { + return NULL; + } + + *n_read += 4; + + return j_null_new(); +} + +evhtp_json * +evhtp_json_parse_value(const char * data, size_t len, size_t * n_read) { + if (data == NULL || len == 0) { + /* *n_read = 0; */ + return NULL; + } + + switch (data[0]) { + case '"': + return evhtp_json_parse_string(data, len, n_read); + case '{': + return evhtp_json_parse_object(data, len, n_read); + case '[': + return evhtp_json_parse_array(data, len, n_read); + default: + if (isdigit(data[0])) { + return evhtp_json_parse_number(data, len, n_read); + } + + switch (*data) { + case 't': + case 'f': + return evhtp_json_parse_boolean(data, len, n_read); + case 'n': + return evhtp_json_parse_null(data, len, n_read); + } + } /* switch */ + + /* *n_read = 0; */ + return NULL; +} + +evhtp_json * +evhtp_json_parse_array(const char * data, size_t len, size_t * n_read) { + unsigned char ch; + unsigned char end_ch; + size_t i; + bool error; + size_t b_read; + evhtp_j_arr_state state; + evhtp_json * js; + + + if (!data || !len || *data != '[') { + /* *n_read = 0; */ + return NULL; + } + + data++; + len--; + + js = j_array_new_(); + state = evhtp_j_arr_s_val; + error = false; + b_read = 0; + end_ch = 0; + + for (i = 0; i < len; i++) { + evhtp_json * val; + + ch = data[i]; + + if (isspace(ch)) { + continue; + } + + switch (state) { + case evhtp_j_arr_s_val: + if (ch == ']') { + end_ch = ch; + state = evhtp_j_arr_s_end; + break; + } + + if (!(val = evhtp_json_parse_value(&data[i], (len - i), &b_read))) { + error = true; + goto end; + } + + i += b_read; + b_read = 0; + + j_array_add(js, val); + + state = evhtp_j_arr_s_comma; + + if ((i + 1) == len) { + end_ch = data[i]; + } + + break; + case evhtp_j_arr_s_comma: + switch (ch) { + case ',': + state = evhtp_j_arr_s_val; + break; + case ']': + end_ch = ch; + state = evhtp_j_arr_s_end; + break; + default: + error = true; + goto end; + } + break; + case evhtp_j_arr_s_end: + goto end; + } /* switch */ + } +end: + *n_read += i; + + if ((end_ch != ']' || error == true)) { + evhtp_safe_free(js, evhtp_json_free); + return NULL; + } + + return js; +} /* evhtp_json_parse_array */ + +evhtp_json * +evhtp_json_parse_object(const char * data, size_t len, size_t * n_read) { + unsigned char ch; + unsigned char end_ch; + size_t i; + evhtp_json * js; + evhtp_json * key; + evhtp_json * val; + evhtp_j_obj_state state; + bool error; + size_t b_read; + + if (*data != '{') { + /* *n_read = 0; */ + return NULL; + } + + state = evhtp_j_obj_s_key; + js = j_object_new_(); + key = NULL; + val = NULL; + error = false; + b_read = 0; + end_ch = 0; + + data++; + len--; + + for (i = 0; i < len; i++) { + ch = data[i]; + + if (isspace(ch)) { + continue; + } + + switch (state) { + case evhtp_j_obj_s_key: + if (ch == '}') { + end_ch = ch; + state = evhtp_j_obj_s_end; + break; + } + + if (!(key = evhtp_json_parse_key(&data[i], (len - i), &b_read))) { + error = true; + i += b_read; + goto end; + } + + i += b_read; + b_read = 0; + state = evhtp_j_obj_s_delim; + break; + case evhtp_j_obj_s_delim: + if (ch != ':') { + error = true; + goto end; + } + + state = evhtp_j_obj_s_val; + break; + + case evhtp_j_obj_s_val: + if (!(val = evhtp_json_parse_value(&data[i], (len - i), &b_read))) { + error = true; + i += b_read; + goto end; + } + + i += b_read; + b_read = 0; + + j_object_add_(js, key->string, val); + + evhtp_safe_free(key, evhtp_json_free); + + key = NULL; + state = evhtp_j_obj_s_comma; + + break; + + case evhtp_j_obj_s_comma: + switch (ch) { + case ',': + state = evhtp_j_obj_s_key; + break; + case '}': + end_ch = ch; + state = evhtp_j_obj_s_end; + break; + default: + error = true; + goto end; + } + break; + case evhtp_j_obj_s_end: + goto end; + } /* switch */ + } + +end: + *n_read += i; + + evhtp_safe_free(key, evhtp_json_free); + + if ((end_ch != '}' || error == true)) { + evhtp_safe_free(js, evhtp_json_free); + return NULL; + } + + return js; +} /* evhtp_json_parse_object */ + +evhtp_json * +evhtp_json_parse_buf(const char * data, size_t len, size_t * n_read) { + unsigned char ch; + size_t b_read; + size_t i; + evhtp_json * js; + evhtp_j_state state; + + js = NULL; + b_read = 0; + state = evhtp_j_s_start; + + for (i = 0; i < len; i++) { + ch = data[i]; + + if (isspace(ch)) { + continue; + } + + switch (state) { + case evhtp_j_s_start: + switch (ch) { + case '{': + if (!(js = evhtp_json_parse_object(&data[i], (len - i), &b_read))) { + *n_read += b_read; + return NULL; + } + + i += b_read; + b_read = 0; + break; + case '[': + if (!(js = evhtp_json_parse_array(&data[i], (len - i), &b_read))) { + *n_read += b_read; + return NULL; + } + + i += b_read; + b_read = 0; + break; + default: + *n_read += i; + return NULL; + } /* switch */ + + state = evhtp_j_s_end; + break; + case evhtp_j_s_end: + break; + } /* switch */ + } + + *n_read += i; + + return js; +} /* evhtp_json_parse_buf */ + +evhtp_json * +evhtp_json_parse_file(const char * filename, size_t * bytes_read) { + evhtp_json * json = NULL; + FILE * fp = NULL; + char * buf = NULL; + size_t n_read = 0; + long file_size; + + if (filename == NULL) { + return NULL; + } + + do { + if (!(fp = fopen(filename, "re"))) { + break; + } + + if (fseek(fp, 0L, SEEK_END) == -1) { + break; + } + + if ((file_size = ftell(fp)) == -1) { + break; + } + + if (fseek(fp, 0L, SEEK_SET) == -1) { + break; + } + + /* allocate 1 more than the size, just incase there is not an EOL + * terminator in the file. + */ + if (!(buf = calloc(file_size + 1, 1))) { + break; + } + + if (fread(buf, 1, file_size, fp) != file_size) { + break; + } + + if (buf[file_size] == 0) { + /* just make sure we have SOME type of EOL terminator by placing a + * \n in it. */ + buf[file_size] = '\n'; + file_size += 1; + } + + if (!(json = evhtp_json_parse_buf(buf, file_size, &n_read))) { + break; + } + } while (0); + + if (fp != NULL) { + fclose(fp); + } + + *bytes_read = n_read; + + evhtp_safe_free(buf, free); + return json; +} /* evhtp_json_parse_file */ + +void +evhtp_json_free(evhtp_json * js) { + if (js == NULL) { + return; + } + + switch (j_type_(js)) { + case evhtp_json_vtype_string: + evhtp_safe_free(js->string, free); + break; + case evhtp_json_vtype_object: + evhtp_safe_free(js->object, evhtp_kvmap_free); + break; + case evhtp_json_vtype_array: + evhtp_safe_free(js->array, evhtp_tailq_free); + break; + default: + break; + } + + evhtp_heap_free(__js_heap, js); +} + +static evhtp_json * +j_array_index_(evhtp_json * array, int offset) { + evhtp_tailq * list; + + if (!(list = evhtp_json_get_array(array))) { + return NULL; + } + + return (evhtp_json *)evhtp_tailq_get_at_index(list, offset); +} + +enum path_state { + path_state_reading_key, + path_state_reading_array, + path_state_reading_array_end +}; + + +evhtp_json * +evhtp_json_get_array_index(evhtp_json * array, int offset) { + if (evhtp_unlikely(array == NULL || offset < 0)) { + return NULL; + } + + return j_array_index_(array, offset); +} + +evhtp_json * +evhtp_json_path_get(evhtp_json * js, const char * path) { + char buf[strlen(path) + 1]; + int buf_idx; + evhtp_kvmap * object; + evhtp_json * prev; + unsigned char ch; + size_t i; + enum path_state state; + + + if (evhtp_unlikely(js == NULL || path == NULL)) { + return NULL; + } + + prev = js; + object = NULL; + buf_idx = 0; + buf[0] = '\0'; + state = path_state_reading_key; + + for (i = 0; i < strlen(path) + 1; i++) { + ch = path[i]; + + switch (state) { + case path_state_reading_key: + switch (ch) { + case '[': + state = path_state_reading_array; + break; + case '\0': + case '.': + if (!(object = evhtp_json_get_object(prev))) { + return NULL; + } + + if (!(prev = evhtp_kvmap_find(object, buf))) { + return NULL; + } + + buf[0] = '\0'; + buf_idx = 0; + break; + default: + buf[buf_idx++] = ch; + buf[buf_idx] = '\0'; + break; + } /* switch */ + break; + case path_state_reading_array: + switch (ch) { + case ']': + if (!(prev = j_array_index_(prev, evhtp_atoi(buf, buf_idx)))) { + return NULL; + } + + buf[0] = '\0'; + buf_idx = 0; + + state = path_state_reading_array_end; + break; + default: + buf[buf_idx++] = ch; + buf[buf_idx] = '\0'; + break; + } + break; + case path_state_reading_array_end: + state = path_state_reading_key; + break; + } /* switch */ + + if (ch == '\0') { + break; + } + } + + return (prev != js) ? prev : NULL; +} /* evhtp_json_path_get */ + +evhtp_json * +evhtp_json_new_object(void) { + return j_object_new_(); +} + +evhtp_json * +evhtp_json_new_array(void) { + return j_array_new_(); +} + +evhtp_json * +evhtp_json_string_new(const char * str) { + return j_string_new_(str, str ? strlen(str) : 0); +} + +evhtp_json * +evhtp_json_string_new_len(const char * str, size_t size) { + return j_string_new_(str, size); +} + +evhtp_json * +evhtp_json_number_new(unsigned int num) { + return j_number_new_(num); +} + +evhtp_json * +evhtp_json_boolean_new(bool boolean) { + return j_boolean_new_(boolean); +} + +evhtp_json * +evhtp_json_null_new(void) { + return j_null_new_(); +} + +int +evhtp_json_object_add(evhtp_json * obj, const char * key, evhtp_json * val) { + if (!obj || !key || !val) { + return -1; + } + + return j_object_add_(obj, key, val); +} + +inline int +evhtp_json_object_add_klen(evhtp_json * obj, const char * k, size_t klen, evhtp_json * v) { + if (evhtp_unlikely(obj == NULL)) { + return -1; + } + + return j_object_add_klen(obj, k, klen, v); +} + +int +evhtp_json_array_add(evhtp_json * array, evhtp_json * val) { + return j_array_add(array, val); +} + +int +evhtp_json_add(evhtp_json * obj, const char * key, evhtp_json * val) { + if (!obj) { + return -1; + } + + if (key == NULL) { + if (j_type_(obj) != evhtp_json_vtype_array) { + return -1; + } + + return evhtp_json_array_add(obj, val); + } + + return evhtp_json_object_add(obj, key, val); +} + +struct __jbuf { + char * buf; + size_t buf_idx; + size_t buf_len; + ssize_t written; + int dynamic; + bool escape; +}; + +static int +j_addbuf_(struct __jbuf * jbuf, const char * buf, size_t len) { + evhtp_assert(jbuf != NULL); + + if (len == 0 || buf == NULL) { + return 0; + } + + if ((jbuf->buf_idx + len) > jbuf->buf_len) { + if (evhtp_unlikely(jbuf->dynamic == 1)) { + char * n_buf; + + jbuf->buf = realloc(jbuf->buf, (size_t)(jbuf->buf_len + len + 32)); + evhtp_alloc_assert(jbuf->buf); + + jbuf->buf_len += len + 32; + } else { + return -1; + } + } + + memcpy(&jbuf->buf[jbuf->buf_idx], buf, len); + + jbuf->buf_idx += len; + jbuf->written += len; + + return 0; +} + +static int +j_addbuf_vprintf_(struct __jbuf * jbuf, const char * fmt, va_list ap) { + char tmpbuf[jbuf->buf_len - jbuf->buf_idx]; + int sres; + + evhtp_assert(jbuf != NULL); + + sres = vsnprintf(tmpbuf, sizeof(tmpbuf), fmt, ap); + + if (sres >= sizeof(tmpbuf) || sres < 0) { + return -1; + } + + return j_addbuf_(jbuf, tmpbuf, (size_t)sres); +} + +static int +j_addbuf_printf_(struct __jbuf * jbuf, const char * fmt, ...) { + va_list ap; + int sres; + + evhtp_assert(jbuf != NULL); + + va_start(ap, fmt); + { + sres = j_addbuf_vprintf_j(jbuf, fmt, ap); + } + va_end(ap); + + return sres; +} + +static const char digits[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +static int +j_addbuf_number_(struct __jbuf * jbuf, unsigned int num) { + char buf[32]; /* 18446744073709551615 64b, 20 characters */ + char * buffer = (char *)buf; + char * buffer_end = buffer + 32; + char * buffer_end_save = buffer + 32; + unsigned index; + + evhtp_assert(jbuf != NULL); + + *--buffer_end = '\0'; + + while (num >= 100) { + index = (num % 100) * 2; + + num /= 100; + + *--buffer_end = digits[index + 1]; + *--buffer_end = digits[index]; + } + + if (num < 10) { + *--buffer_end = (char)('0' + num); + } else { + index = (unsigned)(num * 2); + + *--buffer_end = digits[index + 1]; + *--buffer_end = digits[index]; + } + + return j_addbuf_(jbuf, buffer_end, (size_t)(buffer_end_save - buffer_end - 1)); +} + +static int j_array_to_buffer__(evhtp_json * json, struct __jbuf * jbuf); +static int j_number_to_buffer__(evhtp_json * json, struct __jbuf * jbuf); +static int j_object_to_buffer__(evhtp_json * json, struct __jbuf * jbuf); + +static int +j_number_to_buffer_(evhtp_json * json, struct __jbuf * jbuf) { + if (evhtp_likely(j_type_(json) != evhtp_json_vtype_number)) { + return -1; + } else { + return addbuf__number(jbuf, json->number); + } +} + +static int +j_escape_string_(const char * str, size_t len, struct __jbuf * jbuf) { + unsigned char ch; + size_t i; + + evhtp_assert(jbuf != NULL); + + if (evhtp_unlikely(str == NULL)) { + return -1; + } + + for (i = 0; i < len; i++) { + ch = str[i]; + + switch (ch) { + default: + if (evhtp_unlikely(addbuf_(jbuf, (const char *)&ch, 1) == -1)) { + return -1; + } + break; + case '\n': + if (evhtp_unlikely(addbuf_(jbuf, "\\n", 2) == -1)) { + return -1; + } + break; + case '"': + if (evhtp_unlikely(addbuf_(jbuf, "\\\"", 2) == -1)) { + return -1; + } + break; + case '\t': + if (evhtp_unlikely(addbuf_(jbuf, "\\t", 2) == -1)) { + return -1; + } + break; + case '\r': + if (evhtp_unlikely(addbuf_(jbuf, "\\r", 2) == -1)) { + return -1; + } + break; + case '\\': + if (evhtp_unlikely(addbuf_(jbuf, "\\\\", 2) == -1)) { + return -1; + } + break; + } /* switch */ + } + + return 0; +} /* j_escape_string */ + +static int +j_string_to_buffer_(evhtp_json * json, struct __jbuf * jbuf) { + const char * str; + + if (j_type_(json) != evhtp_json_vtype_string) { + return -1; + } + + str = json->string; + + if (evhtp_unlikely(str == NULL)) { + return -1; + } + + if (evhtp_unlikely(j_addbuf_(jbuf, "\"", 1) == -1)) { + return -1; + } + + if (jbuf->escape == true) { + if (evhtp_unlikely(j_escape_string_(str, json->slen, jbuf) == -1)) { + return -1; + } + } + + return j_addbuf_(jbuf, "\"", 1); +} + +static int +j_boolean_to_buffer_(evhtp_json * json, struct __jbuf * jbuf) { + if (j_type_(json) != evhtp_json_vtype_bool) { + return -1; + } + + return j_addbuf_printf_(jbuf, "%s", + evhtp_json_get_boolean(json) == true ? "true" : "false"); +} + +static int +j_null_to_buffer_(evhtp_json * json, struct __jbuf * jbuf) { + if (j_type_(json) != evhtp_json_vtype_null) { + return -1; + } + + return addbuf__printf(jbuf, "null"); +} + +static int +j_to_buffer(evhtp_json * json, struct __jbuf * jbuf) { + switch (j_type_(json)) { + case evhtp_json_vtype_number: + return j_number_to_buffer_(json, jbuf); + case evhtp_json_vtype_array: + return j_array_to_buffer_(json, jbuf); + case evhtp_json_vtype_object: + return j_object_to_buffer_(json, jbuf); + case evhtp_json_vtype_string: + return j_string_to_buffer_(json, jbuf); + case evhtp_json_vtype_bool: + return j_boolean_to_buffer_(json, jbuf); + case evhtp_json_vtype_null: + return j_null_to_buffer_(json, jbuf); + default: + return -1; + } + + return 0; +} + +static int +j_array_to_buffer_(evhtp_json * json, struct __jbuf * jbuf) { + evhtp_tailq * array; + evhtp_tailq_elem * elem; + evhtp_tailq_elem * temp; + + if (j_type_(json) != evhtp_json_vtype_array) { + return -1; + } + + array = json->array; + + if (evhtp_unlikely(addbuf_(jbuf, "[", 1) == -1)) { + return -1; + } + + for (elem = evhtp_tailq_first(array); elem; elem = temp) { + evhtp_json * val; + + val = (evhtp_json *)evhtp_tailq_elem_data(elem); + evhtp_assert(val != NULL); + + if (evhtp_unlikely(j_to_buffer(val, jbuf) == -1)) { + return -1; + } + + if ((temp = evhtp_tailq_next(elem))) { + if (evhtp_unlikely(addbuf_(jbuf, ",", 1) == -1)) { + return -1; + } + } + } + + if (evhtp_unlikely(addbuf_(jbuf, "]", 1) == -1)) { + return -1; + } + + return 0; +} + +static int +j_object_to_buffer_(evhtp_json * json, struct __jbuf * jbuf) { + evhtp_kvmap * object; + evhtp_kvmap_ent * ent; + evhtp_kvmap_ent * temp; + + if (j_type_(json) != evhtp_json_vtype_object) { + return -1; + } + + object = json->object; + + if (evhtp_unlikely(addbuf_(jbuf, "{", 1) == -1)) { + return -1; + } + + for (ent = evhtp_kvmap_first(object); ent; ent = temp) { + const char * key; + evhtp_json * val; + + key = evhtp_kvmap_ent_key(ent); + evhtp_assert(key != NULL); + + val = (evhtp_json *)evhtp_kvmap_ent_val(ent); + evhtp_assert(val != NULL); + + if (evhtp_unlikely(addbuf_(jbuf, "\"", 1) == -1)) { + return -1; + } + + if (evhtp_unlikely(addbuf_(jbuf, key, evhtp_kvmap_ent_get_klen(ent)) == -1)) { + return -1; + } + + if (evhtp_unlikely(addbuf_(jbuf, "\":", 2) == -1)) { + return -1; + } + + if (evhtp_unlikely(j_to_buffer(val, jbuf) == -1)) { + return -1; + } + + if ((temp = evhtp_kvmap_next(ent))) { + if (evhtp_unlikely(addbuf_(jbuf, ",", 1) == -1)) { + return -1; + } + } + } + + if (evhtp_unlikely(addbuf_(jbuf, "}", 1) == -1)) { + return -1; + } + + return 0; +} /* j_object_to_buffer_ */ + +ssize_t +_evhtp_json_to_buffer(evhtp_json * json, char * buf, size_t buf_len, struct __jbuf * jbuf) { + if (evhtp_unlikely(!json || !buf)) { + return -1; + } + + if (evhtp_unlikely(j_to_buffer(json, jbuf) == -1)) { + return -1; + } + + return jbuf->written; +} + +ssize_t +evhtp_json_to_buffer(evhtp_json * json, char * buf, size_t buf_len) { + struct __jbuf jbuf = { + .buf = buf, + .buf_idx = 0, + .written = 0, + .buf_len = buf_len, + .dynamic = 0, + .escape = true + }; + + if (evhtp_unlikely(j_to_buffer(json, &jbuf) == -1)) { + return -1; + } + + return jbuf.written; +} + +ssize_t +evhtp_json_to_buffer_nescp(evhtp_json * json, char * buf, size_t buf_len) { + struct __jbuf jbuf = { + .buf = buf, + .buf_idx = 0, + .written = 0, + .buf_len = buf_len, + .dynamic = 0, + .escape = false + }; + + if (evhtp_unlikely(j_to_buffer(json, &jbuf) == -1)) { + return -1; + } + + return jbuf.written; +} + +char * +evhtp_json_to_buffer_alloc(evhtp_json * json, size_t * len) { + struct __jbuf jbuf = { + .buf = NULL, + .buf_idx = 0, + .written = 0, + .buf_len = 0, + .dynamic = 1, + .escape = true + }; + + if (!json || !len) { + return NULL; + } + + if (j_to_buffer(json, &jbuf) == -1) { + evhtp_safe_free(jbuf.buf, free); + return NULL; + } + + *len = jbuf.written; + + return jbuf.buf; +} + +static inline int j_compare_(evhtp_json * j1, evhtp_json * j2, evhtp_json_key_filtercb cb); + +static inline int +j_number_compare_(evhtp_json * j1, evhtp_json * j2, evhtp_json_key_filtercb cb) { + if (j1 == NULL || j2 == NULL) { + return -1; + } + + if (j_type_(j1) != evhtp_json_vtype_number) { + return -1; + } + + if (j_type_(j2) != evhtp_json_vtype_number) { + return -1; + } + + if (evhtp_json_get_number(j1) != evhtp_json_get_number(j2)) { + return -1; + } + + return 0; +} + +static inline int +j_array_compare_(evhtp_json * j1, evhtp_json * j2, evhtp_json_key_filtercb cb) { + evhtp_tailq * j1_array; + evhtp_tailq * j2_array; + evhtp_tailq_elem * elem; + evhtp_tailq_elem * temp; + int idx; + + if (j1 == NULL || j2 == NULL) { + return -1; + } + + if (!(j1_array = evhtp_json_get_array(j1))) { + return -1; + } + + if (!(j2_array = evhtp_json_get_array(j2))) { + return -1; + } + + idx = 0; + + for (elem = evhtp_tailq_first(j1_array); elem; elem = temp) { + evhtp_json * j1_val; + evhtp_json * j2_val; + + j1_val = (evhtp_json *)evhtp_tailq_elem_data(elem); + j2_val = (evhtp_json *)evhtp_tailq_get_at_index(j2_array, idx); + + if (j1_val && !j2_val) { + return -1; + } + + if (j2_val && !j1_val) { + return -1; + } + + if (j_compare_(j1_val, j2_val, cb) == -1) { + return -1; + } + + idx += 1; + + temp = evhtp_tailq_next(elem); + } + + return 0; +} /* j_array_compare_ */ + +static int +j_object_compare_(evhtp_json * j1, evhtp_json * j2, evhtp_json_key_filtercb cb) { + evhtp_kvmap * j1_map; + evhtp_kvmap * j2_map; + evhtp_kvmap_ent * ent; + evhtp_kvmap_ent * temp; + + if (j1 == NULL || j2 == NULL) { + return -1; + } + + if (!(j1_map = evhtp_json_get_object(j1))) { + return -1; + } + + if (!(j2_map = evhtp_json_get_object(j2))) { + return -1; + } + + for (ent = evhtp_kvmap_first(j1_map); ent; ent = temp) { + const char * key; + evhtp_json * j1_val; + evhtp_json * j2_val; + + if (!(key = evhtp_kvmap_ent_key(ent))) { + return -1; + } + + if (!(j1_val = (evhtp_json *)evhtp_kvmap_ent_val(ent))) { + return -1; + } + + if (cb && (cb)(key, j1_val) == 1) { + /* the key filter callback returned 1, which means we can ignore the + * comparison of this field. + */ + temp = evhtp_kvmap_next(ent); + continue; + } + + if (!(j2_val = (evhtp_json *)evhtp_kvmap_find(j2_map, key))) { + return -1; + } + + if (j_compare_(j1_val, j2_val, cb) == -1) { + return -1; + } + + temp = evhtp_kvmap_next(ent); + } + + return 0; +} /* j_object_compare_ */ + +static int +j_string_compare_(evhtp_json * j1, evhtp_json * j2, evhtp_json_key_filtercb cb) { + const char * j1_str; + const char * j2_str; + + if (!(j1_str = evhtp_json_get_string(j1))) { + return -1; + } + + if (!(j2_str = evhtp_json_get_string(j2))) { + return -1; + } + + if (strcmp(j1_str, j2_str)) { + return -1; + } + + return 0; +} + +static int +j_boolean_compare_(evhtp_json * j1, evhtp_json * j2, evhtp_json_key_filtercb cb) { + if (!j1 || !j2) { + return -1; + } + + if (j_type_(j1) != evhtp_json_vtype_bool) { + return -1; + } + + if (j_type_(j2) != evhtp_json_vtype_bool) { + return -1; + } + + if (evhtp_json_get_boolean(j1) != evhtp_json_get_boolean(j2)) { + return -1; + } + + return 0; +} + +static int +j_null_compare_(evhtp_json * j1, + evhtp_json * j2, + evhtp_json_key_filtercb cb) { + if (j1 == NULL || j2 == NULL) { + return -1; + } + + if (j_type_(j1) != evhtp_json_vtype_null + || j_type_(j2) != evhtp_json_vtype_null) { + return -1; + } + + return 0; +} + +static int +j_compare_(evhtp_json * j1, evhtp_json * j2, evhtp_json_key_filtercb cb) { + if (j1 == NULL || j2 == NULL) { + return -1; + } + + if (j_type_(j1) != j_type_(j2)) { + return -1; + } + + if (evhtp_json_get_size(j1) != evhtp_json_get_size(j2)) { + return -1; + } + + switch (j_type_(j1)) { + case evhtp_json_vtype_number: + return j_number_compare_(j1, j2, cb); + case evhtp_json_vtype_array: + return j_array_compare_(j1, j2, cb); + case evhtp_json_vtype_object: + return j_object_compare_(j1, j2, cb); + case evhtp_json_vtype_string: + return j_string_compare_(j1, j2, cb); + case evhtp_json_vtype_bool: + return j_boolean_compare_(j1, j2, cb); + case evhtp_json_vtype_null: + return j_null_compare_(j2, j2, cb); + default: + return -1; + } + + return 0; +} + +int +evhtp_json_compare(evhtp_json * j1, + evhtp_json * j2, + evhtp_json_key_filtercb cb) { + return j_compare_(j1, j2, cb); +} + diff --git a/evhtp_json.h b/evhtp_json.h new file mode 100644 index 0000000..f1143c4 --- /dev/null +++ b/evhtp_json.h @@ -0,0 +1,345 @@ +#ifndef __EVHTP_JSON_H__ +#define __EVHTP_JSON_H__ + +enum evhtp_json_vtype_e { + evhtp_json_vtype_string = 0, + evhtp_json_vtype_number, + evhtp_json_vtype_object, + evhtp_json_vtype_array, + evhtp_json_vtype_bool, + evhtp_json_vtype_null +}; + +struct evhtp_json_s; + +typedef enum evhtp_json_vtype_e evhtp_json_vtype; +typedef struct evhtp_json_s evhtp_json; + +typedef int (* evhtp_json_key_filtercb)(const char * key, evhtp_json * val); + + +/** + * @brief creates a new unordered-keyval context + * + * @return evhtp_json context with a vtype of 'evhtp_json_vtype_object' + */ +EVHTP_EXPORT evhtp_json * evhtp_json_new_object(void); + + +/** + * @brief creates a new ordered array of evhtp_json context values + * + * @return evhtp_json context with a vtype of 'evhtp_json_vtype_array' + */ +EVHTP_EXPORT evhtp_json * evhtp_json_new_array(void); + + +/** + * @brief frees data associated with a evhtp_json context. Objects and arrays + * will free all the resources contained within in a recursive manner. + * + * @param js + */ +EVHTP_EXPORT void evhtp_json_free(evhtp_json * js); + +/** + * @brief creates a evhtp_json context for a null terminated string + * and copies the original string + * + * @param str + * + * @return evhtp_json context with vtype of 'evhtp_json_vtype_string' + */ +EVHTP_EXPORT evhtp_json * evhtp_json_string_new(const char * str); + + +/** + * @brief a variant of evhtp_json_string_new which can be faster if you + * already know the length. + * + * @param str + * @param len length of the string + * + * @return + */ +EVHTP_EXPORT evhtp_json * evhtp_json_string_new_len(const char * str, size_t len); + + +/** + * @brief creates a evhtp_json context for a number. Note: this only supports + * unsigned int right now (casted for signed). + * + * @param num + * + * @return evhtp_json context with vtype of 'evhtp_json_vtype_number' + */ +EVHTP_EXPORT evhtp_json * evhtp_json_number_new(unsigned int num); + + +/** + * @brief creates a evhtp_json context for a boolean value. + * + * @param boolean either true or false + * + * @return evhtp_json context with vtype of 'evhtp_json_vtype_bool' + */ +EVHTP_EXPORT evhtp_json * evhtp_json_boolean_new(bool boolean); + + +/** + * @brief creates a evhtp_json context for a null value + * + * @return evhtp_json context with vtype of 'evhtp_json_vtype_null' + */ +EVHTP_EXPORT evhtp_json * evhtp_json_null_new(void); + +/** + * @brief parse a buffer containing raw json and convert it to the + * internal evhtp_json. + * + * @param data the buffer to parse + * @param len the length of the data in the buffer + * @param n_read the number of bytes which were parsed will be stored + * here. + * + * @return evhtp_json context if valid json, NULL on error (also see n_read above) + */ +EVHTP_EXPORT evhtp_json * evhtp_json_parse_buf(const char * data, size_t len, size_t * n_read); + +/** + * @brief wrapper around evhtp_json_parse_buf but opens, reads, parses, and closes + * a file with JSON data. + * + * @param filename + * @param n_read number of bytes parsed + * + * @return see evhtp_json_parse_buf + */ +EVHTP_EXPORT evhtp_json * evhtp_json_parse_file(const char * filename, size_t * n_read); + +/* these next set of parser functions are self explainatory, and will probably be + * made private in the future. + */ +EVHTP_EXPORT evhtp_json * evhtp_json_parse_object(const char * data, size_t len, size_t * n_read); +EVHTP_EXPORT evhtp_json * evhtp_json_parse_array(const char * data, size_t len, size_t * n_read); +EVHTP_EXPORT evhtp_json * evhtp_json_parse_value(const char * data, size_t len, size_t * n_read); +EVHTP_EXPORT evhtp_json * evhtp_json_parse_string(const char * data, size_t len, size_t * n_read); +EVHTP_EXPORT evhtp_json * evhtp_json_parse_number(const char * data, size_t len, size_t * n_read); +EVHTP_EXPORT evhtp_json * evhtp_json_parse_key(const char * data, size_t len, size_t * n_read); +EVHTP_EXPORT evhtp_json * evhtp_json_parse_null(const char * data, size_t len, size_t * n_read); +EVHTP_EXPORT evhtp_json * evhtp_json_parse_boolean(const char * data, size_t len, size_t * n_read); + +/** + * @brief returns the underlying type of the evhtp_json context + * + * @param js + * + * @return + */ +EVHTP_EXPORT evhtp_json_vtype evhtp_json_get_type(evhtp_json * js); + + +/** + * @brief returns the underlying evhtp_kvmap ADT for an object + * + * @param js + * + * @return evhtp_kvmap on success, NULL if the underlying data + * is not an object. + */ +EVHTP_EXPORT evhtp_kvmap * evhtp_json_get_object(evhtp_json * js); + +/** + * @brief returns the underlying evhtp_tailq ADT for an array + * + * @param js + * + * @return evhtp_tailq on success, NULL if the underlying data is + * not an array. + */ +EVHTP_EXPORT evhtp_tailq * evhtp_json_get_array(evhtp_json * js); + + +/** + * @brief fetches the evhtp_json value in an array at a specific index + * + * @param js + * @param index + * + * @return the evhtp_json value, NULL if not found + */ +EVHTP_EXPORT evhtp_json * evhtp_json_get_array_index(evhtp_json * js, int index); + + +/** + * @brief return the underlying number + * + * @param js + * + * @return + */ +EVHTP_EXPORT unsigned int evhtp_json_get_number(evhtp_json * js); + + +/** + * @brief fetches the underlying string of the evhtp_json context + * + * @param js + * + * @return the string on success, NULL if underlying data is not a string + */ +EVHTP_EXPORT const char * evhtp_json_get_string(evhtp_json * js); + + +/** + * @brief boolean fetch + * + * @param js + * + * @return true or false + */ +EVHTP_EXPORT bool evhtp_json_get_boolean(evhtp_json * js); + + +/** + * @brief null fetch + * + * @param js + * + * @return 1 if null, -1 if underlying data is not a null + */ +EVHTP_EXPORT char evhtp_json_get_null(evhtp_json * js); + + +/** + * @brief size fetch of underlying evhtp_json data: + * evhtp_json_vtype_string : length of the string + * evhtp_json_vtype_array : the number of entries in the array + * evhtp_json_vtype_object : number of entries in the object + * + * @param js + * + * @return 0 if the underlying type doesn't have a size, otherwise see above + */ +EVHTP_EXPORT ssize_t evhtp_json_get_size(evhtp_json * js); + + +/** + * @brieffetch evhtp_json data using a special syntax + * + * if the json is { "a" : 1, "b" : [1, 2, 3, { "foo": "bar" }], "c" : {"hi" : null} } + * + * and you would like to get the second element of the key "b" the syntax would be: + * "b.[2]" + * + * or to get the value of "foo" in this example: + * "b.[4].foo" + * + * @param js + * @param path + * + * @return + */ +EVHTP_EXPORT evhtp_json * evhtp_json_path_get(evhtp_json * js, const char * path); + + +/** + * @brief add a string : evhtp_json context to an existing evhtp_json object + * + * @param obj + * @param key + * @param val + * + * @return + */ +EVHTP_EXPORT int evhtp_json_object_add(evhtp_json * obj, const char * key, evhtp_json * val); + + +/** + * @brief same as evhtp_json_object_add, except if the length of the key is known, + * you can reduce overhead + * + * @param o + * @param k + * @param kl + * @param v + * + * @return + */ +EVHTP_EXPORT int evhtp_json_object_add_klen(evhtp_json * o, const char * k, size_t kl, evhtp_json * v); + + +/** + * @brief add a evhtp_json context to a evhtp_json array context + * + * @param array + * @param val + * + * @return + */ +EVHTP_EXPORT int evhtp_json_array_add(evhtp_json * array, evhtp_json * val); + + +/** + * @brief can be used on either evhtp_json arrays or objects. For arrays, just omit the key + * + * @param obj + * @param k + * @param val + * + * @return + */ +EVHTP_EXPORT int evhtp_json_add(evhtp_json * obj, const char * k, evhtp_json * val); + + +/** + * @brief converts the evhtp_json ctx to a valid JSON string + * + * @param json + * @param buf user supplied buffer + * @param buf_len the length of the buffer + * + * @return number of bytes copied into the buffer + */ +EVHTP_EXPORT ssize_t evhtp_json_to_buffer(evhtp_json * json, char * buf, size_t buf_len); + + +/** + * @brief same as evhtp_json_to_buffer but does not escape strings + * + * @param json + * @param buf + * @param buf_len + * + * @return + */ +EVHTP_EXPORT ssize_t evhtp_json_to_buffer_nescp(evhtp_json * json, char * buf, size_t buf_len); + + +/** + * @brief converts to a malloc'd JSON string + * + * @param json + * @param len + * + * @return + */ +EVHTP_EXPORT char * evhtp_json_to_buffer_alloc(evhtp_json * json, size_t * len); + + +/** + * @brief compares two evhtp_json contexts to determine if they match, if the filtercb + * argument is not NULL, and the type being compared currently is an array or object, + * each ts_json context is passed to the callback. If the callback returns -1, that + * value will not be compared to the other. + * + * @param j1 + * @param j2 + * @param cb + * + * @return + */ +EVHTP_EXPORT int evhtp_json_compare(evhtp_json * j1, evhtp_json * j2, evhtp_json_key_filtercb cb); + +#endif +