diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c index 2bddd8c46..412f26f17 100644 --- a/grub-core/lib/json/json.c +++ b/grub-core/lib/json/json.c @@ -17,7 +17,251 @@ */ #include +#include +#define JSMN_STATIC #include "jsmn.h" +#include "json.h" GRUB_MOD_LICENSE ("GPLv3"); + +grub_err_t +grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len) +{ + grub_json_t *json = NULL; + jsmn_parser parser; + grub_err_t ret = GRUB_ERR_NONE; + int jsmn_ret; + + if (!string) + return GRUB_ERR_BAD_ARGUMENT; + + json = grub_zalloc (sizeof (*json)); + if (!json) + return GRUB_ERR_OUT_OF_MEMORY; + json->string = string; + + /* + * Parse the string twice: first to determine how many tokens + * we need to allocate, second to fill allocated tokens. + */ + jsmn_init (&parser); + jsmn_ret = jsmn_parse (&parser, string, string_len, NULL, 0); + if (jsmn_ret <= 0) + { + ret = GRUB_ERR_BAD_ARGUMENT; + goto err; + } + + json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_ret); + if (!json->tokens) + { + ret = GRUB_ERR_OUT_OF_MEMORY; + goto err; + } + + jsmn_init (&parser); + jsmn_ret = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_ret); + if (jsmn_ret <= 0) + { + ret = GRUB_ERR_BAD_ARGUMENT; + goto err; + } + + *out = json; + + err: + if (ret && json) + { + grub_free (json->string); + grub_free (json->tokens); + grub_free (json); + } + return ret; +} + +void +grub_json_free (grub_json_t *json) +{ + if (json) + { + grub_free (json->tokens); + grub_free (json); + } +} + +grub_err_t +grub_json_getsize (grub_size_t *out, const grub_json_t *json) +{ + int size; + + size = ((jsmntok_t *)json->tokens)[json->idx].size; + if (size < 0) + return GRUB_ERR_OUT_OF_RANGE; + + *out = (grub_size_t) size; + return GRUB_ERR_NONE; +} + +grub_err_t +grub_json_gettype (grub_json_type_t *out, const grub_json_t *json) +{ + switch (((jsmntok_t *)json->tokens)[json->idx].type) + { + case JSMN_OBJECT: + *out = GRUB_JSON_OBJECT; + break; + case JSMN_ARRAY: + *out = GRUB_JSON_ARRAY; + break; + case JSMN_STRING: + *out = GRUB_JSON_STRING; + break; + case JSMN_PRIMITIVE: + *out = GRUB_JSON_PRIMITIVE; + break; + default: + return GRUB_ERR_BAD_ARGUMENT; + } + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n) +{ + grub_size_t offset = 1, size; + jsmntok_t *p; + + if (grub_json_getsize (&size, parent) || n >= size) + return GRUB_ERR_OUT_OF_RANGE; + + /* + * Skip the first n children. For each of the children, we need + * to skip their own potential children (e.g. if it's an + * array), as well. We thus add the children's size to n on + * each iteration. + */ + p = &((jsmntok_t *)parent->tokens)[parent->idx]; + while (n--) + n += p[offset++].size; + + out->string = parent->string; + out->tokens = parent->tokens; + out->idx = parent->idx + offset; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key) +{ + grub_json_type_t type; + grub_size_t i, size; + + if (grub_json_gettype (&type, parent) || type != GRUB_JSON_OBJECT) + return GRUB_ERR_BAD_ARGUMENT; + + if (grub_json_getsize (&size, parent)) + return GRUB_ERR_BAD_ARGUMENT; + + for (i = 0; i < size; i++) + { + grub_json_t child; + const char *s; + + if (grub_json_getchild (&child, parent, i) || + grub_json_getstring (&s, &child, NULL) || + grub_strcmp (s, key) != 0) + continue; + + return grub_json_getchild (out, &child, 0); + } + + return GRUB_ERR_FILE_NOT_FOUND; +} + +static grub_err_t +get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key) +{ + const grub_json_t *p = parent; + grub_json_t child; + grub_err_t ret; + jsmntok_t *tok; + + if (key) + { + ret = grub_json_getvalue (&child, parent, key); + if (ret) + return ret; + p = &child; + } + + tok = &((jsmntok_t *) p->tokens)[p->idx]; + p->string[tok->end] = '\0'; + + *out_string = p->string + tok->start; + + return grub_json_gettype (out_type, p); +} + +grub_err_t +grub_json_getstring (const char **out, const grub_json_t *parent, const char *key) +{ + grub_json_type_t type; + const char *value; + grub_err_t ret; + + ret = get_value (&type, &value, parent, key); + if (ret) + return ret; + if (type != GRUB_JSON_STRING) + return GRUB_ERR_BAD_ARGUMENT; + + *out = value; + return GRUB_ERR_NONE; +} + +grub_err_t +grub_json_getuint64 (grub_uint64_t *out, const grub_json_t *parent, const char *key) +{ + grub_json_type_t type; + const char *value; + char *end; + grub_err_t ret; + + ret = get_value (&type, &value, parent, key); + if (ret) + return ret; + if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE) + return GRUB_ERR_BAD_ARGUMENT; + + grub_errno = GRUB_ERR_NONE; + *out = grub_strtoul (value, &end, 10); + if (grub_errno != GRUB_ERR_NONE || *end) + return GRUB_ERR_BAD_NUMBER; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_json_getint64 (grub_int64_t *out, const grub_json_t *parent, const char *key) +{ + grub_json_type_t type; + const char *value; + char *end; + grub_err_t ret; + + ret = get_value (&type, &value, parent, key); + if (ret) + return ret; + if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE) + return GRUB_ERR_BAD_ARGUMENT; + + grub_errno = GRUB_ERR_NONE; + *out = grub_strtol (value, &end, 10); + if (grub_errno != GRUB_ERR_NONE || *end) + return GRUB_ERR_BAD_NUMBER; + + return GRUB_ERR_NONE; +} diff --git a/grub-core/lib/json/json.h b/grub-core/lib/json/json.h new file mode 100644 index 000000000..358e4bca3 --- /dev/null +++ b/grub-core/lib/json/json.h @@ -0,0 +1,122 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2019 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_JSON_JSON_H +#define GRUB_JSON_JSON_H 1 + +#include + +enum grub_json_type +{ + /* Unordered collection of key-value pairs. */ + GRUB_JSON_OBJECT, + /* Ordered list of zero or more values. */ + GRUB_JSON_ARRAY, + /* Zero or more Unicode characters. */ + GRUB_JSON_STRING, + /* Number, boolean or empty value. */ + GRUB_JSON_PRIMITIVE, + /* Invalid token. */ + GRUB_JSON_UNDEFINED, +}; +typedef enum grub_json_type grub_json_type_t; + +struct grub_json +{ + void *tokens; + char *string; + grub_size_t idx; +}; +typedef struct grub_json grub_json_t; + +/* + * Parse a JSON-encoded string. Note that the string passed to + * this function will get modified on subsequent calls to + * grub_json_get*(). Returns the root object of the parsed JSON + * object, which needs to be free'd via grub_json_free(). + */ +extern grub_err_t EXPORT_FUNC(grub_json_parse) (grub_json_t **out, + char *string, + grub_size_t string_len); + +/* + * Free the structure and its contents. The string passed to + * grub_json_parse() will not be free'd. + */ +extern void EXPORT_FUNC(grub_json_free) (grub_json_t *json); + +/* + * Get the child count of a valid grub_json_t instance. Children + * are present for arrays, objects (dicts) and keys of a dict. + */ +extern grub_err_t EXPORT_FUNC(grub_json_getsize) (grub_size_t *out, + const grub_json_t *json); + +/* Get the type of a valid grub_json_t instance. */ +extern grub_err_t EXPORT_FUNC(grub_json_gettype) (grub_json_type_t *out, + const grub_json_t *json); + +/* + * Get n'th child of a valid object, array or key. Will return an + * error if no such child exists. The result does not need to be + * free'd. + */ +extern grub_err_t EXPORT_FUNC(grub_json_getchild) (grub_json_t *out, + const grub_json_t *parent, + grub_size_t n); + +/* + * Get value of key from a valid grub_json_t instance. The result + * does not need to be free'd. + */ +extern grub_err_t EXPORT_FUNC(grub_json_getvalue) (grub_json_t *out, + const grub_json_t *parent, + const char *key); + +/* + * Get the string representation of a valid grub_json_t instance. + * If a key is given and parent is a JSON object, this function + * will return the string value of a child mapping to the key. + * If no key is given, it will return the string value of the + * parent itself. + */ +extern grub_err_t EXPORT_FUNC(grub_json_getstring) (const char **out, + const grub_json_t *parent, + const char *key); + +/* + * Get the uint64 representation of a valid grub_json_t instance. + * Returns an error if the value pointed to by `parent` cannot be + * converted to an uint64. See grub_json_getstring() for details + * on the key parameter. + */ +extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out, + const grub_json_t *parent, + const char *key); + +/* + * Get the int64 representation of a valid grub_json_t instance. + * Returns an error if the value pointed to by `parent` cannot be + * converted to an int64. See grub_json_getstring() for + * details on the key parameter. + */ +extern grub_err_t EXPORT_FUNC(grub_json_getint64) (grub_int64_t *out, + const grub_json_t *parent, + const char *key); + +#endif