/* * Pseudo JSON parser * * Copyright (c) 2017 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include "cutils.h" #include "json.h" #include "fs_utils.h" static JSONValue parse_string(const char **pp) { char buf[4096], *q; const char *p; int c, h; q = buf; p = *pp; p++; for(;;) { c = *p++; if (c == '\0' || c == '\n') { return json_error_new("unterminated string"); } else if (c == '\"') { break; } else if (c == '\\') { c = *p++; switch(c) { case '\'': case '\"': case '\\': goto add_char; case 'n': c = '\n'; goto add_char; case 'r': c = '\r'; goto add_char; case 't': c = '\t'; goto add_char; case 'x': h = from_hex(*p++); if (h < 0) return json_error_new("invalig hex digit"); c = h << 4; h = from_hex(*p++); if (h < 0) return json_error_new("invalig hex digit"); c |= h; goto add_char; default: return json_error_new("unknown escape code"); } } else { add_char: if (q >= buf + sizeof(buf) - 1) return json_error_new("string too long"); *q++ = c; } } *q = '\0'; *pp = p; return json_string_new(buf); } static JSONProperty *json_object_get2(JSONObject *obj, const char *name) { JSONProperty *f; int i; for(i = 0; i < obj->len; i++) { f = &obj->props[i]; if (!strcmp(f->name.u.str->data, name)) return f; } return NULL; } JSONValue json_object_get(JSONValue val, const char *name) { JSONProperty *f; JSONObject *obj; if (val.type != JSON_OBJ) return json_undefined_new(); obj = val.u.obj; f = json_object_get2(obj, name); if (!f) return json_undefined_new(); return f->value; } int json_object_set(JSONValue val, const char *name, JSONValue prop_val) { JSONObject *obj; JSONProperty *f; int new_size; if (val.type != JSON_OBJ) return -1; obj = val.u.obj; f = json_object_get2(obj, name); if (f) { json_free(f->value); f->value = prop_val; } else { if (obj->len >= obj->size) { new_size = max_int(obj->len + 1, obj->size * 3 / 2); obj->props = realloc(obj->props, new_size * sizeof(JSONProperty)); obj->size = new_size; } f = &obj->props[obj->len++]; f->name = json_string_new(name); f->value = prop_val; } return 0; } JSONValue json_array_get(JSONValue val, unsigned int idx) { JSONArray *array; if (val.type != JSON_ARRAY) return json_undefined_new(); array = val.u.array; if (idx < array->len) { return array->tab[idx]; } else { return json_undefined_new(); } } int json_array_set(JSONValue val, unsigned int idx, JSONValue prop_val) { JSONArray *array; int new_size; if (val.type != JSON_ARRAY) return -1; array = val.u.array; if (idx < array->len) { json_free(array->tab[idx]); array->tab[idx] = prop_val; } else if (idx == array->len) { if (array->len >= array->size) { new_size = max_int(array->len + 1, array->size * 3 / 2); array->tab = realloc(array->tab, new_size * sizeof(JSONValue)); array->size = new_size; } array->tab[array->len++] = prop_val; } else { return -1; } return 0; } const char *json_get_str(JSONValue val) { if (val.type != JSON_STR) return NULL; return val.u.str->data; } const char *json_get_error(JSONValue val) { if (val.type != JSON_EXCEPTION) return NULL; return val.u.str->data; } JSONValue json_string_new2(const char *str, int len) { JSONValue val; JSONString *str1; str1 = malloc(sizeof(JSONString) + len + 1); str1->len = len; memcpy(str1->data, str, len + 1); val.type = JSON_STR; val.u.str = str1; return val; } JSONValue json_string_new(const char *str) { return json_string_new2(str, strlen(str)); } JSONValue __attribute__((format(printf, 1, 2))) json_error_new(const char *fmt, ...) { JSONValue val; va_list ap; char buf[256]; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); val = json_string_new(buf); val.type = JSON_EXCEPTION; return val; } JSONValue json_object_new(void) { JSONValue val; JSONObject *obj; obj = mallocz(sizeof(JSONObject)); val.type = JSON_OBJ; val.u.obj = obj; return val; } JSONValue json_array_new(void) { JSONValue val; JSONArray *array; array = mallocz(sizeof(JSONArray)); val.type = JSON_ARRAY; val.u.array = array; return val; } void json_free(JSONValue val) { switch(val.type) { case JSON_STR: case JSON_EXCEPTION: free(val.u.str); break; case JSON_INT: case JSON_BOOL: case JSON_NULL: case JSON_UNDEFINED: break; case JSON_ARRAY: { JSONArray *array = val.u.array; int i; for(i = 0; i < array->len; i++) { json_free(array->tab[i]); } free(array); } break; case JSON_OBJ: { JSONObject *obj = val.u.obj; JSONProperty *f; int i; for(i = 0; i < obj->len; i++) { f = &obj->props[i]; json_free(f->name); json_free(f->value); } free(obj); } break; default: abort(); } } static void skip_spaces(const char **pp) { const char *p; p = *pp; for(;;) { if (isspace(*p)) { p++; } else if (p[0] == '/' && p[1] == '/') { p += 2; while (*p != '\0' && *p != '\n') p++; } else if (p[0] == '/' && p[1] == '*') { p += 2; while (*p != '\0' && (p[0] != '*' || p[1] != '/')) p++; if (*p != '\0') p += 2; } else { break; } } *pp = p; } static inline BOOL is_ident_first(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$'; } static int parse_ident(char *buf, int buf_size, const char **pp) { char *q; const char *p; p = *pp; q = buf; *q++ = *p++; /* first char is already tested */ while (is_ident_first(*p) || isdigit(*p)) { if ((q - buf) >= buf_size - 1) return -1; *q++ = *p++; } *pp = p; *q = '\0'; return 0; } JSONValue json_parse_value2(const char **pp) { char buf[128]; const char *p; JSONValue val, val1, tag; p = *pp; skip_spaces(&p); if (*p == '\0') { return json_error_new("unexpected end of file"); } if (isdigit(*p)) { val = json_int32_new(strtol(p, (char **)&p, 0)); } else if (*p == '"') { val = parse_string(&p); } else if (*p == '{') { p++; val = json_object_new(); for(;;) { skip_spaces(&p); if (*p == '}') { p++; break; } if (*p == '"') { tag = parse_string(&p); if (json_is_error(tag)) return tag; } else if (is_ident_first(*p)) { if (parse_ident(buf, sizeof(buf), &p) < 0) goto invalid_prop; tag = json_string_new(buf); } else { goto invalid_prop; } // printf("property: %s\n", json_get_str(tag)); if (tag.u.str->len == 0) { invalid_prop: return json_error_new("Invalid property name"); } skip_spaces(&p); if (*p != ':') { return json_error_new("':' expected"); } p++; val1 = json_parse_value2(&p); json_object_set(val, tag.u.str->data, val1); skip_spaces(&p); if (*p == ',') { p++; } else if (*p != '}') { return json_error_new("expecting ',' or '}'"); } } } else if (*p == '[') { int idx; p++; val = json_array_new(); idx = 0; for(;;) { skip_spaces(&p); if (*p == ']') { p++; break; } val1 = json_parse_value2(&p); json_array_set(val, idx++, val1); skip_spaces(&p); if (*p == ',') { p++; } else if (*p != ']') { return json_error_new("expecting ',' or ']'"); } } } else if (is_ident_first(*p)) { if (parse_ident(buf, sizeof(buf), &p) < 0) goto unknown_id; if (!strcmp(buf, "null")) { val = json_null_new(); } else if (!strcmp(buf, "true")) { val = json_bool_new(TRUE); } else if (!strcmp(buf, "false")) { val = json_bool_new(FALSE); } else { unknown_id: return json_error_new("unknown identifier: '%s'", buf); } } else { return json_error_new("unexpected character"); } *pp = p; return val; } JSONValue json_parse_value(const char *p) { JSONValue val; val = json_parse_value2(&p); if (json_is_error(val)) return val; skip_spaces(&p); if (*p != '\0') { json_free(val); return json_error_new("unexpected characters at the end"); } return val; } JSONValue json_parse_value_len(const char *p, int len) { char *str; JSONValue val; str = malloc(len + 1); memcpy(str, p, len); str[len] = '\0'; val = json_parse_value(str); free(str); return val; }