From 5387aaf076c7ddd6b238944fe13670fdd9139abf Mon Sep 17 00:00:00 2001 From: Tristan Penman Date: Wed, 17 Jun 2015 10:00:13 +1000 Subject: [PATCH] Support ~0 and ~1 character escape sequences in JSON Reference tokens --- include/valijson/internal/json_pointer.hpp | 40 ++++++++++++++- tests/test_json_pointer.cpp | 59 +++++++++++++++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/include/valijson/internal/json_pointer.hpp b/include/valijson/internal/json_pointer.hpp index 5bb4326..0a8bdef 100644 --- a/include/valijson/internal/json_pointer.hpp +++ b/include/valijson/internal/json_pointer.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -13,6 +14,42 @@ namespace valijson { namespace internal { namespace json_pointer { +/** + * @brief Extract and transform the token between two iterators + * + * This function is responsible for extracting a JSON Reference token from + * between two iterators, and performing any necessary transformations, before + * returning the resulting string. Its main purpose is to replace escaped + * character sequences. + * + * From the JSON Pointer specification (RFC 6901, April 2013): + * + * Evaluation of each reference token begins by decoding any escaped + * character sequence. This is performed by first transforming any + * occurrence of the sequence '~1' to '/', and then transforming any + * occurrence of the sequence '~0' to '~'. By performing the + * substitutions in this order, an implementation avoids the error of + * turning '~01' first into '~1' and then into '/', which would be + * incorrect (the string '~01' correctly becomes '~1' after + * transformation). + * + * @param begin iterator pointing to beginning of a token + * @param end iterator pointing to one character past the end of the token + * + * @return string with escaped character sequences replaced + * + */ +inline std::string extractReferenceToken(std::string::const_iterator begin, + std::string::const_iterator end) +{ + std::string token(begin, end); + + boost::replace_all(token, "~1", "/"); + boost::replace_all(token, "~0", "~"); + + return token; +} + /** * @brief Recursively locate the value referenced by a JSON Pointer * @@ -69,7 +106,8 @@ inline AdapterType resolveJsonPointer( std::find(jsonPointerItr + 1, jsonPointerEnd, '/'); // Extract the next reference token - const std::string referenceToken(jsonPointerItr + 1, jsonPointerNext); + const std::string referenceToken = extractReferenceToken( + jsonPointerItr + 1, jsonPointerNext); // Empty reference tokens should be ignored if (referenceToken.empty()) { diff --git a/tests/test_json_pointer.cpp b/tests/test_json_pointer.cpp index 1517437..9d23d0f 100644 --- a/tests/test_json_pointer.cpp +++ b/tests/test_json_pointer.cpp @@ -163,7 +163,7 @@ std::vector > testArray.PushBack("test2", allocator); testCase = boost::make_shared( - "Resolveing '/test/3' in object containing one member containing " + "Resolving '/test/3' in object containing one member containing " "an array with 3 elements should throw an exception"); testCase->value.SetObject(); testCase->value.AddMember("test", testArray, allocator); @@ -172,6 +172,63 @@ std::vector > testCases.push_back(testCase); } + // + // The following tests ensure that escape sequences are handled correctly. + // + // From the JSON Pointer specification (RFC 6901, April 2013): + // + // Evaluation of each reference token begins by decoding any escaped + // character sequence. This is performed by first transforming any + // occurrence of the sequence '~1' to '/', and then transforming any + // occurrence of the sequence '~0' to '~'. By performing the + // substitutions in this order, an implementation avoids the error of + // turning '~01' first into '~1' and then into '/', which would be + // incorrect (the string '~01' correctly becomes '~1' after + // transformation). + // + + { + rapidjson::Value value; + value.SetDouble(10.); + + testCase = boost::make_shared( + "Resolving '/hello~1world' in object containing one member named " + "'hello/world' should return the associated value"); + testCase->value.SetObject(); + testCase->value.AddMember("hello/world", value, allocator); + testCase->jsonPointer = "/hello~1world"; + testCase->expectedValue = &testCase->value.FindMember("hello/world")->value; + testCases.push_back(testCase); + } + + { + rapidjson::Value value; + value.SetDouble(10.); + + testCase = boost::make_shared( + "Resolving '/hello~0world' in object containing one member named " + "'hello~world' should return the associated value"); + testCase->value.SetObject(); + testCase->value.AddMember("hello~world", value, allocator); + testCase->jsonPointer = "/hello~0world"; + testCase->expectedValue = &testCase->value.FindMember("hello~world")->value; + testCases.push_back(testCase); + } + + { + rapidjson::Value value; + value.SetDouble(10.); + + testCase = boost::make_shared( + "Resolving '/hello~01world' in object containing one member named " + "'hello~1world' should return the associated value"); + testCase->value.SetObject(); + testCase->value.AddMember("hello~1world", value, allocator); + testCase->jsonPointer = "/hello~01world"; + testCase->expectedValue = &testCase->value.FindMember("hello~1world")->value; + testCases.push_back(testCase); + } + return testCases; }