json_patch: fix move/copy false rejection between sibling keys sharing a prefix

json_patch_apply_move_copy() checks that "from" is not a proper prefix
of "path" per RFC 6902 section 4.4 (a location cannot be moved into one
of its own children), but it did so with a plain strncmp() over the raw
pointer strings, without checking that the shared run of characters
actually ends on a JSON Pointer reference-token boundary in "path".

As a result, moving or copying between two sibling object keys where
one name is a textual prefix of the other, e.g. "/aa" to "/aab", was
rejected with "Invalid attempt to move parent under a child" even
though "/aa" is not an ancestor pointer of "/aab" at all, just an
unrelated key that happens to share a few leading characters.

The fix requires that the character in "path" right after the shared
"from" prefix be either the end of the string (equal paths, a no-op)
or a '/' (a genuine nested reference token), before treating it as an
attempt to move a location under its own descendant.

Added two cases to json_patch_tests.json covering the move and copy
operations between such sibling keys, and refreshed the corresponding
golden test_json_patch.expected output.
This commit is contained in:
Afonso Januário committed 2026-09-25 23:52:49 +01:00
1 parent 25895a587e
commit 1b504e26ab
3 files changed
+24 -2

No files matched your search

+12 -2
View File
@@ -215,7 +215,17 @@ static int json_patch_apply_move_copy(struct json_object **res,
}
from_s_len = strlen(from_s);
if (strncmp(from_s, path, from_s_len) == 0) {
/**
* "from" is only a (possibly improper) prefix of "path" when the
* shared run of characters ends on a reference-token boundary in
* "path", i.e. the next character is '/' or the strings end there.
* Without the boundary check, "from" being a plain string prefix of
* "path" (e.g. "/aa" and "/aab") was mistaken for "from" being an
* ancestor pointer of "path", rejecting moves/copies between mere
* sibling keys that happen to share a name prefix.
*/
if (strncmp(from_s, path, from_s_len) == 0 &&
(path[from_s_len] == '\0' || path[from_s_len] == '/')) {
/**
* If lengths match, it's a noop, if they don't,
* then we're trying to move a parent under a child
@@ -223,7 +233,7 @@ static int json_patch_apply_move_copy(struct json_object **res,
* The "from" location MUST NOT be a proper prefix of the "path"
* location; i.e., a location cannot be moved into one of its children.
*/
if (from_s_len == strlen(path))
if (path[from_s_len] == '\0')
return 0;
_set_err(EINVAL, "Invalid attempt to move parent under a child");
return -1;