diff --git a/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h b/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h index 20c92a82c0..2ffe0b2d3c 100644 --- a/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h +++ b/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h @@ -409,9 +409,10 @@ auto to_pointer( -> Pointer; /// @ingroup jsonpointer -/// Parse the URI fragment representation of a JSON Pointer, percent-decoding -/// the fragment data before interpretation as mandated by RFC 3986, Section -/// 2.4 and RFC 6901, Section 6. The result is not set if the URI has no +/// Parse the URI fragment representation of a JSON Pointer. Reference tokens +/// are separated before their octets are decoded, so a percent-encoded slash +/// neither separates two tokens, per RFC 3986, Section 2.2, nor belongs to +/// one, per RFC 6901, Section 3. The result is not set if the URI has no /// fragment or if its fragment is not a valid JSON Pointer. For example: /// /// ```cpp diff --git a/src/core/jsonpointer/jsonpointer.cc b/src/core/jsonpointer/jsonpointer.cc index 67470cbf09..983f5cc63a 100644 --- a/src/core/jsonpointer/jsonpointer.cc +++ b/src/core/jsonpointer/jsonpointer.cc @@ -11,7 +11,6 @@ #include "stringify.h" #include // assert -#include // std::size_t #include // std::cbegin, std::cend, std::prev, std::advance #include // std::allocator #include // std::optional, std::nullopt @@ -24,6 +23,29 @@ namespace { +// The slash that belongs to a reference token is spelled `~1`, and the +// transformation evaluation applies to a token is alone in turning that back +// into one, so a token still holding a slash once its octets are decoded +// conforms to no reference token and is an error condition rather than a +// member name +// See https://www.rfc-editor.org/rfc/rfc6901#section-3 +// See https://www.rfc-editor.org/rfc/rfc6901#section-4 +auto append_reference_token(sourcemeta::core::JSON::String &output, + const std::string_view token) -> bool { + if (token.find('%') == std::string_view::npos) { + output.append(token); + return true; + } + + const auto decoded{sourcemeta::core::URI::unescape(token)}; + if (decoded.find('/') != std::string::npos) { + return false; + } + + output.append(decoded); + return true; +} + template