mirror of
https://github.com/json-c/json-c.git
synced 2026-11-05 21:20:16 +08:00
json_patch: remove members whose names contain '/' or '~'
json_pointer_get_internal() decodes the last reference token in a private copy of the path, then pointed key_in_parent at the same offset in the caller's path, where the token is still escaped. For a path like "/a~1b" that gave "a~1b" instead of "a/b", so json_patch's remove, and the remove step of move, called json_object_object_del() with a key that doesn't exist. Nothing was removed and the patch still reported success; a move left the source member in place. Point key_in_parent at the key as it is stored in the parent object instead, which is always the decoded name. Added remove and move cases with escaped member names to json_patch_tests.json and refreshed test_json_patch.expected.
This commit is contained in:
1 parent
e77ca4fb3d
commit
86fe287188
4 files changed
+26
-3
No files matched your search
+11
-2
@@ -18,6 +18,7 @@
|
|||||||
#include "json_object_private.h"
|
#include "json_object_private.h"
|
||||||
#include "json_pointer.h"
|
#include "json_pointer.h"
|
||||||
#include "json_pointer_private.h"
|
#include "json_pointer_private.h"
|
||||||
|
#include "linkhash.h"
|
||||||
#include "strdup_compat.h"
|
#include "strdup_compat.h"
|
||||||
#include "vasprintf_compat.h"
|
#include "vasprintf_compat.h"
|
||||||
|
|
||||||
@@ -260,9 +261,17 @@ int json_pointer_get_internal(struct json_object *obj, const char *path,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
rc = json_pointer_result_get_recursive(obj, path_copy, res);
|
rc = json_pointer_result_get_recursive(obj, path_copy, res);
|
||||||
/* re-map the path string to the const-path string */
|
/* key_in_parent points at the decoded reference token in path_copy,
|
||||||
|
* which is freed below. The same offset in the caller's path is still
|
||||||
|
* escaped ("a~1b" rather than "a/b"), so use the key as it is stored in
|
||||||
|
* the parent object instead.
|
||||||
|
*/
|
||||||
if (rc == 0 && json_object_is_type(res->parent, json_type_object) && res->key_in_parent)
|
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);
|
{
|
||||||
|
struct lh_entry *entry =
|
||||||
|
lh_table_lookup_entry(json_object_get_object(res->parent), res->key_in_parent);
|
||||||
|
res->key_in_parent = entry ? (const char *)lh_entry_k(entry) : NULL;
|
||||||
|
}
|
||||||
free(path_copy);
|
free(path_copy);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ struct json_pointer_get_result {
|
|||||||
struct json_object *parent;
|
struct json_object *parent;
|
||||||
struct json_object *obj;
|
struct json_object *obj;
|
||||||
// The key of the found object; only valid when parent is json_type_object
|
// The key of the found object; only valid when parent is json_type_object
|
||||||
// Caution: re-uses tail end of the `path` argument to json_pointer_get_internal
|
// Caution: points at the key stored in `parent`, so it is only valid
|
||||||
|
// as long as that member exists
|
||||||
const char *key_in_parent;
|
const char *key_in_parent;
|
||||||
// the index of the found object; only valid when parent is json_type_array
|
// the index of the found object; only valid when parent is json_type_array
|
||||||
uint32_t index_in_parent;
|
uint32_t index_in_parent;
|
||||||
|
|||||||
@@ -380,6 +380,17 @@
|
|||||||
"patch": [{"op": "copy", "from": "/aa", "path": "/aab"}],
|
"patch": [{"op": "copy", "from": "/aa", "path": "/aab"}],
|
||||||
"expected": {"aa": 1, "aab": 1} },
|
"expected": {"aa": 1, "aab": 1} },
|
||||||
|
|
||||||
|
{ "comment": "Remove members whose names need escaping",
|
||||||
|
"doc": {"a/b": 1, "m~n": 2, "c": 3},
|
||||||
|
"patch": [{"op": "remove", "path": "/a~1b"},
|
||||||
|
{"op": "remove", "path": "/m~0n"}],
|
||||||
|
"expected": {"c": 3} },
|
||||||
|
|
||||||
|
{ "comment": "Move from a member whose name needs escaping",
|
||||||
|
"doc": {"a/b": 1},
|
||||||
|
"patch": [{"op": "move", "from": "/a~1b", "path": "/c"}],
|
||||||
|
"expected": {"c": 1} },
|
||||||
|
|
||||||
{ "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
|
{ "doc": {"foo": 1, "baz": [{"qux": "hello"}]},
|
||||||
"patch": [{"op": "move", "from": "/foo", "path": "/bar"}],
|
"patch": [{"op": "move", "from": "/foo", "path": "/bar"}],
|
||||||
"expected": {"baz": [{"qux": "hello"}], "bar": 1} },
|
"expected": {"baz": [{"qux": "hello"}], "bar": 1} },
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ Testing '(null)', doc '{ "foo": [ "bar", "baz" ], "": 0, "a\/b": 1, "c%d": 2, "e
|
|||||||
Testing 'Move to same location has no effect', doc '{ "foo": 1 }' patch '[ { "op": "move", "from": "\/foo", "path": "\/foo" } ]' : OK
|
Testing 'Move to same location has no effect', doc '{ "foo": 1 }' patch '[ { "op": "move", "from": "\/foo", "path": "\/foo" } ]' : OK
|
||||||
Testing 'Move between sibling keys sharing a name prefix is not a parent/child move', doc '{ "aa": 1, "aab": 2 }' patch '[ { "op": "move", "from": "\/aa", "path": "\/aab" } ]' : OK
|
Testing 'Move between sibling keys sharing a name prefix is not a parent/child move', doc '{ "aa": 1, "aab": 2 }' patch '[ { "op": "move", "from": "\/aa", "path": "\/aab" } ]' : OK
|
||||||
Testing 'Copy between sibling keys sharing a name prefix is not a parent/child move', doc '{ "aa": 1, "aab": 2 }' patch '[ { "op": "copy", "from": "\/aa", "path": "\/aab" } ]' : OK
|
Testing 'Copy between sibling keys sharing a name prefix is not a parent/child move', doc '{ "aa": 1, "aab": 2 }' patch '[ { "op": "copy", "from": "\/aa", "path": "\/aab" } ]' : OK
|
||||||
|
Testing 'Remove members whose names need escaping', doc '{ "a\/b": 1, "m~n": 2, "c": 3 }' patch '[ { "op": "remove", "path": "\/a~1b" }, { "op": "remove", "path": "\/m~0n" } ]' : OK
|
||||||
|
Testing 'Move from a member whose name needs escaping', doc '{ "a\/b": 1 }' patch '[ { "op": "move", "from": "\/a~1b", "path": "\/c" } ]' : OK
|
||||||
Testing '(null)', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "move", "from": "\/foo", "path": "\/bar" } ]' : OK
|
Testing '(null)', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "move", "from": "\/foo", "path": "\/bar" } ]' : OK
|
||||||
Testing '(null)', doc '{ "baz": [ { "qux": "hello" } ], "bar": 1 }' patch '[ { "op": "move", "from": "\/baz\/0\/qux", "path": "\/baz\/1" } ]' : OK
|
Testing '(null)', doc '{ "baz": [ { "qux": "hello" } ], "bar": 1 }' patch '[ { "op": "move", "from": "\/baz\/0\/qux", "path": "\/baz\/1" } ]' : OK
|
||||||
Testing '(null)', doc '{ "baz": [ { "qux": "hello" } ], "bar": 1 }' patch '[ { "op": "copy", "from": "\/baz\/0", "path": "\/boo" } ]' : OK
|
Testing '(null)', doc '{ "baz": [ { "qux": "hello" } ], "bar": 1 }' patch '[ { "op": "copy", "from": "\/baz\/0", "path": "\/boo" } ]' : OK
|
||||||
|
|||||||
Reference in new issue
Block a user