/* * Copyright (c) 2016 Alexandru Ardelean. * * This is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See COPYING for details. * */ #include "config.h" #include "strerror_override.h" #include #include #include #include #include "json_object_private.h" #include "json_pointer.h" #include "json_pointer_private.h" #include "strdup_compat.h" #include "vasprintf_compat.h" /* Avoid ctype.h and locale overhead */ #define is_plain_digit(c) ((c) >= '0' && (c) <= '9') /** * JavaScript Object Notation (JSON) Pointer * RFC 6901 - https://tools.ietf.org/html/rfc6901 */ static void string_replace_all_occurrences_with_char(char *s, const char *occur, char repl_char) { size_t slen = strlen(s); size_t skip = strlen(occur) - 1; /* length of the occurrence, minus the char we're replacing */ char *p = s; while ((p = strstr(p, occur))) { *p = repl_char; p++; slen -= skip; memmove(p, (p + skip), slen - (p - s) + 1); /* includes null char too */ } } static int is_valid_index(const char *path, size_t *idx) { size_t i, len = strlen(path); /* this code-path optimizes a bit, for when we reference the 0-9 index range * in a JSON array and because leading zeros not allowed */ if (len == 1) { if (is_plain_digit(path[0])) { *idx = (path[0] - '0'); return 1; } errno = EINVAL; return 0; } /* leading zeros not allowed per RFC */ if (path[0] == '0') { errno = EINVAL; return 0; } /* RFC states base-10 decimals */ for (i = 0; i < len; i++) { if (!is_plain_digit(path[i])) { errno = EINVAL; return 0; } } // We know it's all digits, so the only error case here is overflow, // but ULLONG_MAX will be longer than any array length so that's ok. *idx = strtoull(path, NULL, 10); return 1; } static int json_pointer_get_single_path(struct json_object *obj, char *path, struct json_object **value, size_t *idx) { if (json_object_is_type(obj, json_type_array)) { if (!is_valid_index(path, idx)) return -1; if (*idx >= json_object_array_length(obj)) { errno = ENOENT; return -1; } obj = json_object_array_get_idx(obj, *idx); if (obj) { if (value) *value = obj; return 0; } /* Entry not found */ errno = ENOENT; return -1; } /* RFC states that we first must eval all ~1 then all ~0 */ string_replace_all_occurrences_with_char(path, "~1", '/'); string_replace_all_occurrences_with_char(path, "~0", '~'); if (!json_object_object_get_ex(obj, path, value)) { errno = ENOENT; return -1; } return 0; } static int json_object_array_put_idx_cb(struct json_object *parent, size_t idx, struct json_object *value, void *priv) { return json_object_array_put_idx(parent, idx, value); } static int json_pointer_set_single_path(struct json_object *parent, const char *path, struct json_object *value, json_pointer_array_set_cb array_set_cb, void *priv) { if (json_object_is_type(parent, json_type_array)) { size_t idx; /* RFC (Chapter 4) states that '-' may be used to add new elements to an array */ if (path[0] == '-' && path[1] == '\0') return json_object_array_add(parent, value); if (!is_valid_index(path, &idx)) return -1; return array_set_cb(parent, idx, value, priv); } /* path replacements should have been done in json_pointer_get_single_path(), * and we should still be good here */ if (json_object_is_type(parent, json_type_object)) return json_object_object_add(parent, path, value); /* Getting here means that we tried to "dereference" a primitive JSON type * (like string, int, bool).i.e. add a sub-object to it */ errno = ENOENT; return -1; } static int json_pointer_result_get_recursive(struct json_object *obj, char *path, struct json_pointer_get_result *res) { struct json_object *parent_obj = obj; size_t idx = 0; char *endp; int rc; /* All paths (on each recursion level must have a leading '/' */ if (path[0] != '/') { errno = EINVAL; return -1; } path++; endp = strchr(path, '/'); if (endp) *endp = '\0'; /* If we err-ed here, return here */ if ((rc = json_pointer_get_single_path(obj, path, &obj, &idx))) return rc; if (endp) { /* Put the slash back, so that the sanity check passes on next recursion level */ *endp = '/'; return json_pointer_result_get_recursive(obj, endp, res); } /* We should be at the end of the recursion here */ if (res) { res->parent = parent_obj; res->obj = obj; if (json_object_is_type(res->parent, json_type_array)) res->index_in_parent = idx; else res->key_in_parent = path; } return 0; } static int json_pointer_object_get_recursive(struct json_object *obj, char *path, struct json_object **value) { struct json_pointer_get_result res; int rc; rc = json_pointer_result_get_recursive(obj, path, &res); if (rc) return rc; if (value) *value = res.obj; return 0; } int json_pointer_get_internal(struct json_object *obj, const char *path, struct json_pointer_get_result *res) { char *path_copy = NULL; int rc; if (!obj || !path) { errno = EINVAL; return -1; } if (path[0] == '\0') { res->parent = NULL; res->obj = obj; res->key_in_parent = NULL; res->index_in_parent = UINT32_MAX; return 0; } /* pass a working copy to the recursive call */ if (!(path_copy = strdup(path))) { errno = ENOMEM; return -1; } rc = json_pointer_result_get_recursive(obj, path_copy, res); /* re-map the path string to the const-path string */ if (rc == 0 && json_object_is_type(res->parent, json_type_object) && res->key_in_parent) res->key_in_parent = path + (res->key_in_parent - path_copy); free(path_copy); return rc; } int json_pointer_get(struct json_object *obj, const char *path, struct json_object **res) { struct json_pointer_get_result jpres; int rc; rc = json_pointer_get_internal(obj, path, &jpres); if (rc) return rc; if (res) *res = jpres.obj; return 0; } int json_pointer_getf(struct json_object *obj, struct json_object **res, const char *path_fmt, ...) { char *path_copy = NULL; int rc = 0; va_list args; if (!obj || !path_fmt) { errno = EINVAL; return -1; } va_start(args, path_fmt); rc = vasprintf(&path_copy, path_fmt, args); va_end(args); if (rc < 0) return rc; if (path_copy[0] == '\0') { if (res) *res = obj; goto out; } rc = json_pointer_object_get_recursive(obj, path_copy, res); out: free(path_copy); return rc; } int json_pointer_set_with_array_cb(struct json_object **obj, const char *path, struct json_object *value, json_pointer_array_set_cb array_set_cb, void *priv) { const char *endp; char *path_copy = NULL; struct json_object *set = NULL; int rc; if (!obj || !path) { errno = EINVAL; return -1; } if (path[0] == '\0') { json_object_put(*obj); *obj = value; return 0; } if (path[0] != '/') { errno = EINVAL; return -1; } /* If there's only 1 level to set, stop here */ if ((endp = strrchr(path, '/')) == path) { path++; return json_pointer_set_single_path(*obj, path, value, array_set_cb, priv); } /* pass a working copy to the recursive call */ if (!(path_copy = strdup(path))) { errno = ENOMEM; return -1; } path_copy[endp - path] = '\0'; rc = json_pointer_object_get_recursive(*obj, path_copy, &set); free(path_copy); if (rc) return rc; endp++; return json_pointer_set_single_path(set, endp, value, array_set_cb, priv); } int json_pointer_set(struct json_object **obj, const char *path, struct json_object *value) { return json_pointer_set_with_array_cb(obj, path, value, json_object_array_put_idx_cb, NULL); } int json_pointer_setf(struct json_object **obj, struct json_object *value, const char *path_fmt, ...) { char *endp; char *path_copy = NULL; struct json_object *set = NULL; va_list args; int rc = 0; if (!obj || !path_fmt) { errno = EINVAL; return -1; } /* pass a working copy to the recursive call */ va_start(args, path_fmt); rc = vasprintf(&path_copy, path_fmt, args); va_end(args); if (rc < 0) return rc; if (path_copy[0] == '\0') { json_object_put(*obj); *obj = value; goto out; } if (path_copy[0] != '/') { errno = EINVAL; rc = -1; goto out; } /* If there's only 1 level to set, stop here */ if ((endp = strrchr(path_copy, '/')) == path_copy) { set = *obj; goto set_single_path; } *endp = '\0'; rc = json_pointer_object_get_recursive(*obj, path_copy, &set); if (rc) goto out; set_single_path: endp++; rc = json_pointer_set_single_path(set, endp, value, json_object_array_put_idx_cb, NULL); out: free(path_copy); return rc; }