/* * Amanda, The Advanced Maryland Automatic Network Disk Archiver * Copyright (c) 2017-2017 Carbonite, Inc. All Rights Reserved. * All Rights Reserved. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of U.M. not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. U.M. makes no representations about the * suitability of this software for any purpose. It is provided "as is" * without express or implied warranty. * * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M. * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "amanda.h" #include "amutil.h" #include "conffile.h" #include "amjson.h" static char *string_encode_json(char *str); static void free_json_value_full(gpointer); static void free_json_value_full( gpointer pointer) { amjson_t *json = pointer; if (json->type == JSON_STRING) { g_free(json->string); json->string = NULL; } else if (json->type == JSON_ARRAY) { guint i; for (i = 0; i < json->array->len; i++) { free_json_value_full(g_ptr_array_index(json->array, i)); } g_ptr_array_free(json->array, TRUE); json->array = NULL; } else if (json->type == JSON_HASH) { g_hash_table_destroy(json->hash); json->hash = NULL; } json->type = JSON_NULL; g_free(json); } void delete_json( amjson_t *json) { free_json_value_full(json); } static char *json_value_to_string(amjson_t *json, int first, int indent); char * json_to_string( amjson_t *json) { return json_value_to_string(json, 1, 0); } typedef struct message_hash_s { GString *r; int first; int indent; } message_hash_t; static void json_hash_to_string( gpointer gkey, gpointer gvalue, gpointer user_data) { char *key = gkey; amjson_t *value = gvalue; message_hash_t *mh = user_data; char *result_value = json_value_to_string(value, 1, mh->indent); if (mh->first) { g_string_append_printf(mh->r,"%*c\"%s\": %s", mh->indent, ' ', (char *)key, result_value); mh->first = 0; } else { g_string_append_printf(mh->r,",\n%*c\"%s\": %s", mh->indent, ' ', (char *)key, result_value); } g_free(result_value); } static char * json_value_to_string( amjson_t *json, int first, int indent) { char *result = NULL; switch (json->type) { case JSON_TRUE: result = g_strdup_printf("%s", "true"); break; case JSON_FALSE: result = g_strdup_printf("%s", "false"); break; case JSON_NULL: result = g_strdup_printf("%s", "null"); break; case JSON_STRING: { char *encoded = string_encode_json(json->string); result = g_strdup_printf("\"%s\"", encoded); g_free(encoded); } break; case JSON_NUMBER: { result = g_strdup_printf("%lld", (long long)json->number); } break; case JSON_ARRAY: if (json->array->len == 0) { result = g_strdup_printf("[ ]"); } else { GString *r = g_string_sized_new(512); guint i; int lfirst = 1; if (indent == 0) { g_string_append_printf(r, "[\n"); } else { g_string_append_printf(r, "[\n%*c", indent+2, ' '); } for (i = 0; i < json->array->len; i++) { amjson_t *value = g_ptr_array_index(json->array, i); char *result_value = json_value_to_string(value, lfirst, indent+2); lfirst = 0; if (i>0) { g_string_append(r, ",\n"); } g_string_append(r, result_value); g_free(result_value); } g_string_append_printf(r, "\n%*c]", indent, ' '); result = g_string_free(r, FALSE); } break; case JSON_HASH: if (g_hash_table_size(json->hash) == 0) { result = g_strdup("{ }"); } else { GString *r = g_string_sized_new(512); message_hash_t mh = {r, 1, indent+2};; if (first) { g_string_append_printf(r, "{\n"); } else { g_string_append_printf(r, "%*c{\n", indent, ' '); } g_hash_table_foreach(json->hash, json_hash_to_string, &mh); g_string_append_printf(r, "\n%*c}", indent, ' '); result = g_string_free(r, FALSE); } break; case JSON_BAD: g_critical("JSON_BAD"); break; } return result; } amjson_type_t parse_json_primitive( char *s, int *i, int len G_GNUC_UNUSED) { if (strncmp(&s[*i], "null", 4) == 0) { *i += 4; return JSON_NULL; } else if (strncmp(&s[*i], "true", 4) == 0) { *i += 4; return JSON_TRUE; } else if (strncmp(&s[*i], "false", 5) == 0) { *i += 5; return JSON_FALSE; } return JSON_BAD; } char * json_parse_string( char *s, int *i, int len) { char *string = g_malloc(len); char *sp = string; (*i)++; for (; *i < len && s[*i] != '\0'; (*i)++) { char c = s[*i]; if (c == '"') { *sp = '\0'; return string; } else if (c == '\\') { (*i)++; c = s[*i]; switch (c) { case '"': case '/': case '\\': *sp++ = c; break; case 'b': case 'f': case 'r': case 'n': case 't': *sp++ = '\\'; *sp++ = c; break; case 'u': break; default: break; } } else { *sp++ = c; } } g_free(string); return NULL; } static uint64_t json_parse_number(char *s, int *i, int len); static uint64_t json_parse_number( char *s, int *i, int len G_GNUC_UNUSED) { gboolean negate = FALSE; guint64 result = 0; char c = s[*i]; if (s[*i] == '-') { negate = TRUE; (*i)++; c = s[*i]; } if (c >= '0' && c <= '9') { result = c-'0'; } else { g_critical("json not a number"); } (*i)++; c = s[*i]; while (c >= '0' && c <= '9') { result = result*10 + c-'0'; (*i)++; c = s[*i]; } (*i)--; if (negate) return -result; else return result; } static amjson_t *parse_json_hash(char *s, int *i); static amjson_t *parse_json_array(char *s, int *i); static amjson_t * parse_json_array( char *s, int *i) { int len = strlen(s); char *token; uint64_t itoken; amjson_type_t json_token; amjson_t *json = g_new0(amjson_t, 1); json->type = JSON_ARRAY; json->array = g_ptr_array_sized_new(10); (*i)++; for (; *i < len && s[*i] != '\0'; (*i)++) { char c = s[*i]; switch (c) { case '[': { amjson_t *value = parse_json_array(s, i); g_ptr_array_add(json->array, value); } break; case ']': return json; break; case '{': { amjson_t *value = parse_json_hash(s, i); g_ptr_array_add(json->array, value); } break; case '}': break; //return; case '"': token = json_parse_string(s, i, len); assert(token); { amjson_t *value = g_new0(amjson_t, 1); value->type = JSON_STRING; value->string = token; g_ptr_array_add(json->array, value); } token = NULL; break; case '\t': case '\r': case '\n': case ':': case ',': case ' ': break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': itoken = json_parse_number(s, i, len); { amjson_t *value = g_new0(amjson_t, 1); value->type = JSON_NUMBER; value->number = itoken; g_ptr_array_add(json->array, value); } break; default: json_token = parse_json_primitive(s, i, len); if (json_token != JSON_BAD) { amjson_t *value = g_new(amjson_t, 1); value->type = json_token; value->string = NULL; g_ptr_array_add(json->array, value); } token = NULL; break; } } return json; } static amjson_t * parse_json_hash( char *s, int *i) { int len = strlen(s); char *token; uint64_t itoken; amjson_type_t json_token; gboolean expect_key = TRUE; char *key = NULL; amjson_t *json = g_new0(amjson_t, 1); json->type = JSON_HASH; json->hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_json_value_full); (*i)++; for (; *i < len && s[*i] != '\0'; (*i)++) { char c = s[*i]; switch (c) { case '[': if (key) { amjson_t *value = parse_json_array(s, i); g_hash_table_insert(json->hash, key, value); key = NULL; expect_key = TRUE; } break; case ']': break; case '{': if (key) { amjson_t *value = parse_json_hash(s, i); g_hash_table_insert(json->hash, key, value); key = NULL; expect_key = TRUE; } else { } break; case '}': return json; case '"': token = json_parse_string(s, i, len); assert(token); if (expect_key) { expect_key = FALSE; key = token; } else { amjson_t *value = g_new0(amjson_t, 1); value->type = JSON_STRING; value->string = token; g_hash_table_insert(json->hash, key, value); key = NULL; expect_key = TRUE; } token = NULL; break; case '\t': case '\r': case '\n': case ':': case ',': case ' ': break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': itoken = json_parse_number(s, i, len); if (expect_key) { g_critical("number as hash key"); } else { amjson_t *value = g_new0(amjson_t, 1); value->type = JSON_NUMBER; value->number = itoken; g_hash_table_insert(json->hash, key, value); key = NULL; expect_key = TRUE; } token = NULL; break; default: json_token = parse_json_primitive(s, i, len); if (expect_key) { g_critical("primitive as hash key"); } else if (json_token != JSON_BAD) { amjson_t *value = g_new0(amjson_t, 1); value->type = json_token; value->string = NULL; g_hash_table_insert(json->hash, key, value); key = NULL; expect_key = TRUE; } else { g_critical("JSON_BAD"); } token = NULL; break; } } return json; } amjson_t * parse_json( char *s) { int i; int len = strlen(s); char *token; uint64_t itoken; amjson_type_t json_token; amjson_t *json = NULL; for (i = 0; i < len && s[i] != '\0'; i++) { char c = s[i]; switch (c) { case '[': json = parse_json_array(s, &i); break; case ']': break; case '{': json = parse_json_hash(s, &i); break; case '}': break; case '"': token = json_parse_string(s, &i, len); assert(token); json = g_new0(amjson_t, 1); json->type = JSON_STRING; json->string = token; token = NULL; break; case '\t': case '\r': case '\n': case ':': case ',': case ' ': break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': itoken = json_parse_number(s, &i, len); json = g_new0(amjson_t, 1); json->type = JSON_NUMBER; json->number = itoken; token = NULL; break; default: json_token = parse_json_primitive(s, &i, len); if (json_token != JSON_BAD) { json = g_new0(amjson_t, 1); json->type = json_token; } token = NULL; break; } } return json; } static char * string_encode_json( char *str) { int i = 0; int len; unsigned char *s; char *encoded; char *e; if (!str) { return g_strdup("null"); } len = strlen(str)*2; s = (unsigned char *)str; encoded = g_malloc(len+1); e = encoded; while(*s != '\0') { if (i++ >= len) { error("string_encode_json: str is too long: %s", str); } if (*s == '\\' || *s == '"') { *e++ = '\\'; *e++ = *s++; } else if (*s == '\b') { *e++ = '\\'; *e++ = 'b'; s++; } else if (*s == '\f') { *e++ = '\\'; *e++ = 'f'; s++; } else if (*s == '\n') { *e++ = '\\'; *e++ = 'n'; s++; } else if (*s == '\r') { *e++ = '\\'; *e++ = 'r'; s++; } else if (*s == '\t') { *e++ = '\\'; *e++ = 't'; s++; } else if (*s == '\v') { *e++ = '\\'; *e++ = 'v'; s++; } else if (*s < 32) { *e++ = '\\'; *e++ = 'u'; *e++ = '0'; *e++ = '0'; if ((*s>>4) <= 9) *e++ = '0' + (*s>>4); else *e++ = 'A' + (*s>4) - 10; if ((*s & 0x0F) <= 9) *e++ = '0' + (*s & 0x0F); else *e++ = 'A' + (*s & 0x0F) - 10; s++; } else { *e++ = *s++; } } *e = '\0'; return encoded; } amjson_type_t get_json_type( amjson_t *json) { return json->type; } char * get_json_string( amjson_t *json) { assert(json->type == JSON_STRING); return json->string; } uint64_t get_json_number( amjson_t *json) { assert(json->type == JSON_NUMBER); return json->number; } amjson_t * get_json_hash_from_key( amjson_t *json, char *key) { assert(json->type == JSON_HASH); return g_hash_table_lookup(json->hash, key); } void foreach_json_array( amjson_t *json, GFunc func, gpointer user_data) { assert(json->type == JSON_ARRAY); g_ptr_array_foreach(json->array, func, user_data); } void foreach_json_hash( amjson_t *json, GHFunc func, gpointer user_data) { assert(json->type == JSON_HASH); g_hash_table_foreach(json->hash, func, user_data); }