From dcdea4e906e3786675c059fbdd94899434afd539 Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Sat, 22 Aug 2026 12:01:01 +0530 Subject: [PATCH] decode ~1/~0 escapes when setting an object member in json_pointer --- json_pointer.c | 27 ++++++++++++++++++--------- tests/test_json_pointer.c | 19 +++++++++++++++++++ tests/test_json_pointer.expected | 1 + 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/json_pointer.c b/json_pointer.c index 9d7ad18..41ac620 100644 --- a/json_pointer.c +++ b/json_pointer.c @@ -141,19 +141,28 @@ static int json_pointer_set_single_path(struct json_object *parent, const char * return set_cb(parent, NULL, 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)) { + /* Unlike the getter, which decodes the reference token in + * json_pointer_get_single_path(), nothing had decoded the '~1'/'~0' + * escapes on the way here, so a member whose name contains '/' or '~' + * was addressed under its still-escaped spelling. Decode a copy per + * RFC 6901 (first '~1' then '~0') before using it as the key. + */ + char *key = strdup(path); + int rc; + + if (key == NULL) + return -1; + string_replace_all_occurrences_with_char(key, "~1", '/'); + string_replace_all_occurrences_with_char(key, "~0", '~'); + if (cb_handles_obj) - { - return set_cb(parent, path, (size_t)-1, value, priv); - } + rc = set_cb(parent, key, (size_t)-1, value, priv); else - { - return json_object_object_add(parent, path, value); - } + rc = json_object_object_add(parent, key, value); + free(key); + return rc; } /* Getting here means that we tried to "dereference" a primitive JSON type diff --git a/tests/test_json_pointer.c b/tests/test_json_pointer.c index b18c98a..fb52a0d 100644 --- a/tests/test_json_pointer.c +++ b/tests/test_json_pointer.c @@ -268,6 +268,25 @@ static void test_example_set(void) printf("PASSED - SET - Final JSON is: %s\n", json_object_get_string(jo1)); json_object_put(jo2); + /* RFC 6901 escapes must be decoded on the set path too, just as the + * getter does, so "/a~1b" addresses the member "a/b" and "/m~0n" + * addresses "m~n" rather than creating keys spelled "a~1b"/"m~0n". */ + { + struct json_object *esc = json_tokener_parse("{ 'a/b': 1, 'm~n': 2 }"); + struct json_object *v = NULL; + assert(0 == json_pointer_set(&esc, "/a~1b", json_object_new_int(11))); + assert(0 == json_pointer_setf(&esc, json_object_new_int(22), "/m~0n")); + assert(0 == json_pointer_get(esc, "/a~1b", &v)); + assert(11 == json_object_get_int(v)); + assert(0 == json_pointer_get(esc, "/m~0n", &v)); + assert(22 == json_object_get_int(v)); + assert(NULL == json_object_object_get(esc, "a~1b")); + assert(NULL == json_object_object_get(esc, "m~0n")); + assert(2 == json_object_object_length(esc)); + printf("PASSED - SET - escaped keys /a~1b and /m~0n\n"); + json_object_put(esc); + } + assert(0 == json_pointer_set(&jo1, "", json_object_new_int(10))); assert(10 == json_object_get_int(jo1)); printf("%s\n", json_object_get_string(jo1)); diff --git a/tests/test_json_pointer.expected b/tests/test_json_pointer.expected index 3b363a5..18d5ea3 100644 --- a/tests/test_json_pointer.expected +++ b/tests/test_json_pointer.expected @@ -29,6 +29,7 @@ PASSED - SET - /fug/gaw == [0,2,3] PASSED - SET - /fug/gaw == [0,2,3,4] PASSED - SET - / == 9 PASSED - SET - Final JSON is: { "foo": [ "bar", "cod" ], "": 9, "a\/b": 1, "c%d": 2, "e^f": 3, "g|h": 4, "i\\j": 5, "k\"l": 6, " ": 7, "m~n": 8, "fud": { "gaw": [ 0, 2, 3, 4 ] } } +PASSED - SET - escaped keys /a~1b and /m~0n 10 PASSED - SET - LOADED TEST JSON { "foo": [ "bar", "baz" ], "": 0, "a\/b": 1, "c%d": 2, "e^f": 3, "g|h": 4, "i\\j": 5, "k\"l": 6, " ": 7, "m~n": 8 }