From ef42dae296f1f8b807ac6638f23dccd9c5d9ef94 Mon Sep 17 00:00:00 2001 From: Ray Vincent Date: Mon, 23 Aug 2021 15:45:49 -0700 Subject: [PATCH 1/5] Add support for urn document references --- CMakeLists.txt | 3 +- include/valijson/internal/uri.hpp | 19 ++++- include/valijson/schema_parser.hpp | 8 +- ..._fetch_absolute_uri_document_callback.cpp} | 12 +-- tests/test_fetch_urn_document_callback.cpp | 80 +++++++++++++++++++ 5 files changed, 110 insertions(+), 12 deletions(-) rename tests/{test_fetch_document_callback.cpp => test_fetch_absolute_uri_document_callback.cpp} (86%) create mode 100644 tests/test_fetch_urn_document_callback.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 64f901d..74f4013 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,8 @@ if(valijson_BUILD_TESTS) set(TEST_SOURCES tests/test_adapter_comparison.cpp - tests/test_fetch_document_callback.cpp + tests/test_fetch_urn_document_callback.cpp + tests/test_fetch_absolute_uri_document_callback.cpp tests/test_json_pointer.cpp tests/test_json11_adapter.cpp tests/test_jsoncpp_adapter.cpp diff --git a/include/valijson/internal/uri.hpp b/include/valijson/internal/uri.hpp index d04a18b..232bfa0 100644 --- a/include/valijson/internal/uri.hpp +++ b/include/valijson/internal/uri.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include namespace valijson { @@ -19,8 +20,22 @@ inline bool isUriAbsolute(const std::string &documentUri) } /** - * Placeholder function to resolve a relative URI within a given scope - */ + * @brief Placeholder function to check whether a URI is a URN + * + * This function validates that the URI matches the RFC 8141 + */ +inline bool isUrn(const std::string &documentUri) { + static const std::regex pattern( + "^((urn)|(URN)):[a-zA-Z0-9]+[-]{0,1}[\\.a-zA-Z0-9]+(:[-a-zA-Z0-9\\\\._~%!" + "$&'()\\/*+,;=]+)+(\\?[-a-zA-Z0-9\\\\._~%!$&'()\\/" + "*+,;:=]+){0,1}(#[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}"); + + return std::regex_match(documentUri, pattern); +} + +/** + * Placeholder function to resolve a relative URI within a given scope + */ inline std::string resolveRelativeUri( const std::string &resolutionScope, const std::string &relativeUri) diff --git a/include/valijson/schema_parser.hpp b/include/valijson/schema_parser.hpp index 908cd18..846597b 100644 --- a/include/valijson/schema_parser.hpp +++ b/include/valijson/schema_parser.hpp @@ -200,7 +200,7 @@ private: { if (resolutionScope) { if (documentUri) { - if (internal::uri::isUriAbsolute(*documentUri)) { + if (internal::uri::isUriAbsolute(*documentUri) || internal::uri::isUrn(*documentUri)) { return *documentUri; } else { return internal::uri::resolveRelativeUri(*resolutionScope, *documentUri); @@ -210,6 +210,8 @@ private: } } else if (documentUri && internal::uri::isUriAbsolute(*documentUri)) { return *documentUri; + } else if (documentUri && internal::uri::isUrn(*documentUri)) { + return *documentUri; } else { return opt::optional(); } @@ -600,7 +602,7 @@ private: if ((itr = object.find("id")) != object.end() && itr->second.maybeString()) { const std::string id = itr->second.asString(); rootSchema.setSubschemaId(&subschema, itr->second.asString()); - if (!currentScope || internal::uri::isUriAbsolute(id)) { + if (!currentScope || internal::uri::isUriAbsolute(id) || internal::uri::isUrn(id)) { updatedScope = id; } else { updatedScope = internal::uri::resolveRelativeUri(*currentScope, id); @@ -993,7 +995,7 @@ private: const std::string actualJsonPointer = sanitiseJsonPointer( internal::json_reference::getJsonReferencePointer(jsonRef)); - if (documentUri && internal::uri::isUriAbsolute(*documentUri)) { + if (documentUri && (internal::uri::isUriAbsolute(*documentUri) || internal::uri::isUrn(*documentUri))) { // Resolve reference against remote document if (!fetchDoc) { throwRuntimeError("Fetching of remote JSON References not enabled."); diff --git a/tests/test_fetch_document_callback.cpp b/tests/test_fetch_absolute_uri_document_callback.cpp similarity index 86% rename from tests/test_fetch_document_callback.cpp rename to tests/test_fetch_absolute_uri_document_callback.cpp index b8fd47b..c1e703c 100644 --- a/tests/test_fetch_document_callback.cpp +++ b/tests/test_fetch_absolute_uri_document_callback.cpp @@ -12,12 +12,12 @@ using valijson::SchemaParser; using valijson::adapters::RapidJsonAdapter; using valijson::Validator; -class TestFetchDocumentCallback : public ::testing::Test +class TestFetchAbsoluteUriDocumentCallback : public ::testing::Test { }; -const rapidjson::Document * fetchDocument(const std::string &uri) +const rapidjson::Document * fetchAbsoluteUriDocument(const std::string &uri) { EXPECT_STREQ("http://localhost:1234/", uri.c_str()); @@ -43,12 +43,12 @@ const rapidjson::Document * fetchDocument(const std::string &uri) return fetchedRoot; } -void freeDocument(const rapidjson::Document *adapter) +void freeAbsoluteUriDocument(const rapidjson::Document *adapter) { delete adapter; } -TEST_F(TestFetchDocumentCallback, Basics) +TEST_F(TestFetchAbsoluteUriDocumentCallback, Basics) { // Define schema rapidjson::Document schemaDocument; @@ -60,8 +60,8 @@ TEST_F(TestFetchDocumentCallback, Basics) // Parse schema document Schema schema; SchemaParser schemaParser; - schemaParser.populateSchema(schemaDocumentAdapter, schema, fetchDocument, - freeDocument); + schemaParser.populateSchema(schemaDocumentAdapter, schema, fetchAbsoluteUriDocument, + freeAbsoluteUriDocument); // Test resulting schema with a valid document rapidjson::Document validDocument; diff --git a/tests/test_fetch_urn_document_callback.cpp b/tests/test_fetch_urn_document_callback.cpp new file mode 100644 index 0000000..0121a9d --- /dev/null +++ b/tests/test_fetch_urn_document_callback.cpp @@ -0,0 +1,80 @@ + +#include + +#include + +#include +#include +#include + +using valijson::Schema; +using valijson::SchemaParser; +using valijson::adapters::RapidJsonAdapter; +using valijson::Validator; + +class TestFetchUrnDocumentCallback : public ::testing::Test +{ + +}; + +const rapidjson::Document * fetchUrnDocument(const std::string &uri) +{ + EXPECT_STREQ("urn:mvn:example.schema.common:status:1.1.0", uri.c_str()); + + rapidjson::Document *fetchedRoot = new rapidjson::Document(); + fetchedRoot->SetObject(); + + rapidjson::Value valueOfTypeAttribute; + valueOfTypeAttribute.SetString("string", fetchedRoot->GetAllocator()); + + rapidjson::Value schemaOfTestProperty; + schemaOfTestProperty.SetObject(); + schemaOfTestProperty.AddMember("type", valueOfTypeAttribute, + fetchedRoot->GetAllocator()); + + rapidjson::Value propertiesConstraint; + propertiesConstraint.SetObject(); + propertiesConstraint.AddMember("test", schemaOfTestProperty, + fetchedRoot->GetAllocator()); + + fetchedRoot->AddMember("properties", propertiesConstraint, + fetchedRoot->GetAllocator()); + + return fetchedRoot; +} + +void freeUrnDocument(const rapidjson::Document *adapter) +{ + delete adapter; +} + +TEST_F(TestFetchUrnDocumentCallback, Basics) +{ + // Define schema + rapidjson::Document schemaDocument; + RapidJsonAdapter schemaDocumentAdapter(schemaDocument); + schemaDocument.SetObject(); + schemaDocument.AddMember("$ref", "urn:mvn:example.schema.common:status:1.1.0", + schemaDocument.GetAllocator()); + + // Parse schema document + Schema schema; + SchemaParser schemaParser; + schemaParser.populateSchema(schemaDocumentAdapter, schema, fetchUrnDocument, + freeUrnDocument); + + // Test resulting schema with a valid document + rapidjson::Document validDocument; + validDocument.SetObject(); + validDocument.AddMember("test", "valid", schemaDocument.GetAllocator()); + Validator validator; + EXPECT_TRUE(validator.validate(schema, RapidJsonAdapter(validDocument), + NULL)); + + // Test resulting schema with an invalid document + rapidjson::Document invalidDocument; + invalidDocument.SetObject(); + invalidDocument.AddMember("test", 123, schemaDocument.GetAllocator()); + EXPECT_FALSE(validator.validate(schema, RapidJsonAdapter(invalidDocument), + NULL)); +} From eac0859cd82775870016214e99a754af19708d25 Mon Sep 17 00:00:00 2001 From: Ray Vincent Date: Tue, 24 Aug 2021 13:39:05 -0700 Subject: [PATCH 2/5] Ensure that urn NID cannot include '.' --- include/valijson/internal/uri.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/valijson/internal/uri.hpp b/include/valijson/internal/uri.hpp index 232bfa0..777ca2f 100644 --- a/include/valijson/internal/uri.hpp +++ b/include/valijson/internal/uri.hpp @@ -26,7 +26,7 @@ inline bool isUriAbsolute(const std::string &documentUri) */ inline bool isUrn(const std::string &documentUri) { static const std::regex pattern( - "^((urn)|(URN)):[a-zA-Z0-9]+[-]{0,1}[\\.a-zA-Z0-9]+(:[-a-zA-Z0-9\\\\._~%!" + "^((urn)|(URN)):[a-zA-Z0-9]+[-]{0,1}[a-zA-Z0-9]+(:[-a-zA-Z0-9\\\\._~%!" "$&'()\\/*+,;=]+)+(\\?[-a-zA-Z0-9\\\\._~%!$&'()\\/" "*+,;:=]+){0,1}(#[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}"); From 653d515d32a1f396a0dd2612eebc2c529a799c5e Mon Sep 17 00:00:00 2001 From: Ray Vincent Date: Tue, 24 Aug 2021 13:43:00 -0700 Subject: [PATCH 3/5] Ensure that urn regex checks to end of string --- include/valijson/internal/uri.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/valijson/internal/uri.hpp b/include/valijson/internal/uri.hpp index 777ca2f..6adc1e7 100644 --- a/include/valijson/internal/uri.hpp +++ b/include/valijson/internal/uri.hpp @@ -28,7 +28,7 @@ inline bool isUrn(const std::string &documentUri) { static const std::regex pattern( "^((urn)|(URN)):[a-zA-Z0-9]+[-]{0,1}[a-zA-Z0-9]+(:[-a-zA-Z0-9\\\\._~%!" "$&'()\\/*+,;=]+)+(\\?[-a-zA-Z0-9\\\\._~%!$&'()\\/" - "*+,;:=]+){0,1}(#[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}"); + "*+,;:=]+){0,1}(#[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}$"); return std::regex_match(documentUri, pattern); } From f787a8a7a70e91d9281c3e17c7d9a2322830ec75 Mon Sep 17 00:00:00 2001 From: Ray Vincent Date: Tue, 24 Aug 2021 13:51:32 -0700 Subject: [PATCH 4/5] Update urn regex to match widely accepted expression --- include/valijson/internal/uri.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/valijson/internal/uri.hpp b/include/valijson/internal/uri.hpp index 6adc1e7..91b3fae 100644 --- a/include/valijson/internal/uri.hpp +++ b/include/valijson/internal/uri.hpp @@ -26,9 +26,7 @@ inline bool isUriAbsolute(const std::string &documentUri) */ inline bool isUrn(const std::string &documentUri) { static const std::regex pattern( - "^((urn)|(URN)):[a-zA-Z0-9]+[-]{0,1}[a-zA-Z0-9]+(:[-a-zA-Z0-9\\\\._~%!" - "$&'()\\/*+,;=]+)+(\\?[-a-zA-Z0-9\\\\._~%!$&'()\\/" - "*+,;:=]+){0,1}(#[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}$"); + "^(?i:urn:(?!urn:)([a-z0-9][a-z0-9-]{1,31}):((?:[-a-z0-9()+,.:=@;$_!*'&~\\/]|%[0-9a-f]{2})+)(?:\\?\\+(.*?))?(?:\\?=(.*?))?(?:#(.*?))?)$"); return std::regex_match(documentUri, pattern); } From 3442709aa7f5929e3696333ddb925b02bfec8738 Mon Sep 17 00:00:00 2001 From: Ray Vincent Date: Wed, 25 Aug 2021 16:55:01 -0700 Subject: [PATCH 5/5] Update urn regex expression to be std::regex safe --- include/valijson/internal/uri.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/valijson/internal/uri.hpp b/include/valijson/internal/uri.hpp index 91b3fae..17a9c9d 100644 --- a/include/valijson/internal/uri.hpp +++ b/include/valijson/internal/uri.hpp @@ -22,11 +22,11 @@ inline bool isUriAbsolute(const std::string &documentUri) /** * @brief Placeholder function to check whether a URI is a URN * - * This function validates that the URI matches the RFC 8141 + * This function validates that the URI matches the RFC 8141 spec */ inline bool isUrn(const std::string &documentUri) { static const std::regex pattern( - "^(?i:urn:(?!urn:)([a-z0-9][a-z0-9-]{1,31}):((?:[-a-z0-9()+,.:=@;$_!*'&~\\/]|%[0-9a-f]{2})+)(?:\\?\\+(.*?))?(?:\\?=(.*?))?(?:#(.*?))?)$"); + "^((urn)|(URN)):(?!urn:)([a-zA-Z0-9][a-zA-Z0-9-]{1,31})(:[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;=]+)+(\\?[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}(#[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}$"); return std::regex_match(documentUri, pattern); }